// AGENTFORGE-IA — barra lateral + barra superior const { useState, useEffect, useMemo, useRef } = React; function Topbar({ theme, onTheme, onCmdK, currentUser, onLoginClick, onLogout, goBack, canGoBack, sessionExpired }) { const { t, lang, setLang } = useI18n(); const [open, setOpen] = useState(false); const [userOpen, setUserOpen] = useState(false); const langTimer = useRef(null); const userTimer = useRef(null); const initials = currentUser ? (currentUser.full_name || currentUser.email || '?').split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase() : null; const roleLabels = { propietario: 'Propietario', administrador: 'Administrador', constructor: 'Builder', revisor: 'Revisor', lector: 'Lector', operador_sensible: 'Operador sensible', }; const sectionLabels = { playground: 'Pruebas', integrations: 'Integraciones', deployment: 'Despliegue', library: 'Biblioteca', crm: 'CRM', scheduling: 'Agenda', billing: 'Facturación', settings: 'Ajustes', runs: 'Ejecuciones', analytics: 'Analítica', admin: 'Admin usuarios', ops_finance: 'Ops Finanzas', ops_sales: 'Ops Ventas', ops_marketing: 'Ops Marketing', ops_operations: 'Ops Operaciones', ops_hr: 'Ops RRHH', ops_support: 'Ops Soporte', }; const roles = Array.isArray(currentUser?.roles) ? currentUser.roles : []; const sections = currentUser?.sections && typeof currentUser.sections === 'object' ? currentUser.sections : {}; const sectionEntries = Object.entries(sections).filter(([, level]) => level === 'view' || level === 'edit'); return (
{canGoBack && ( )} AgentForge v2.4
clearTimeout(langTimer.current)} onMouseLeave={() => { langTimer.current = setTimeout(() => setOpen(false), 250); }} > {open && (
{t('lang.label')}
{[['es','Español','🇪🇸'],['en','English','🇬🇧']].map(([code, name, flag]) => ( ))}
)}
{currentUser ? (
clearTimeout(userTimer.current)} onMouseLeave={() => { userTimer.current = setTimeout(() => setUserOpen(false), 250); }} >
setUserOpen(o => !o)} >
{initials}
{currentUser.full_name || currentUser.email} {(() => { const ROLE_LABELS = { propietario: 'Propietario', administrador: 'Admin', constructor: 'Builder', revisor: 'Revisor', lector: 'Lector', operador_sensible: 'Op. sensible' }; const ROLE_COLORS = { propietario: '#7c3aed', administrador: '#f472b6', constructor: '#34d399', revisor: '#60a5fa', lector: '#fbbf24', operador_sensible: '#ef4444' }; const topRole = currentUser.is_admin ? 'propietario' : (currentUser.roles && currentUser.roles[0]); if (!topRole) return null; return {ROLE_LABELS[topRole] || topRole}; })()}
{userOpen && (
{currentUser.full_name || '—'}
{currentUser.email}
Permisos efectivos
Roles:{' '} {currentUser.is_admin ? 'Admin global' : (roles.length > 0 ? roles.map((r) => roleLabels[r] || r).join(', ') : 'Sin roles')}
{sectionEntries.length > 0 ? ( sectionEntries.map(([section, level]) => ( {(sectionLabels[section] || section)} · {level} )) ) : ( Sin secciones RBAC asignadas )}
)}
) : ( )}
); } // Rutas que requieren solo login (cualquier usuario registrado) const SIDEBAR_USER = new Set(['library', 'builder', 'playground', 'integrations', 'deployment']); // Rutas que requieren perfil admin/trabajador const SIDEBAR_ADMIN = new Set([ 'crm', 'scheduling', 'plans', 'billing', 'settings', 'runs', 'analytics', 'admin', ]); const SIDEBAR_SENSITIVE = new Set([ 'ops_finance', 'ops_sales', 'ops_marketing', 'ops_operations', 'ops_hr', 'ops_support', ]); const STRICT_ADMIN_ONLY = new Set(['crm', 'scheduling']); const USER_RBAC_ROUTES = new Set(['playground', 'integrations']); function Sidebar({ route, setRoute, currentUser }) { const { t } = useI18n(); const canAccessUserRbacRoute = (section) => { if (!USER_RBAC_ROUTES.has(section)) return true; if (!currentUser) return false; if (currentUser.is_admin) return true; return !!(currentUser.sections && currentUser.sections[section]); }; const openContactForm = () => { window.dispatchEvent(new CustomEvent('af:contact:open', { detail: { public: true } })); }; const adminChildren = [ { id: 'plans', label: t('sb.plans'), icon: 'layers' }, { id: 'billing', label: t('sb.billing'), icon: 'wallet' }, ]; if (currentUser && (currentUser.is_admin || (currentUser.sections && Object.keys(currentUser.sections).length > 0))) { adminChildren.push({ id: 'admin', label: 'Admin usuarios', icon: 'shield' }); } const sensitiveChildren = [ { id: 'ops_finance', label: t('sb.ops_finance'), icon: 'wallet' }, { id: 'ops_sales', label: t('sb.ops_sales'), icon: 'megaphone' }, { id: 'ops_marketing', label: t('sb.ops_marketing'), icon: 'sparkles' }, { id: 'ops_operations', label: t('sb.ops_operations'), icon: 'grid' }, { id: 'ops_hr', label: t('sb.ops_hr'), icon: 'users' }, { id: 'ops_support', label: t('sb.ops_support'), icon: 'headset' }, ]; const items = [ { group: t('sb.group.space'), children: [ { id: 'home', label: t('sb.home'), icon: 'home' }, { id: 'marketplace', label: t('sb.marketplace'), icon: 'store', count: '1.085' }, { id: 'library', label: t('sb.library'), icon: 'grid', count: 6 }, { id: 'builder', label: t('sb.builder'), icon: 'flow' }, { id: 'playground', label: t('sb.playground'), icon: 'chat' }, { id: 'integrations', label: t('sb.integrations'), icon: 'plug', count: 18 }, { id: 'deployment', label: t('sb.deployment'), icon: 'cloud' }, ]}, { group: t('sb.group.observe'), children: [ { id: 'crm', label: t('sb.crm'), icon: 'users', count: 60 }, { id: 'scheduling', label: t('sb.scheduling'), icon: 'calendar' }, { id: 'runs', label: t('sb.runs'), icon: 'logs', count: 412 }, { id: 'analytics', label: t('sb.analytics'), icon: 'chart' }, ]}, { group: t('sb.group.admin'), children: adminChildren }, { group: t('sb.group.ops'), children: sensitiveChildren }, ]; return ( ); } // ── Booking Modal global ─────────────────────────────────────────────────────── function BookingModal({ onClose, prefill = {}, currentUser }) { const { lang } = useI18n(); const token = sessionStorage.getItem('af_token') || ''; const [googleOk, setGoogleOk] = React.useState(null); const [loading, setLoading] = React.useState(false); const [done, setDone] = React.useState(null); const [error, setError] = React.useState(''); const today = new Date().toISOString().slice(0, 10); const durByType = { demo: 30, discovery: 20, support: 15, consulting: 45, qbr: 60 }; const localDateTime = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}T${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:00`; const [form, setForm] = React.useState({ title: prefill.title || '', email: prefill.email || '', type: prefill.type || 'demo', date: prefill.date || today, start: prefill.start || '10:00', duration: prefill.duration || durByType[prefill.type] || 30, }); React.useEffect(() => { if (!token) { setGoogleOk(false); return; } fetch('/api/v1/scheduling/google/status', { headers: { Authorization: 'Bearer ' + token } }) .then(r => r.json()).then(d => setGoogleOk(!!d?.connected)).catch(() => setGoogleOk(false)); }, [token]); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); const types = [ { id: 'demo', label: lang === 'en' ? 'Sales demo (30 min)' : 'Demo comercial (30 min)', dur: 30 }, { id: 'discovery', label: lang === 'en' ? 'Discovery call (20 min)' : 'Llamada de descubrimiento (20 min)', dur: 20 }, { id: 'support', label: lang === 'en' ? 'Support session (15 min)' : 'Sesión de soporte (15 min)', dur: 15 }, { id: 'consulting', label: lang === 'en' ? 'Consulting (45 min)' : 'Consultoría (45 min)', dur: 45 }, { id: 'qbr', label: lang === 'en' ? 'Quarterly review (60 min)' : 'Revisión trimestral (60 min)', dur: 60 }, ]; const submit = async () => { if (!form.title.trim()) { setError(lang === 'en' ? 'Name required' : 'Nombre requerido'); return; } setLoading(true); setError(''); try { const endDate = new Date(`${form.date}T${form.start}:00`); endDate.setMinutes(endDate.getMinutes() + Number(form.duration)); const res = await fetch('/api/v1/scheduling/events', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token }, body: JSON.stringify({ title: form.title, start: `${form.date}T${form.start}:00`, end: localDateTime(endDate), description: `Tipo: ${form.type}${form.email ? '\nContacto: ' + form.email : ''}`, attendee_email: form.email || null, event_type: form.type, }), }); const data = await res.json(); if (!res.ok) throw new Error(data.detail || 'Error'); setDone(data.html_link || true); } catch (e) { setError(e.message); } finally { setLoading(false); } }; return (
e.target === e.currentTarget && onClose()}>
{lang === 'en' ? 'Book appointment' : 'Agendar cita'}
{done ? (

{lang === 'en' ? 'Appointment created in Google Calendar' : 'Cita creada en Google Calendar'}

{done !== true && ( {lang === 'en' ? 'Open in Google Calendar' : 'Abrir en Google Calendar'} → )}
) : (
{googleOk === false && (
{lang === 'en' ? 'Google Workspace is not connected. The appointment will be saved in AgentForge and can be synced after connecting Google.' : 'Google Workspace no está conectado. La cita se guardará en AgentForge y podrá sincronizarse al conectar Google.'}
)}
{error &&

{error}

}
)}
); } window.BookingModal = BookingModal; window.Topbar = Topbar; window.Sidebar = Sidebar;