/* Trascend CRM · Shell: login (con 2FA), sidebar por permisos, router */

const ACL = window.TrascendACL;

/* ------------------------------------------------ carga diferida de módulos
   Los módulos de negocio (modules/*.jsx) NO se cargan al abrir la página:
   se piden recién DESPUÉS del login. En producción se sirven por
   api/asset.php, que exige sesión → su código no se puede descargar sin
   estar autenticado. En modo demo (sin PHP) se cargan directo. */
const BUSINESS_MODULES = [
  'onboarding', 'dashboard', 'clientes', 'leads', 'pipeline', 'diagnostico', 'flows', 'qa', 'monitoreo', 'documentos', 'age-billing', 'automatizacion', 'integraciones',
  'informes', 'social', 'calendario', 'inbox', 'finanzas', 'paquetes', 'analitica', 'age', 'comercial',
  'ajustes-roles', 'ajustes-conexiones', 'ajustes-seguridad', 'ajustes',
];
let __modulesPromise = null;
function loadBusinessModules() {
  if (window.__modulesLoaded) return Promise.resolve();
  if (__modulesPromise) return __modulesPromise;
  __modulesPromise = (async () => {
    const V = '20260819p';
    let mode = 'local';
    try { mode = await TrascendAPI.mode(); } catch (e) {}
    for (let i = 0; i < BUSINESS_MODULES.length; i++) {
      const name = BUSINESS_MODULES[i];
      const url = (mode === 'mysql')
        ? ('api/asset.php?f=modules/' + name + '.jsx&v=' + V)
        : ('modules/' + name + '.jsx?v=' + V);
      const res = await fetch(url, { credentials: 'same-origin' });
      if (!res.ok) throw new Error('No se pudo cargar el módulo «' + name + '» (HTTP ' + res.status + '). Volvé a iniciar sesión.');
      const srcCode = await res.text();
      let out;
      try { out = Babel.transform(srcCode, { presets: ['react'], filename: name + '.jsx' }).code; }
      catch (e) { throw new Error('Error al compilar «' + name + '»: ' + ((e && e.message) || e)); }
      const s = document.createElement('script');
      s.setAttribute('data-module', name);
      s.textContent = out; // se ejecuta en ámbito global, igual que un <script> normal
      document.head.appendChild(s);
    }
    window.__modulesLoaded = true;
  })();
  return __modulesPromise;
}

/* ------------------------------------------------ login */
function LoginScreen({ onLogin, expired }) {
  const [email, setEmail] = React.useState('');
  const [pass, setPass] = React.useState('');
  const [code, setCode] = React.useState('');
  const [stage, setStage] = React.useState('cred'); // 'cred' | '2fa'
  const [err, setErr] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [mode, setMode] = React.useState(null);
  const [demoCode, setDemoCode] = React.useState(null);

  React.useEffect(() => { TrascendAPI.mode().then(setMode); }, []);

  // En modo demo no hay app de autenticación: mostramos el código vigente para no quedar afuera.
  React.useEffect(() => {
    if (stage !== '2fa' || mode !== 'local') { setDemoCode(null); return; }
    let live = true;
    const tick = () => { TrascendAPI.auth.demoCode(email).then((c) => { if (live) setDemoCode(c); }); };
    tick();
    const id = setInterval(tick, 1000);
    return () => { live = false; clearInterval(id); };
  }, [stage, mode, email]);

  async function submit(e) {
    e.preventDefault();
    setBusy(true); setErr(null);
    const res = await TrascendAPI.auth.login(email, pass, stage === '2fa' ? code : undefined);
    setBusy(false);
    if (res.ok) { onLogin(res.user); return; }
    if (res.twofa) { setStage('2fa'); if (res.error) setErr(res.error); return; }
    setErr(res.error);
  }

  return (
    <div className="login-screen">
      <div className="login-card">
        <div className="sb-logo">
          <img src="../assets/logo-trascend.png" alt="" />
          <span><b>Trascend</b><em>Operaciones digitales</em></span>
        </div>
        <h2>CRM interno</h2>
        <p className="login-sub">Ingresá con tu cuenta del equipo para gestionar clientes, operaciones y finanzas.</p>
        {expired ? <div className="login-error" style={{ marginTop: 16 }}>Tu sesión se cerró por inactividad. Volvé a ingresar.</div> : null}
        <form className="login-form" onSubmit={submit}>
          {err ? <div className="login-error">{err}</div> : null}
          {stage === 'cred' ? (
            <React.Fragment>
              <div className="field">
                <label htmlFor="lg-email">Email corporativo</label>
                <input id="lg-email" className="input" type="email" required autoComplete="username"
                  placeholder="tunombre@trascendco.com" value={email} onChange={(e) => setEmail(e.target.value)} />
              </div>
              <div className="field">
                <label htmlFor="lg-pass">Contraseña</label>
                <input id="lg-pass" className="input" type="password" required autoComplete="current-password"
                  placeholder="••••••••••" value={pass} onChange={(e) => setPass(e.target.value)} />
              </div>
            </React.Fragment>
          ) : (
            <div className="field">
              <label htmlFor="lg-code"><span className="row" style={{ gap: 6 }}><Icons.shieldCheck style={{ width: 14, height: 14 }} /> Verificación en 2 pasos</span></label>
              <input id="lg-code" className="input mono" inputMode="numeric" maxLength={6} autoFocus
                placeholder="000000" value={code} onChange={(e) => setCode(e.target.value.replace(/\D/g, ''))}
                style={{ letterSpacing: '0.4em', textAlign: 'center', fontSize: '1.2rem' }} />
              {demoCode ? (
                <div className="twofa-demo">Modo demo · tu código ahora: <b className="mono">{demoCode}</b></div>
              ) : (
                <span className="login-hint" style={{ marginTop: 8 }}>Ingresá el código de 6 dígitos de tu app de autenticación.</span>
              )}
            </div>
          )}
          <button className="btn btn-primary" type="submit" disabled={busy}>
            <span>{busy ? 'Verificando…' : (stage === '2fa' ? 'Verificar e ingresar' : 'Ingresar')}</span>
            <Icons.arrow />
          </button>
          {stage === '2fa' ? (
            <button type="button" className="btn btn-ghost btn-sm" onClick={() => { setStage('cred'); setErr(null); setCode(''); }}>Volver</button>
          ) : null}
        </form>
        {mode === 'local' && stage === 'cred' ? (
          <p className="login-hint">
            Demo — probá con:<br />
            <code>valentina@trascendco.com</code> (admin)<br />
            <code>marcos@trascendco.com</code> (gestor) · <code>sofia@trascendco.com</code> (finanzas)<br />
            Contraseña: <code>trascend123</code>
          </p>
        ) : null}
      </div>
    </div>
  );
}

/* ------------------------------------------------ títulos del topbar */
const TITLES = {
  dashboard: 'Dashboard', clientes: 'Clientes y contactos', pipeline: 'Pipeline comercial',
  social: 'Nuestras redes', calendario: 'Nuestro contenido', inbox: 'Bandeja unificada',
  diagnostico: 'Diagnóstico de eficiencia digital', flows: 'Automatizaciones instaladas', qa: 'Trascend QA',
  documentos: 'Documentación del proyecto', monitoreo: 'Monitoreo de la operación',
  leads: 'Atribución y leads', automatizacion: 'Scoring & nurturing',
  integraciones: 'Integraciones', informes: 'Informes y KPIs',
  finanzas: 'Finanzas', ajustes: 'Configuración del CRM',
  age: 'AGE · Atención y seguimiento automático',
};

/* ------------------------------------------------ qué es y para qué sirve cada sección */
const MODULE_INFO = {
  dashboard: { title: 'Dashboard', body: 'Resumen del estado del negocio: KPIs, actividad reciente y alertas. Es el punto de partida diario. Los números se calculan a partir de los demás módulos.' },
  clientes: { title: 'Clientes y contactos', body: 'La ficha de cada cliente de la agencia: datos, contactos, estado y responsable. Desde acá nacen el pipeline, los proyectos y la facturación de cada cuenta.' },
  pipeline: { title: 'Pipeline comercial', body: 'Las oportunidades de venta por etapa (de prospecto a ganado). Cada negocio pertenece a un cliente. Mové las tarjetas entre columnas para cambiar la etapa.' },
  social: { title: 'Nuestras redes', body: 'Las cuentas de Trascend y las que seguimos de referencia: seguidores, crecimiento y handles.', scope: 'Uso interno — no gestionamos redes de clientes.' },
  calendario: { title: 'Nuestro contenido', body: 'Lo que publica Trascend y cuándo: idea → diseño → aprobación → programado → publicado.', scope: 'Uso interno — no producimos contenido para clientes.' },
  inbox: { title: 'Bandeja unificada', body: 'Mensajes de WhatsApp y email de todos los contactos en un solo lugar. Cada hilo se vincula a un contacto y su cliente.' },
  leads: { title: 'Atribución y leads', body: 'Cada oportunidad que entra, con su ORIGEN: campaña, canal, UTM y landing. Acá ves de dónde viene la demanda. Un lead calificado se convierte en cliente y entra al pipeline.', scope: 'Por defecto son leads de la agencia, pero cada lead puede asignarse a un cliente.' },
  diagnostico: { title: 'Diagnóstico de eficiencia digital', body: 'La oferta ancla. Relevás las tareas manuales del cliente, el sistema calcula cuántas horas por mes consume cada una y cuántas devuelve automatizarla. El resultado es el mapa de oportunidades que se le entrega.', flow: 'Sesión de 90 min → cargar tareas (biblioteca o a mano) → mapa de oportunidades → propuesta de implementación.' },
  flows: { title: 'Automatizaciones instaladas', body: 'Cada automatización que instalamos en un cliente, con su blueprint de origen, las horas que devuelve por mes y su mantenimiento. El catálogo de blueprints es lo que permite que el tercer cliente se entregue más rápido que el primero.', flow: 'Blueprint del catálogo → instalación en el cliente → producción → mantenimiento mensual.' },
  documentos: { title: 'Documentación del proyecto', body: 'Todos los documentos internos en un solo lugar: estrategia, despliegue, pruebas y operación. Se leen acá adentro y se exportan a PDF con el botón, sin salir del CRM.', flow: 'Elegí un documento de la lista → se abre en el lector → “Exportar PDF” abre el diálogo de impresión ya paginado.' },
  monitoreo: { title: 'Monitoreo de la operación', body: 'El tablero de guardia: qué está corriendo, qué se cayó y qué falta resolver. Los datos entran solos — cada automatización reporta sus ejecuciones al endpoint de ingesta y el cron abre un incidente cuando algo se rompe o deja de reportar.', flow: 'La automatización reporta → si falla tres veces se marca caída → se abre un incidente → sale el aviso por correo → se cierra cuando vuelve a funcionar.' },
  qa: { title: 'Trascend QA', body: 'Suites de verificación por cliente: qué se prueba, con qué frecuencia y qué falló. Publicá una suite para que el cliente vea el estado de su sistema desde el Portal, sin tener que preguntarlo.', flow: 'Suite → checks (precargados por tipo de sistema) → corrida → hallazgos ordenados por impacto → reprueba.' },
  automatizacion: { title: 'Scoring & nurturing', body: 'Dos motores: SCORING (puntuá leads según lo que hacen) y NURTURING (secuencias de email/WhatsApp automáticas).', flow: 'El scoring lee los eventos del lead (Atribución); al superar el umbral dispara un flujo, que envía por las cuentas conectadas en Ajustes → Conexiones.' },
  integraciones: { title: 'Integraciones', body: 'Conexión con plataformas externas: Meta/Google Ads, Analytics, etc. De acá entran las campañas y los leads con su atribución.', scope: 'Se conectan a nivel agencia; cada campaña/lead se reparte luego a su cliente.' },
  informes: { title: 'Informes y KPIs', body: 'Métricas y objetivos: conversión, ROI, crecimiento. Definís las metas y medís contra ellas. Combina datos de leads, pipeline y finanzas.' },
  finanzas: { title: 'Finanzas', body: 'Ingresos, gastos, facturas y presupuestos por cliente, con tipo de cambio multi-moneda. Es la salud económica de la agencia.' },
  ajustes: { title: 'Configuración del CRM', body: 'Usuarios, roles y permisos, conexiones (mail/WhatsApp), seguridad y tipo de cambio. Solo para administradores.' },
  age: { title: 'AGE · Atención y seguimiento automático', body: 'Administración del producto AGE: cuentas prepago (auto-registro con verificación por email) y comerciales (las habilita el equipo), paquetes y precios editables, y las conexiones (Mercado Pago, WhatsApp, Anthropic). Solo administradores.' },
};

function Sidebar({ perm, route, setRoute, onLogout, counts, open, onClose, onOpenSecurity, onOpenPortal }) {
  const items = perm.visibleModules();
  const sections = [];
  items.forEach((n) => { if (!sections.includes(n.section)) sections.push(n.section); });
  const user = perm.user;
  return (
    <aside className={'sidebar' + (open ? ' is-open' : '')} data-screen-label="Sidebar">
      <div className="sb-logo">
        <img src="../assets/logo-trascend.png" alt="" />
        <span><b>Trascend</b><em>CRM interno</em></span>
      </div>
      {sections.map((sec) => (
        <React.Fragment key={sec}>
          <p className="sb-section">{sec}</p>
          <nav className="sb-nav" aria-label={sec}>
            {items.filter((n) => n.section === sec).map((n) => {
              const Ic = Icons[n.icon];
              return (
                <button key={n.key} className={"sb-item" + (route === n.key ? " is-active" : "")}
                  onClick={() => { setRoute(n.key); onClose && onClose(); }}>
                  <Ic /><span>{n.label}</span>
                  {n.key === 'inbox' && counts.unread > 0 ? <span className="sb-count">{counts.unread}</span> : null}
                </button>
              );
            })}
          </nav>
        </React.Fragment>
      ))}
      <div className="sb-footer">
        <div className="sb-user">
          <Avatar name={user.name} />
          <span className="col" style={{ gap: 0, minWidth: 0 }}>
            <b>{user.name}</b>
            <span>{perm.roleLabel}</span>
          </span>
        </div>
        <button className="sb-item" onClick={onOpenPortal}>
          <Icons.external /><span>Portal de clientes</span>
        </button>
        <button className="sb-item" onClick={onOpenSecurity}>
          <Icons.shieldCheck /><span>Seguridad / 2FA</span>
        </button>
        <button className="sb-item" onClick={onLogout} style={{ marginTop: 4 }}>
          <Icons.logout /><span>Cerrar sesión</span>
        </button>
      </div>
    </aside>
  );
}

/* ------------------------------------------------ app raíz */
function App() {
  const [user, setUser] = React.useState(undefined); // undefined = cargando
  const [roles, setRoles] = React.useState(null);
  const [settings, setSettings] = React.useState(null);
  const [modulesReady, setModulesReady] = React.useState(!!window.__modulesLoaded);
  const [moduleErr, setModuleErr] = React.useState(null);

  // Guardia: si roles o settings nunca llegan (error de API), desbloquear igual
  React.useEffect(() => {
    const t = setTimeout(() => {
      setRoles(r => r === null ? [] : r);
      setSettings(s => s === null ? {} : s);
      setUser(u => u === undefined ? null : u);
    }, 6000);
    return () => clearTimeout(t);
  }, []);

  // Cargar los módulos de negocio recién cuando hay un usuario logueado.
  React.useEffect(() => {
    if (!user || modulesReady) return;
    let alive = true;
    loadBusinessModules()
      .then(() => { if (alive) setModulesReady(true); })
      .catch((e) => { if (alive) setModuleErr((e && e.message) || String(e)); });
    return () => { alive = false; };
  }, [user, modulesReady]);

  const [route, setRouteRaw] = React.useState(() => (location.hash || '#dashboard').slice(1).split('/')[0]);
  const [routeArg, setRouteArg] = React.useState(() => (location.hash.split('/')[1] || null));
  const [unread, setUnread] = React.useState(0);
  const [backendMode, setBackendMode] = React.useState(null);
  const [expired, setExpired] = React.useState(false);
  const [navOpen, setNavOpen] = React.useState(false);
  const [twofaOpen, setTwofaOpen] = React.useState(false);
  const [portalHubOpen, setPortalHubOpen] = React.useState(false);

  const setRoute = (r, arg) => { location.hash = arg != null ? `${r}/${arg}` : r; };

  const reloadRoles = React.useCallback(() => TrascendAPI.roles.list().then(setRoles).catch(() => setRoles([])), []);
  const reloadSettings = React.useCallback(() => TrascendAPI.settings.get().then(setSettings).catch(() => setSettings({})), []);

  React.useEffect(() => {
    const onHash = () => {
      const [r, arg] = location.hash.slice(1).split('/');
      setRouteRaw(r || 'dashboard');
      setRouteArg(arg != null ? Number(arg) : null);
    };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);

  React.useEffect(() => {
    TrascendAPI.auth.current().then((u) => setUser(u || null)).catch(() => setUser(null));
    TrascendAPI.mode().then(setBackendMode).catch(() => setBackendMode('local'));
    reloadRoles(); reloadSettings();
  }, [reloadRoles, reloadSettings]);

  React.useEffect(() => { setNavOpen(false); }, [route]);

  const refreshUnread = React.useCallback(() => {
    TrascendAPI.threads.list().then((ts) => setUnread(ts.reduce((a, t) => a + (t.unread || 0), 0)));
  }, []);
  React.useEffect(() => { if (user) refreshUnread(); }, [user, refreshUnread]);

  // expiración de sesión por inactividad + heartbeat + refresco de roles
  React.useEffect(() => {
    if (!user) return;
    let last = Date.now();
    let ticks = 0;
    const touch = () => { const now = Date.now(); if (now - last > 15000) { last = now; TrascendAPI.auth.touch(); } };
    const events = ['mousemove', 'keydown', 'click', 'scroll'];
    events.forEach((ev) => window.addEventListener(ev, touch, { passive: true }));
    const iv = setInterval(async () => {
      const u = await TrascendAPI.auth.current();
      if (!u) {
        setUser(null); setExpired(true);
        try { history.replaceState(null, '', location.pathname); } catch (e) { location.hash = ''; }
        return;
      }
      /* Si el rol o el estado del user cambió en la base (admin lo editó
         desde otra sesión), refrescamos el user local — perm recomputa
         en el siguiente render. */
      if (u && (u.role !== user.role || u.active !== user.active)) setUser(u);
      /* Cada 3 ticks (~60s) refrescamos los roles + settings. Así cuando
         el admin cambia perms de un rol, cualquier sesión activa se pone
         al día en <= 1 minuto sin necesidad de logout. */
      ticks++;
      if (ticks % 3 === 0) { reloadRoles(); reloadSettings(); }
    }, 20000);
    /* Al volver a la pestaña, refrescar en el acto. */
    const onVis = () => { if (document.visibilityState === 'visible') { reloadRoles(); reloadSettings(); TrascendAPI.auth.current().then((u) => u && setUser(u)).catch(() => {}); } };
    document.addEventListener('visibilitychange', onVis);
    return () => { events.forEach((ev) => window.removeEventListener(ev, touch)); clearInterval(iv); document.removeEventListener('visibilitychange', onVis); };
  }, [user, reloadRoles, reloadSettings]);

  if (user === undefined || roles === null || settings === null) return <ConfirmHost />;

  const perm = ACL.makePerm(user || { role: null }, roles, settings);

  if (!user) return (
    <React.Fragment>
      <LoginScreen expired={expired} onLogin={(u) => {
        setExpired(false); setUser(u);
        const p = ACL.makePerm(u, roles, settings);
        setRoute(p.homeRoute());
        reloadRoles(); reloadSettings();
      }} />
      <ConfirmHost />
      <ApiErrorHost />
    </React.Fragment>
  );

  if (moduleErr) return (
    <div className="login-screen">
      <div className="login-card" style={{ textAlign: 'center' }}>
        <h2>No se pudieron cargar los módulos</h2>
        <p className="login-sub" style={{ margin: '8px 0 0' }}>{moduleErr}</p>
        <button className="btn btn-primary" style={{ marginTop: 18, width: '100%', justifyContent: 'center' }}
          onClick={() => { window.__modulesLoaded = false; __modulesPromise = null; setModuleErr(null); setModulesReady(false); }}>Reintentar</button>
      </div>
    </div>
  );
  if (!modulesReady) return (
    <div className="login-screen">
      <div className="app-boot"><span className="app-boot-spin" aria-hidden="true"></span><span>Cargando módulos…</span></div>
    </div>
  );

  const visible = perm.visibleModules().map((m) => m.key);
  const active = visible.includes(route) ? route : (visible[0] || 'dashboard');

  const pageProps = { user, perm, roles, settings, reloadRoles, reloadSettings, setRoute };
  const pages = {
    dashboard:  <DashboardPage {...pageProps} />,
    clientes:   <ClientesPage {...pageProps} clientId={routeArg} />,
    leads:      <LeadsPage {...pageProps} leadId={routeArg} />,
    pipeline:   <PipelinePage {...pageProps} />,
    diagnostico: <DiagnosticoPage {...pageProps} />,
    paquetes:   <PaquetesPage {...pageProps} />,
    analitica:  <AnaliticaPage {...pageProps} />,
    flows:       <FlowsPage {...pageProps} />,
    qa:          <QaPage {...pageProps} />,
    monitoreo:   <MonitoreoPage {...pageProps} />,
    documentos:  <DocumentosPage {...pageProps} />,
    automatizacion: <AutomatizacionPage {...pageProps} />,
    integraciones:  <IntegracionesPage {...pageProps} />,
    informes:   <InformesPage {...pageProps} />,
    social:     <SocialPage {...pageProps} />,
    calendario: <CalendarioPage {...pageProps} />,
    inbox:      <InboxPage {...pageProps} onRead={refreshUnread} />,
    finanzas:   <FinanzasPage {...pageProps} />,
    age:        <AGEAdminPage {...pageProps} />,
    comercial:  <ComercialPage {...pageProps} />,
    ajustes:    <AjustesPage {...pageProps} />,
  };

  return (
    <div className="shell">
      <Sidebar perm={perm} route={active} setRoute={setRoute} counts={{ unread }}
        open={navOpen} onClose={() => setNavOpen(false)} onOpenSecurity={() => setTwofaOpen(true)} onOpenPortal={() => setPortalHubOpen(true)}
        onLogout={async () => {
          const ok = await confirmDialog({ title: 'Cerrar sesión', message: '¿Querés salir del CRM?', confirmLabel: 'Cerrar sesión' });
          if (ok) {
            TrascendAPI.auth.logout(); setUser(null); setExpired(false);
            /* limpia el hash residual (#age, #ajustes, etc.) y vuelve a /crm/
               tal cual, sin dejar rastro de la sección donde se estaba. */
            try { history.replaceState(null, '', location.pathname); }
            catch (e) { location.hash = ''; }
          }
        }} />
      <div className={'sb-overlay' + (navOpen ? ' is-open' : '')} onClick={() => setNavOpen(false)}></div>
      <div className="main">
        <header className="topbar">
          <button className="topbar-burger" onClick={() => setNavOpen(true)} aria-label="Abrir menú"><Icons.menu /></button>
          <h1>{TITLES[active] || (ACL.MODULE_MAP[active] || {}).label}</h1>
          {MODULE_INFO[active] ? (
            <InfoButton title={MODULE_INFO[active].title}>
              <p>{MODULE_INFO[active].body}</p>
              {MODULE_INFO[active].scope ? <p className="info-tag"><b>Alcance:</b> {MODULE_INFO[active].scope}</p> : null}
              {MODULE_INFO[active].flow ? <p className="info-tag"><b>Cómo conecta:</b> {MODULE_INFO[active].flow}</p> : null}
            </InfoButton>
          ) : null}
          <div className="topbar-actions">
            <NotifBell onNavigate={(routeStr) => {
              const [r, arg] = String(routeStr).split('/');
              setRoute(r, arg);
            }} />
            <button className="btn btn-ghost btn-icon" title="Refrescar permisos y datos"
              onClick={async () => {
                try {
                  const u = await TrascendAPI.auth.current();
                  if (u) setUser(u);
                  await reloadRoles();
                  await reloadSettings();
                } catch (e) {}
              }}>
              <Icons.refresh />
            </button>
            <span className="badge badge-dim" title={'Rol: ' + perm.roleLabel + (perm.role ? ' (' + perm.role.key + ')' : '')}><Icons.shield style={{ width: 12, height: 12 }} /> {perm.roleLabel}</span>
            {backendMode === 'mysql'
              ? <span className="badge badge-dim mono" title="Conectado a la base de datos">MySQL · conectado</span>
              : <span className="badge badge-dim mono" title="Entorno de demostración">DEMO · datos locales</span>}
          </div>
        </header>
        <div className="content" data-screen-label={TITLES[active] || active}>
          {pages[active]}
        </div>
      </div>
      <ConfirmHost />
      <ApiErrorHost />
      {twofaOpen ? <TwoFASetup user={user} forced={!!(settings && Number(settings.require_2fa) === 1)} onClose={() => setTwofaOpen(false)} onSaved={() => TrascendAPI.auth.current().then((u) => u && setUser(u))} /> : null}
      {portalHubOpen ? <PortalHubModal onClose={() => setPortalHubOpen(false)} onGoClient={(id) => { setPortalHubOpen(false); setRoute('clientes', id); }} /> : null}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
