// LOLA Consola Web — íconos + shell (sidebar, topbar, router)
// Exporta window.WI (íconos) y monta <ConsoleApp/>

const Ico = (p) => React.createElement('svg', { width: p.size || 19, height: p.size || 19, viewBox: '0 0 24 24',
  fill: 'none', stroke: 'currentColor', strokeWidth: p.sw || 1.8, strokeLinecap: 'round', strokeLinejoin: 'round' }, p.children);

const WI = {
  Grid: (p) => <Ico {...p}><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></Ico>,
  Check: (p) => <Ico {...p}><path d="M20 6 9 17l-5-5"/></Ico>,
  X: (p) => <Ico {...p}><path d="M18 6 6 18M6 6l12 12"/></Ico>,
  Wallet: (p) => <Ico {...p}><path d="M19 7H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-1"/><path d="M3 9V7a2 2 0 0 1 2-2h12v4"/><circle cx="17" cy="13" r="1.4"/></Ico>,
  Users: (p) => <Ico {...p}><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></Ico>,
  User: (p) => <Ico {...p}><circle cx="12" cy="8" r="4"/><path d="M4 21v-1a6 6 0 0 1 6-6h4a6 6 0 0 1 6 6v1"/></Ico>,
  Doc: (p) => <Ico {...p}><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></Ico>,
  Book: (p) => <Ico {...p}><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></Ico>,
  Gear: (p) => <Ico {...p}><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></Ico>,
  Bell: (p) => <Ico {...p}><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></Ico>,
  Chat: (p) => <Ico {...p}><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></Ico>,
  Shield: (p) => <Ico {...p}><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></Ico>,
  Trend: (p) => <Ico {...p}><path d="M22 7l-8.5 8.5-5-5L2 17"/><path d="M16 7h6v6"/></Ico>,
  Tag: (p) => <Ico {...p}><path d="M20.59 13.41 13.42 20.6a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><circle cx="7" cy="7" r="1.2"/></Ico>,
  Percent: (p) => <Ico {...p}><path d="M19 5 5 19"/><circle cx="6.5" cy="6.5" r="2.5"/><circle cx="17.5" cy="17.5" r="2.5"/></Ico>,
  Clock: (p) => <Ico {...p}><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></Ico>,
  Logout: (p) => <Ico {...p}><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="M16 17l5-5-5-5"/><path d="M21 12H9"/></Ico>,
  Search: (p) => <Ico {...p}><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></Ico>,
  Download: (p) => <Ico {...p}><path d="M12 3v12"/><path d="m7 12 5 5 5-5"/><path d="M5 21h14"/></Ico>,
  Truck: (p) => <Ico {...p}><path d="M10 17h4V5H2v12h3"/><path d="M15 17h6v-5l-3-3h-3v8z"/><circle cx="7.5" cy="17.5" r="1.5"/><circle cx="17.5" cy="17.5" r="1.5"/></Ico>,
  Building: (p) => <Ico {...p}><path d="M3 21h18"/><path d="M5 21V7l8-4v18"/><path d="M19 21V11l-6-4"/><path d="M9 9v.01M9 12v.01M9 15v.01M9 18v.01"/></Ico>,
  Scan: (p) => <Ico {...p}><path d="M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="11" r="3"/></Ico>,
  Plus: (p) => <Ico {...p}><path d="M12 5v14M5 12h14"/></Ico>,
  Mail: (p) => <Ico {...p}><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></Ico>,
  Globe: (p) => <Ico {...p}><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a15 15 0 0 1 0 18 15 15 0 0 1 0-18z"/></Ico>,
  Play: (p) => <Ico {...p}><path d="M7 5v14l11-7z"/></Ico>,
  Cursor: (p) => <Ico {...p}><path d="m3 3 7.07 17 2.51-7.42L20 10.09z"/></Ico>,
  Megaphone: (p) => <Ico {...p}><path d="m3 11 18-5v12L3 13"/><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"/></Ico>,
};

const NAV = [
  { id: 'dashboard', label: 'Dashboard', icon: 'Grid', cat: 'General' },
  { id: 'aprobaciones', label: 'Aprobaciones', icon: 'Check', cat: 'Operación' },
  { id: 'comisiones', label: 'Comisiones', icon: 'Wallet', cat: 'Operación' },
  { id: 'pagos', label: 'Pagos', icon: 'Tag', cat: 'Operación' },
  { id: 'usuarios', label: 'Usuarios', icon: 'Users', cat: 'Operación' },
  { id: 'publicidad', label: 'Publicidad', icon: 'Megaphone', cat: 'Operación' },
  { id: 'catalogos', label: 'Catálogos', icon: 'Truck', cat: 'Operación' },
  { id: 'seguridad', label: 'Seguridad', icon: 'Shield', cat: 'Sistema' },
  { id: 'bitacora', label: 'Auditoría', icon: 'Book', cat: 'Sistema' },
  { id: 'config', label: 'Configuración', icon: 'Gear', cat: 'Sistema' },
  { id: 'pruebas', label: 'Pruebas funcionales', icon: 'Grid', cat: 'Sistema' },
  { id: 'perfil', label: 'Mi perfil', icon: 'User', cat: 'Cuenta' },
];
const NAV_CATS = ['General', 'Operación', 'Sistema', 'Cuenta'];
const TITLES = {
  dashboard: ['Dashboard', 'Analítica financiera y operativa en tiempo real'],
  aprobaciones: ['Aprobaciones', 'Revisa y autoriza contratos de clientes y proveedores'],
  comisiones: ['Comisiones', 'Ingresos de la plataforma y tasas por categoría'],
  pagos: ['Pagos y tesorería', 'Confirma pagos en efectivo/transferencia, recargas y retiros en revisión'],
  usuarios: ['Usuarios', 'Gestión de clientes y proveedores'],
  publicidad: ['Publicidad', 'Campañas y anuncios en redes sociales y en la app'],
  catalogos: ['Catálogos', 'Tipos de transporte y de carga que ven cliente y proveedor al solicitar un servicio'],
  seguridad: ['Usuarios, roles y permisos', 'Acceso denegado por defecto. Concede permisos explícitos por módulo y acción.'],
  perfil: ['Mi perfil', 'Tus datos personales, contraseña y acceso por reconocimiento facial.'],
  bitacora: ['Auditoría', 'Registro inmutable de todo lo que ocurre en la plataforma: accesos, cambios y acciones — quién, qué y cuándo.'],
  config: ['Configuración', 'Empresa, notificaciones, seguridad, biometría y equipo'],
  pruebas: ['Pruebas funcionales', 'Recorridos de los casos de uso: usuario nuevo, con cuenta, transportista, administrador y traslado completo.'],
};

// Hook responsive compartido (lo usan también dashboard.jsx y views.jsx, que cargan después).
window.useIsMobile = function (bp) {
  bp = bp || 860;
  const get = () => (typeof window !== 'undefined' ? window.innerWidth <= bp : false);
  const [m, setM] = React.useState(get);
  React.useEffect(() => {
    const h = () => setM(get());
    window.addEventListener('resize', h);
    return () => window.removeEventListener('resize', h);
  }, []);
  return m;
};

function InstallBtn({ t, I }) {
  const [can, setCan] = React.useState(!!window.__lolaCanInstall);
  React.useEffect(() => {
    const h = () => setCan(!!window.__lolaCanInstall);
    window.addEventListener('lola-installable', h);
    return () => window.removeEventListener('lola-installable', h);
  }, []);
  if (!can) return null;
  return (
    <button onClick={() => window.__lolaPromptInstall && window.__lolaPromptInstall()}
      style={{ display: 'inline-flex', alignItems: 'center', gap: 8, height: 40, padding: '0 15px',
        borderRadius: 10, border: 'none', cursor: 'pointer', backgroundColor: t.brand, color: '#fff',
        fontFamily: t.font, fontSize: 13.5, fontWeight: 700 }}>
      <I.Download size={17}/> Instalar app
    </button>
  );
}

function ConsoleApp() {
  const t = window.WC, I = window.WI;
  const isMobile = window.useIsMobile();
  const [navOpen, setNavOpen] = React.useState(false);
  const [view, setView] = React.useState(() => location.hash.slice(1) || 'dashboard');
  // Usuario de la sesión (desde el cliente API — sessionStorage, NO localStorage).
  const sessUser = (window.LOLA.api.user && window.LOLA.api.user()) || {};
  const userName = sessUser.nombre || 'Administrador';
  const userMail = sessUser.email || '';
  const userIni = window.LOLA.map.iniciales(userName);
  // Dashboard real (KPIs + analítica + badge de aprobaciones). rangoDias controla la ventana
  // del panel de comisión (7/30/90 días) — al cambiar, se re-consulta el backend con ese rango.
  const [rangoDias, setRangoDias] = React.useState(30);
  const dash = window.useAsync(() => window.LOLA.api.dashboard(rangoDias), [rangoDias]);
  const pendientes = (dash.data && dash.data.kpis && dash.data.kpis.pendientes) || 0;
  React.useEffect(() => {
    const h = () => setView(location.hash.slice(1) || 'dashboard');
    window.addEventListener('hashchange', h); return () => window.removeEventListener('hashchange', h);
  }, []);

  // M2: bloqueo automático por inactividad — lee seguridad.minutosBloqueo de Configuración (real,
  // backend) y bloquea la sesión si no hay mouse/teclado/scroll/touch por ese tiempo.
  const cfgQ = window.useAsync(() => window.LOLA.api.getConfig(), []);
  const minutosBloqueo = (cfgQ.data && cfgQ.data.config && cfgQ.data.config.seguridad && cfgQ.data.config.seguridad.minutosBloqueo) || 15;
  React.useEffect(() => {
    let timer;
    const armar = () => {
      clearTimeout(timer);
      timer = setTimeout(() => { window.__lolaLock && window.__lolaLock(); }, minutosBloqueo * 60 * 1000);
    };
    const eventos = ['mousemove', 'mousedown', 'keydown', 'scroll', 'touchstart'];
    eventos.forEach((ev) => window.addEventListener(ev, armar, { passive: true }));
    armar();
    return () => { clearTimeout(timer); eventos.forEach((ev) => window.removeEventListener(ev, armar)); };
  }, [minutosBloqueo]);
  const go = (id) => { location.hash = id; setView(id); setNavOpen(false); };
  const [title, sub] = TITLES[view] || TITLES.dashboard;

  const Views = {
    dashboard: window.DashboardView, aprobaciones: window.AprobacionesView, comisiones: window.ComisionesView,
    usuarios: window.UsuariosView, publicidad: window.PublicidadView, catalogos: window.CatalogosView, seguridad: window.SeguridadView, bitacora: window.BitacoraView, config: window.ConfiguracionView, perfil: window.PerfilView, pagos: window.PagosView,
    pruebas: window.PruebasView,
  };
  const Current = Views[view] || window.DashboardView;

  return (
    <div style={{ display: 'flex', minHeight: '100vh', background: '#F6F7F9', fontFamily: t.font, color: t.text }}>
      {/* Overlay del cajón en móvil */}
      {isMobile && navOpen && (
        <div onClick={() => setNavOpen(false)} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.5)', zIndex: 55 }}/>
      )}
      {/* Sidebar (cajón deslizable en móvil) */}
      <aside style={{ width: 248, background: '#0C1826', color: '#fff', display: 'flex', flexDirection: 'column',
        ...(isMobile
          ? { position: 'fixed', top: 0, left: 0, height: '100vh', zIndex: 60,
              transform: navOpen ? 'none' : 'translateX(-100%)', transition: 'transform .22s ease',
              boxShadow: navOpen ? '0 0 60px rgba(0,0,0,.55)' : 'none' }
          : { position: 'sticky', top: 0, height: '100vh', flexShrink: 0 }) }}>
        <div style={{ padding: '22px 22px 18px', borderBottom: '1px solid rgba(255,255,255,.08)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
            <div style={{ width: 38, height: 38, borderRadius: 10, background: t.brand, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
              <I.Truck size={21} sw={2}/>
            </div>
            <div>
              <div style={{ fontSize: 17, fontWeight: 800, letterSpacing: -.4 }}>LOLA</div>
              <div style={{ fontSize: 10.5, color: t.accent, fontFamily: t.mono, letterSpacing: 1.2, textTransform: 'uppercase' }}>Consola Admin</div>
            </div>
          </div>
        </div>
        <nav style={{ flex: 1, padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 2, overflowY: 'auto' }}>
          {NAV_CATS.map(cat => (
            <div key={cat} style={{ marginBottom: 8 }}>
              <div style={{ padding: '8px 14px 5px', fontSize: 10, fontFamily: t.mono, letterSpacing: 1.3, textTransform: 'uppercase', color: 'rgba(255,255,255,.32)', fontWeight: 700 }}>{cat}</div>
              {NAV.filter(n => n.cat === cat).map(n => {
                const Icn = I[n.icon]; const active = view === n.id;
                const badge = n.id === 'aprobaciones' && pendientes ? pendientes : null;
                return (
                  <button key={n.id} onClick={() => go(n.id)} style={{ display: 'flex', alignItems: 'center', gap: 12,
                    padding: '10px 14px', borderRadius: 10, border: 'none', cursor: 'pointer', width: '100%', textAlign: 'left',
                    backgroundColor: active ? t.brand : 'transparent', color: active ? '#fff' : 'rgba(255,255,255,.62)',
                    fontFamily: t.font, fontSize: 14, fontWeight: active ? 700 : 500 }}>
                    <Icn size={18}/>
                    <span style={{ flex: 1 }}>{n.label}</span>
                    {badge && <span style={{ backgroundColor: active ? 'rgba(255,255,255,.25)' : t.accent, color: active ? '#fff' : '#241C00',
                      fontSize: 11, fontWeight: 800, fontFamily: t.mono, padding: '1px 7px', borderRadius: 999 }}>{badge}</span>}
                  </button>
                );
              })}
            </div>
          ))}
        </nav>
        <div style={{ padding: '14px 16px', borderTop: '1px solid rgba(255,255,255,.08)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '6px 4px' }}>
            <button onClick={() => go('perfil')} title="Mi perfil" style={{ display: 'flex', alignItems: 'center', gap: 11, flex: 1, minWidth: 0, background: 'none', border: 'none', cursor: 'pointer', padding: 0, textAlign: 'left' }}>
              <div style={{ width: 36, height: 36, borderRadius: 9, background: view === 'perfil' ? t.brand : 'rgba(255,255,255,.12)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700, fontFamily: t.mono, fontSize: 13, color: '#fff', flexShrink: 0 }}>{userIni}</div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 13, fontWeight: 700, color: '#fff', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{userName}</div>
                <div style={{ fontSize: 11, color: 'rgba(255,255,255,.5)', fontFamily: t.mono, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{userMail}</div>
              </div>
            </button>
            <button title="Bloquear sesión" onClick={() => window.__lolaLock && window.__lolaLock()}
              style={{ background: 'none', border: 'none', color: 'rgba(255,255,255,.5)', cursor: 'pointer', padding: 4 }}><I.Shield size={17}/></button>
            <button title="Salir" onClick={() => window.__lolaLogout && window.__lolaLogout()}
              style={{ background: 'none', border: 'none', color: 'rgba(255,255,255,.5)', cursor: 'pointer', padding: 4 }}><I.Logout size={18}/></button>
          </div>
        </div>
      </aside>

      {/* Main */}
      <main style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' }}>
        <header style={{ background: '#fff', borderBottom: `1px solid ${t.border}`, padding: isMobile ? '12px 14px' : '16px 30px',
          display: 'flex', alignItems: 'center', gap: isMobile ? 12 : 20, position: 'sticky', top: 0, zIndex: 20 }}>
          {isMobile && (
            <button onClick={() => setNavOpen(true)} aria-label="Abrir menú" style={{ width: 40, height: 40, borderRadius: 10,
              border: `1px solid ${t.border}`, background: '#fff', cursor: 'pointer', color: t.sec, flexShrink: 0,
              display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M3 12h18M3 6h18M3 18h18"/></svg>
            </button>
          )}
          <div style={{ flex: 1, minWidth: 0 }}>
            <h1 style={{ fontSize: isMobile ? 17 : 21, fontWeight: 800, letterSpacing: -.6, color: t.text }}>{title}</h1>
            <div style={{ fontSize: 12.5, color: t.ter, marginTop: 1 }}>{sub}</div>
          </div>
          {!isMobile && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, background: '#F6F7F9', border: `1px solid ${t.border}`,
            borderRadius: 10, padding: '8px 13px', width: 260, color: t.ter }}>
            <I.Search size={16}/>
            <input placeholder="Buscar usuario, servicio, ID…" style={{ border: 'none', background: 'none', outline: 'none', fontFamily: t.font, fontSize: 13, color: t.text, width: '100%' }}/>
          </div>
          )}
          <InstallBtn t={t} I={I}/>
          <button style={{ position: 'relative', width: 40, height: 40, borderRadius: 10, border: `1px solid ${t.border}`, background: '#fff', cursor: 'pointer', color: t.sec, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <I.Bell size={18}/>
            <span style={{ position: 'absolute', top: 9, right: 10, width: 8, height: 8, borderRadius: '50%', background: t.danger, border: '1.5px solid #fff' }}/>
          </button>
          {!isMobile && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 7, fontSize: 12.5, color: t.success, fontFamily: t.mono, fontWeight: 600 }}>
            <span style={{ width: 8, height: 8, borderRadius: '50%', background: t.success }}/>En línea
          </div>
          )}
        </header>

        <div style={{ padding: isMobile ? '16px 14px 48px' : '24px 30px 60px', flex: 1 }}>
          <Current dash={dash.data} loading={dash.loading} error={dash.error} reload={dash.reload} rangoDias={rangoDias} onRangoChange={setRangoDias}/>
        </div>
      </main>
    </div>
  );
}

// ── Helpers compartidos de datos/estado (usados por todas las vistas) ──
window.useAsync = function (fn, deps) {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  const [n, setN] = React.useState(0);
  React.useEffect(() => {
    let alive = true; setLoading(true); setError(null);
    Promise.resolve(fn())
      .then((d) => { if (alive) { setData(d); setLoading(false); } })
      .catch((e) => { if (alive) { setError(e); setLoading(false); } });
    return () => { alive = false; };
    // eslint-disable-next-line
  }, (deps || []).concat(n));
  return { data, loading, error, reload: () => setN((x) => x + 1) };
};

function Spinner({ size = 22 }) {
  const t = window.WC;
  return (
    <span style={{ display: 'inline-block', width: size, height: size, borderRadius: '50%',
      border: `2.5px solid ${t.grid}`, borderTopColor: t.brand, animation: 'lolaspin .7s linear infinite' }}/>
  );
}
window.Loading = function ({ label }) {
  const t = window.WC;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      gap: 12, padding: '56px 0', color: t.ter }}>
      <style>{'@keyframes lolaspin{to{transform:rotate(360deg)}}'}</style>
      <Spinner/>
      <span style={{ fontSize: 13, fontFamily: t.mono }}>{label || 'Cargando…'}</span>
    </div>
  );
};
window.EmptyState = function ({ icon, title, sub }) {
  const t = window.WC; const I = window.WI; const Icn = I[icon || 'Book'];
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      gap: 10, padding: '48px 24px', textAlign: 'center' }}>
      <span style={{ width: 52, height: 52, borderRadius: 14, background: t.brand + '10', color: t.brand,
        display: 'flex', alignItems: 'center', justifyContent: 'center' }}><Icn size={24}/></span>
      <div style={{ fontSize: 15, fontWeight: 700, color: t.text }}>{title || 'Sin datos todavía'}</div>
      {sub && <div style={{ fontSize: 13, color: t.ter, maxWidth: 360, lineHeight: 1.5 }}>{sub}</div>}
    </div>
  );
};
window.ErrState = function ({ error, onRetry }) {
  const t = window.WC;
  const offline = error && (error.status === 0 || error.message === 'Failed to fetch');
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      gap: 10, padding: '48px 24px', textAlign: 'center' }}>
      <span style={{ width: 52, height: 52, borderRadius: 14, background: t.danger + '12', color: t.danger,
        display: 'flex', alignItems: 'center', justifyContent: 'center' }}><window.WI.Shield size={24}/></span>
      <div style={{ fontSize: 15, fontWeight: 700, color: t.text }}>
        {offline ? 'Sin conexión con el servidor' : 'No se pudieron cargar los datos'}</div>
      <div style={{ fontSize: 13, color: t.ter, maxWidth: 380, lineHeight: 1.5 }}>
        {offline ? 'La API no responde. Verifica que el backend esté corriendo.' : (error && error.message) || ''}</div>
      {onRetry && <button onClick={onRetry} style={window.btnGhost(t)}>Reintentar</button>}
    </div>
  );
};
// Placeholder para gráficas cuando aún no hay datos suficientes.
window.ChartEmpty = function ({ h = 200, label }) {
  const t = window.WC;
  return (
    <div style={{ height: h, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      gap: 6, color: t.ter, background: '#FAFBFC', border: `1px dashed ${t.border}`, borderRadius: 10 }}>
      <window.WI.Trend size={22}/>
      <span style={{ fontSize: 12.5, fontFamily: t.mono }}>{label || 'Se llena con la operación'}</span>
    </div>
  );
};

// Pruebas funcionales — embebidas dentro de la consola (no abre pestaña nueva).
window.PruebasView = function () {
  const t = window.WC;
  return (
    <iframe src="pruebas.html" title="Pruebas funcionales"
      style={{ width: '100%', height: 'calc(100vh - 148px)', minHeight: 520, border: `1px solid ${t.border}`, borderRadius: 14, background: '#0C1826', display: 'block' }}/>
  );
};

window.WI = WI;
window.ConsoleApp = ConsoleApp;
