// ─── Main App ──────────────────────────────────────
// El componente App: dueño de TODO el estado de navegación (route + ~15 selected*Id),
// la hidratación de sesión (GET /api/me) + store (MicosStore.hydrate antes del primer
// render) y el switch gigante que decide qué pantalla renderizar. El router (URL↔estado
// con History API + permisos), los breadcrumbs y la navegación cruzada del directorio
// viven en core/router.jsx, core/breadcrumbs.jsx y core/directorio-nav.jsx (P3, split
// del monolito de 1295 líneas) — este archivo los consume vía sus hooks/funciones.
// Expone: App, HydrationWarningBanner, HydrationErrorScreen (montado uno de los dos
// últimos por el gate de hidratación al final del archivo — ver su header, P5/D4).
// SIEMPRE el último <script> del bundle: su JSX referencia ~50 componentes de pantalla
// como identificadores globales pelados — si corriera antes de que esos scripts
// cargaran, Babel fallaría en runtime con ReferenceError.

function App({ hydrationWarnings }) {
  // P5 (D4, hidratación honesta): colecciones que no se pudieron hidratar al montar
  // (`MicosStore.hydrate()` parcial — ver su header en core/store.js). Estado propio
  // (no solo el prop inicial) para que "Reintentar" del banner pueda vaciarlo sin
  // tener que desmontar/remontar toda la app.
  const [hydWarnings, setHydWarnings] = React.useState(hydrationWarnings || []);
  const retryHydration = React.useCallback(() => {
    if (!(window.MicosStore && typeof window.MicosStore.hydrate === 'function')) return;
    window.MicosStore.hydrate()
      .then((res) => setHydWarnings((res && res.failed) || []))
      .catch((e) => console.error('[micos] reintento de hidratación (parcial) falló:', e));
  }, []);
  const [route, setRoute] = React.useState('dashboard');
  const [theme, setTheme] = React.useState('light');
  const [user, setUser] = React.useState(null);
  const [authReady, setAuthReady] = React.useState(false);
  const [selectedCompraId, setSelectedCompraId] = React.useState(null);
  const [selectedLogisticaId, setSelectedLogisticaId] = React.useState(null);
  const [selectedCajaChicaId, setSelectedCajaChicaId] = React.useState(null);
  const [selectedInventarioId, setSelectedInventarioId] = React.useState(null);
  // P23 (VACs): equipo físico de alquiler seleccionado (inventario_activo) — distinto de
  // selectedInventarioId (producto, inventario_activo era un dominio filtrado dentro del
  // mismo `inventario`, ver screens/inventario/lista.jsx).
  const [selectedActivoId, setSelectedActivoId] = React.useState(null);
  const [docDelete, setDocDelete] = React.useState(null);
  const [selectedEntidadId, setSelectedEntidadId] = React.useState(null);
  const [selectedMedicoId, setSelectedMedicoId] = React.useState(null);
  const [selectedParticularId, setSelectedParticularId] = React.useState(null);
  const [selectedProveedorId, setSelectedProveedorId] = React.useState(null);
  const [selectedExpedienteId, setSelectedExpedienteId] = React.useState(null);
  const [selectedFacturaIndex, setSelectedFacturaIndex] = React.useState(0);
  const [selectedDoc, setSelectedDoc] = React.useState(null);
  const [newDoc, setNewDoc] = React.useState(null);

  // Cross-module list prefilter (set when user clicks a "Registros asociados" card
  // in Entidades / Médicos / Particulares detail). Cleared by user via the
  // "Quitar filtro" button on the list screen.
  // Shape: { module, label, sublabel, predicate, source }
  const [listPrefilter, setListPrefilter] = React.useState(null);

  // Sync theme to html
  React.useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
  }, [theme]);

  // ─── Global cursor-spotlight ──────────────────────
  // Single delegated mousemove handler. Walks up from the event target
  // to the nearest opt-in surface and writes --mx / --my CSS vars.
  // CSS handles the radial gradient and opacity (see styles.css).
  React.useEffect(() => {
    const SELECTOR = '.glow-card, .kpi-card, .kpi-card-tile, .stat-tile, .sidebar-block, .exp-row';
    let raf = 0, lastEv = null;
    const apply = () => {
      raf = 0;
      if (!lastEv) return;
      const el = lastEv.target.closest ? lastEv.target.closest(SELECTOR) : null;
      if (!el) return;
      const r = el.getBoundingClientRect();
      el.style.setProperty('--mx', `${lastEv.clientX - r.left}px`);
      el.style.setProperty('--my', `${lastEv.clientY - r.top}px`);
    };
    const onMove = (e) => {
      lastEv = e;
      if (!raf) raf = requestAnimationFrame(apply);
    };
    document.addEventListener('mousemove', onMove, { passive: true });
    return () => {
      document.removeEventListener('mousemove', onMove);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  // Hidratar la sesión: primero la cuenta REAL logueada (GET /api/me → rol+permisos
  // de la DB) para que el gating del front refleje quién entró; si no hay sesión
  // (o el endpoint no responde), cae al `micos-user` de storage (login visual/demo).
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await fetch('/api/me', { credentials: 'same-origin' });
        if (res.ok) {
          const me = await res.json();
          const initials = ((me.nombre || ' ')[0] + (me.apellido || ' ')[0]).toUpperCase();
          const sess = {
            name: ((me.nombre || '') + ' ' + (me.apellido || '')).trim() || me.email,
            email: me.email, initials, rol: me.rol, permisos: me.permisos || {},
          };
          try { localStorage.setItem('micos-user', JSON.stringify(sess)); } catch (_) {}
          if (!cancelled) { setUser(sess); setAuthReady(true); }
          return;
        }
      } catch (_) { /* sin backend / sin sesión → fallback a storage */ }
      try {
        const raw = localStorage.getItem('micos-user') || sessionStorage.getItem('micos-user');
        if (raw && !cancelled) setUser(JSON.parse(raw));
      } catch (_) {}
      if (!cancelled) setAuthReady(true);
    })();
    return () => { cancelled = true; };
  }, []);

  const handleLogout = async () => {
    try { await fetch('/api/logout', { method: 'POST', credentials: 'same-origin' }); } catch (_) {}
    try { localStorage.removeItem('micos-user'); sessionStorage.removeItem('micos-user'); } catch (_) {}
    setUser(null);
    window.location.replace(window.ROUTES && window.ROUTES.LOGIN || '/login');
  };

  // Restaura el tema guardado al montar (localStorage 'micos-theme'). Antes vivía
  // mezclado en el mismo efecto que el bootstrap del router (deep-link + popstate) por
  // pura casualidad de orden en el archivo monolítico — son dos temas sin relación; el
  // bootstrap del router ahora vive en core/router.jsx (useMicosRouter, más abajo).
  React.useEffect(() => {
    const savedTheme = localStorage.getItem('micos-theme');
    if (savedTheme) setTheme(savedTheme);
  }, []);

  React.useEffect(() => { localStorage.setItem('micos-theme', theme); }, [theme]);

  const onToggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');

  const selectedExpediente = selectedExpedienteId
    ? (EXPEDIENTES.find((e) => e.id === selectedExpedienteId) || null)
    : null;

  // Router (core/router.jsx): URL↔estado con History API (deep-link/back-forward/push)
  // + ROUTE_SECTION/RouteDenied (gate de permisos, consumidos más abajo como globals).
  const { openExpedienteFor } = useMicosRouter({
    route, setRoute, user,
    selectedExpedienteId, setSelectedExpedienteId,
    selectedCompraId, setSelectedCompraId,
    selectedLogisticaId, setSelectedLogisticaId,
    selectedCajaChicaId, setSelectedCajaChicaId,
    selectedInventarioId, setSelectedInventarioId,
    selectedEntidadId, setSelectedEntidadId,
    selectedMedicoId, setSelectedMedicoId,
    selectedParticularId, setSelectedParticularId,
    selectedProveedorId, setSelectedProveedorId,
    selectedDoc, setSelectedDoc,
    newDoc, setNewDoc,
  });

  // Buscador global del navbar (P21, ui/global-search.jsx + dominio/busqueda.js):
  // interpreta el campo `nav` que arma MicosGlobalSearch y navega con el MISMO
  // router real que ya usa el resto de la app (los mismos setSelected*Id/setRoute
  // de arriba + openExpedienteFor) — nada de lógica de ruteo nueva, un solo switch
  // sobre los 3 modos posibles que puede traer un resultado de búsqueda.
  const navigateSearchResult = (nav) => {
    if (!nav) return;
    if (nav.modo === 'detalle') {
      const setters = {
        expedientes: setSelectedExpedienteId, compras: setSelectedCompraId, inventario: setSelectedInventarioId,
        entidades: setSelectedEntidadId, medicos: setSelectedMedicoId, particulares: setSelectedParticularId,
        proveedores: setSelectedProveedorId,
      };
      const set = setters[nav.seg];
      if (set) { set(nav.id); setRoute(`${nav.seg}-detail`); }
      return;
    }
    // Presupuesto/OC/remito no tienen pantalla propia: abren SU expediente (mismo
    // patrón que ya usan PresupuestosScreen/OrdenesCompraScreen/RemitosScreen onOpen).
    if (nav.modo === 'expediente') { openExpedienteFor(nav.expedienteId, nav.seg); return; }
    // Factura/recibo/nota: si tiene expediente lo abre ahí (mismo criterio que
    // FacturasScreen/RecibosScreen, P10); si no, visor standalone de Finanzas.
    if (nav.modo === 'doc-standalone') {
      if (nav.expedienteId) { openExpedienteFor(nav.expedienteId, 'finanzas'); return; }
      setSelectedDoc({ kind: nav.kind, id: nav.id });
      setRoute('finanzas-doc-detail');
    }
  };

  // Navegación cruzada del directorio (core/directorio-nav.jsx): saltos "Registros
  // asociados" entre Entidades/Médicos/Particulares/Proveedores y sus listas relacionadas.
  const { directorioNav } = useDirectorioNav({
    route, listPrefilter, setListPrefilter, setRoute,
    setSelectedEntidadId, setSelectedMedicoId, setSelectedParticularId, setSelectedProveedorId,
  });

  // Breadcrumbs (core/breadcrumbs.jsx): esquema «Sección · Pantalla», resuelve el 3er
  // nivel (publicId/nombre real) para las rutas de detalle.
  const breadcrumbs = buildBreadcrumbs(route, {
    selectedExpediente,
    selectedCompraId, selectedLogisticaId, selectedCajaChicaId,
    selectedInventarioId, selectedActivoId,
    selectedEntidadId, selectedMedicoId, selectedParticularId, selectedProveedorId,
  });

  const onNavSidebar = (key) => {
    if (key === 'expedientes') setRoute('expedientes-list');
    else setRoute(key);
  };

  // Ítem del Sidebar a resaltar (fix a — tabla pareja en core/router.jsx: antes cada
  // módulo tenía su propio caso especial a mano, con huecos — ver ROUTE_SIDEBAR_KEY).
  const sidebarActive = sidebarKeyForRoute(route);

  // Gate deny-by-default del router (M14 — BUGS-caza-01-07-2026.md #21): la ruta actual
  // pertenece a un módulo/sección de MICOS_SECCIONES que el usuario NO puede ver. Cubre
  // tanto la navegación interna (setRoute) como el deep-link (applyPath, core/router.jsx)
  // porque ambos caminos terminan escribiendo el MISMO estado `route` que este chequeo
  // lee — no hace falta duplicar el chequeo en cada lugar que llama setRoute/applyPath.
  // El admin (MicosPuedeVer siempre true) y las rutas fuera de la matriz (dashboard,
  // configuracion, etc. — no están en ROUTE_SECTION) nunca quedan denegadas.
  const routeSection = ROUTE_SECTION[route];
  // FIX P13 (ítem 11, Review P12 BAJO-1 — hardening OPCIONAL, aplicado por ser barato):
  // antes `!!window.MicosPuedeVer && !window.MicosPuedeVer(...)` — si dominio/sesion.js NO
  // cargaba (script roto/404/red), `MicosPuedeVer` es undefined, el `&&` corta corto y
  // `routeDenied` daba `false` SIEMPRE: fail-open teórico (cualquier ruta de la matriz
  // pintaba sin chequeo). Invertido: sin `MicosPuedeVer` cargado, DENEGAR (fail-closed) en
  // vez de permitir — sin cambio de comportamiento en el camino normal (sesion.js carga
  // síncrono, antes que este archivo, siempre el último `<script>`).
  const routeDenied = !!routeSection && (!window.MicosPuedeVer || !window.MicosPuedeVer(routeSection));

  if (!authReady) return null;
  if (!user) {
    window.location.replace(window.ROUTES && window.ROUTES.LOGIN || '/login');
    return null;
  }

  return (
    <div className="flex min-h-screen relative" style={{ background: 'var(--background)' }}>
      {/* Ambient lights backdrop */}
      <div className="ambient-lights">
        <div className="ambient-light al-1" />
        <div className="ambient-light al-2" />
        <div className="ambient-light al-3" />
      </div>

      <Sidebar activeKey={sidebarActive} onNav={onNavSidebar} user={user} onLogout={handleLogout} />

      <div className="flex-1 flex flex-col min-w-0 relative">
        <Header breadcrumbs={breadcrumbs} theme={theme} onToggleTheme={onToggleTheme} onNav={(k) => setRoute(k)} onSearchOpen={navigateSearchResult} user={user} />

        {hydWarnings.length > 0 && (
          <HydrationWarningBanner failed={hydWarnings} onRetry={retryHydration} />
        )}

        <main className="flex-1 px-4 sm:px-6 lg:px-8 pt-2 pb-12 relative" key={route}>
          {routeDenied ? <RouteDenied /> : <>
          {route === 'expedientes-list' && (
            <ExpedientesListScreen
              onOpen={(row) => { setSelectedExpedienteId(row.id); setRoute('expedientes-detail'); }}
              onNew={() => setRoute('expedientes-nuevo')}
              prefilter={listPrefilter && listPrefilter.module === 'expedientes' ? listPrefilter : null}
            />
          )}
          {route === 'expedientes-nuevo' && (
            <ExpedienteNuevoScreen
              onCancel={() => setRoute('expedientes-list')}
              onSaved={(id) => { setSelectedExpedienteId(id); setRoute('expedientes-detail'); }}
              onIdReconciled={(oldId, newId) => setSelectedExpedienteId((cur) => (cur === oldId ? newId : cur))}
            />
          )}
          {route === 'expedientes-detail' && (
            <ExpedienteDetailScreen
              expediente={selectedExpediente}
              onBack={() => setRoute('expedientes-list')}
              onOpenSubview={(s) => {
                if (typeof s === 'string' && s.indexOf('factura:') === 0) {
                  setSelectedFacturaIndex(Number(s.split(':')[1]) || 0);
                  setRoute('factura');
                } else {
                  setRoute(s);
                }
              }}
            />
          )}
          {/* Documentos del expediente (sub-rutas): envueltos en DocSubrouteGate
              (screens/docs/compartido.jsx) — al recargar (F5) parado en el documento,
              la compuerta pide /api/expedientes/detail y mergea los documentos ANTES
              de montar la pantalla, así useDocEdit decide isCreate sobre datos reales
              en vez de arrancar en un alta en blanco. En la navegación normal (detalle
              → documento) monta directo, sin loading (el detalle ya está mergeado). */}
          {route === 'presupuesto' && (
            <DocSubrouteGate expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')}>
              <PresupuestoScreen expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')} />
            </DocSubrouteGate>
          )}
          {route === 'orden-compra' && (
            <DocSubrouteGate expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')}>
              <OrdenCompraScreen expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')} />
            </DocSubrouteGate>
          )}
          {route === 'remito' && (
            <DocSubrouteGate expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')}>
              <RemitoScreen expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')} />
            </DocSubrouteGate>
          )}
          {route === 'factura' && (
            <DocSubrouteGate expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')}>
              <FacturaScreen expediente={selectedExpediente} facturaIndex={selectedFacturaIndex} onBack={() => setRoute('expedientes-detail')} />
            </DocSubrouteGate>
          )}
          {route === 'recibo' && (
            <DocSubrouteGate expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')}>
              <ReciboScreen expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')} />
            </DocSubrouteGate>
          )}
          {route === 'documentacion-medica' && (
            <DocSubrouteGate expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')}>
              <DocumentacionMedicaScreen expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')} />
            </DocSubrouteGate>
          )}
          {route === 'documentacion-extra' && (
            <DocSubrouteGate expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')}>
              <DocumentacionExtraScreen expediente={selectedExpediente} onBack={() => setRoute('expedientes-detail')} />
            </DocSubrouteGate>
          )}

          {/* Sections (list-style screens) */}
          {route === 'dashboard' && (
            <DashboardScreen onNav={(k) => setRoute(k)} />
          )}
          {route === 'configuracion' && (
            <ConfiguracionScreen />
          )}
          {route === 'proveedores' && (
            <ProveedoresScreen
              onOpen={(row) => { setSelectedProveedorId(row.id); setRoute('proveedores-detail'); }}
              onNew={() => { setSelectedProveedorId(null); setRoute('proveedores-nuevo'); }}
            />
          )}
          {route === 'proveedores-nuevo' && (
            <RecordFormScreen config={RECORD_FORMS.proveedor} recordId={null}
              onCancel={() => setRoute('proveedores')}
              onSaved={(id) => { setSelectedProveedorId(id); setRoute('proveedores-detail'); }}
              onIdReconciled={(oldId, newId) => setSelectedProveedorId((cur) => (cur === oldId ? newId : cur))} />
          )}
          {route === 'proveedores-editar' && (
            <RecordFormScreen config={RECORD_FORMS.proveedor}
              recordId={selectedProveedorId}
              onCancel={() => setRoute('proveedores-detail')}
              onSaved={(id) => { setSelectedProveedorId(id); setRoute('proveedores-detail'); }}
              onDeleted={() => { setSelectedProveedorId(null); setRoute('proveedores'); }} />
          )}
          {route === 'proveedores-detail' && (
            <ProveedorDetailScreen
              proveedorId={selectedProveedorId}
              onBack={() => setRoute('proveedores')}
              onEdit={() => setRoute('proveedores-editar')}
              navigator={directorioNav}
            />
          )}
          {route === 'presupuestos' && (
            <PresupuestosScreen onOpen={(d) => openExpedienteFor(d.expedienteId, 'presupuestos')} />
          )}
          {route === 'ordenes-compra' && (
            <OrdenesCompraScreen onOpen={(d) => openExpedienteFor(d.expedienteId, 'ordenes-compra')} />
          )}
          {route === 'remitos' && (
            <RemitosScreen onOpen={(d) => openExpedienteFor(d.expedienteId, 'remitos')} />
          )}
          {route === 'facturas' && (
            <FacturasScreen onOpen={(d) => {
              // FIX (P10, screens/documentos/facturas.jsx — ver su header): el
              // 100% de las facturas migradas no tiene expedienteId (standalone
              // por diseño) — antes esto SIEMPRE llamaba openExpedienteFor, que
              // con expedienteId null cae a `fallbackRoute` = 'facturas' (la
              // MISMA ruta en la que ya está): un no-op silencioso en cada fila.
              // Con expediente real, mismo camino de siempre; sin él, abre el
              // visor standalone fusionado de P8 (mismo destino que finanzas).
              if (d.expedienteId) { openExpedienteFor(d.expedienteId, 'facturas'); return; }
              setSelectedDoc({ kind: 'factura', id: d.id, origin: 'facturas' });
              setRoute('finanzas-doc-detail');
            }} />
          )}
          {route === 'recibos' && (
            <RecibosScreen onOpen={(d) => {
              // FIX ídem facturas (ver arriba) — el 100% de los recibos
              // migrados tampoco tiene expedienteId.
              if (d.expedienteId) { openExpedienteFor(d.expedienteId, 'recibos'); return; }
              setSelectedDoc({ kind: 'recibo', id: d.id, origin: 'recibos' });
              setRoute('finanzas-doc-detail');
            }} />
          )}
          {route === 'particulares' && (
            <ParticularesScreen
              onOpen={(row) => { setSelectedParticularId(row.id); setRoute('particulares-detail'); }}
              onNew={() => { setSelectedParticularId(null); setRoute('particulares-nuevo'); }}
              prefilter={listPrefilter && listPrefilter.module === 'particulares' ? listPrefilter : null}
            />
          )}
          {route === 'particulares-nuevo' && (
            <RecordFormScreen config={RECORD_FORMS.particular} recordId={null}
              onCancel={() => setRoute('particulares')}
              onSaved={(id) => { setSelectedParticularId(id); setRoute('particulares-detail'); }}
              onIdReconciled={(oldId, newId) => setSelectedParticularId((cur) => (cur === oldId ? newId : cur))} />
          )}
          {route === 'particulares-editar' && (
            <RecordFormScreen config={RECORD_FORMS.particular}
              recordId={selectedParticularId}
              onCancel={() => setRoute('particulares-detail')}
              onSaved={(id) => { setSelectedParticularId(id); setRoute('particulares-detail'); }}
              onDeleted={() => { setSelectedParticularId(null); setRoute('particulares'); }} />
          )}
          {route === 'particulares-detail' && (
            <ParticularDetailScreen
              particularId={selectedParticularId}
              onBack={() => setRoute('particulares')}
              onEdit={() => setRoute('particulares-editar')}
              navigator={directorioNav}
            />
          )}
          {route === 'medicos' && (
            <MedicosScreen
              onOpen={(row) => { setSelectedMedicoId(row.id); setRoute('medicos-detail'); }}
              onNew={() => { setSelectedMedicoId(null); setRoute('medicos-nuevo'); }}
            />
          )}
          {route === 'medicos-nuevo' && (
            <RecordFormScreen config={RECORD_FORMS.medico} recordId={null}
              onCancel={() => setRoute('medicos')}
              onSaved={(id) => { setSelectedMedicoId(id); setRoute('medicos-detail'); }}
              onIdReconciled={(oldId, newId) => setSelectedMedicoId((cur) => (cur === oldId ? newId : cur))} />
          )}
          {route === 'medicos-editar' && (
            <RecordFormScreen config={RECORD_FORMS.medico}
              recordId={selectedMedicoId}
              onCancel={() => setRoute('medicos-detail')}
              onSaved={(id) => { setSelectedMedicoId(id); setRoute('medicos-detail'); }}
              onDeleted={() => { setSelectedMedicoId(null); setRoute('medicos'); }} />
          )}
          {route === 'medicos-detail' && (
            <MedicoDetailScreen
              medicoId={selectedMedicoId}
              onBack={() => setRoute('medicos')}
              onEdit={() => setRoute('medicos-editar')}
              navigator={directorioNav}
            />
          )}
          {route === 'entidades' && (
            <EntidadesScreen
              onOpen={(row) => { setSelectedEntidadId(row.id); setRoute('entidades-detail'); }}
              onNew={() => { setSelectedEntidadId(null); setRoute('entidades-nuevo'); }}
            />
          )}
          {route === 'entidades-nuevo' && (
            <RecordFormScreen config={RECORD_FORMS.entidad} recordId={null}
              onCancel={() => setRoute('entidades')}
              onSaved={(id) => { setSelectedEntidadId(id); setRoute('entidades-detail'); }}
              onIdReconciled={(oldId, newId) => setSelectedEntidadId((cur) => (cur === oldId ? newId : cur))} />
          )}
          {route === 'entidades-editar' && (
            <RecordFormScreen config={RECORD_FORMS.entidad}
              recordId={selectedEntidadId}
              onCancel={() => setRoute('entidades-detail')}
              onSaved={(id) => { setSelectedEntidadId(id); setRoute('entidades-detail'); }}
              onDeleted={() => { setSelectedEntidadId(null); setRoute('entidades'); }} />
          )}
          {route === 'entidades-detail' && (
            <EntidadDetailScreen
              entidadId={selectedEntidadId}
              onBack={() => setRoute('entidades')}
              onEdit={() => setRoute('entidades-editar')}
              navigator={directorioNav}
            />
          )}
          {route === 'inventario' && (
            <InventarioScreen
              onOpen={(row) => { setSelectedInventarioId(row.id); setRoute('inventario-detail'); }}
              onNew={() => { setSelectedInventarioId(null); setRoute('inventario-nuevo'); }}
              onOpenActivo={(row) => { setSelectedActivoId(row.id); setRoute('inventario-activo-detail'); }}
              onNewActivo={() => { setSelectedActivoId(null); setRoute('inventario-activo-nuevo'); }}
              onVerHistorialAlquileres={() => setRoute('inventario-historial-alquileres')}
            />
          )}
          {route === 'inventario-activo-detail' && (
            <InventarioActivoDetailScreen
              activoId={selectedActivoId}
              onBack={() => setRoute('inventario')}
              onVerHistorialAlquileres={() => setRoute('inventario-historial-alquileres')}
            />
          )}
          {/* DECISIÓN 2 (06/07/2026): alta de equipo VAC — mismo componente en modo
              `isCreate`, ver activo-detalle.jsx (InventarioActivoAltaForm). */}
          {route === 'inventario-activo-nuevo' && (
            <InventarioActivoDetailScreen
              isCreate
              onBack={() => setRoute('inventario')}
              onSaved={(id) => { setSelectedActivoId(id); setRoute('inventario-activo-detail'); }}
              onIdReconciled={(oldId, newId) => setSelectedActivoId((cur) => (cur === oldId ? newId : cur))}
            />
          )}
          {route === 'inventario-historial-alquileres' && (
            <InventarioHistorialAlquileresScreen
              onBack={() => setRoute('inventario')}
              onOpenActivo={(id) => { setSelectedActivoId(id); setRoute('inventario-activo-detail'); }}
            />
          )}
          {route === 'inventario-nuevo' && (
            <InventarioDetailScreen
              isCreate
              onBack={() => setRoute('inventario')}
              onSaved={(id) => { setSelectedInventarioId(id); setRoute('inventario-detail'); }} />
          )}
          {route === 'inventario-editar' && (
            <InventarioDetailScreen
              inventarioId={selectedInventarioId}
              startEditing
              onBack={() => setRoute('inventario-detail')}
              onSaved={(id) => { setSelectedInventarioId(id); setRoute('inventario-detail'); }}
              onVerHistorial={() => setRoute('inventario-historial')}
              onDeleted={() => { setSelectedInventarioId(null); setRoute('inventario'); }} />
          )}
          {route === 'inventario-detail' && (
            <InventarioDetailScreen
              inventarioId={selectedInventarioId}
              onBack={() => setRoute('inventario')}
              onVerHistorial={() => setRoute('inventario-historial')}
              onDeleted={() => { setSelectedInventarioId(null); setRoute('inventario'); }}
            />
          )}
          {route === 'inventario-historial' && (
            <InventarioHistorialScreen
              onBack={() => setRoute('inventario')}
              onOpenItem={(id) => { setSelectedInventarioId(id); setRoute('inventario-detail'); }}
            />
          )}
          {route === 'compras' && (
            <ComprasScreen
              onOpen={(row) => { setSelectedCompraId(row.id); setRoute('compras-detail'); }}
              onNew={() => { setSelectedCompraId(null); setRoute('compras-nuevo'); }}
              prefilter={listPrefilter && listPrefilter.module === 'compras' ? listPrefilter : null}
            />
          )}
          {route === 'compras-nuevo' && (
            <ComprasDetailScreen
              isCreate
              onBack={() => setRoute('compras')}
              onSaved={(id) => { setSelectedCompraId(id); setRoute('compras-detail'); }} />
          )}
          {route === 'compras-editar' && (
            <ComprasDetailScreen
              compraId={selectedCompraId}
              startEditing
              onBack={() => setRoute('compras-detail')}
              onSaved={(id) => { setSelectedCompraId(id); setRoute('compras-detail'); }}
              onDeleted={() => { setSelectedCompraId(null); setRoute('compras'); }} />
          )}
          {route === 'compras-detail' && (
            <ComprasDetailScreen
              compraId={selectedCompraId}
              onBack={() => setRoute('compras')}
              onDeleted={() => { setSelectedCompraId(null); setRoute('compras'); }}
            />
          )}
          {route === 'logistica' && (
            <LogisticaScreen
              onOpen={(row) => { setSelectedLogisticaId(row.id); setRoute('logistica-detail'); }}
              onNew={() => { setSelectedLogisticaId(null); setRoute('logistica-nuevo'); }}
            />
          )}
          {route === 'logistica-nuevo' && (
            <LogisticaDetailScreen
              isCreate
              onBack={() => setRoute('logistica')}
              onSaved={(id) => { setSelectedLogisticaId(id); setRoute('logistica-detail'); }} />
          )}
          {route === 'logistica-editar' && (
            <LogisticaDetailScreen
              logisticaId={selectedLogisticaId}
              startEditing
              onBack={() => setRoute('logistica-detail')}
              onSaved={(id) => { setSelectedLogisticaId(id); setRoute('logistica-detail'); }}
              onOpenCompra={(id) => { setSelectedCompraId(id); setRoute('compras-detail'); }}
              onDeleted={() => { setSelectedLogisticaId(null); setRoute('logistica'); }} />
          )}
          {route === 'logistica-detail' && (
            <LogisticaDetailScreen
              logisticaId={selectedLogisticaId}
              onBack={() => setRoute('logistica')}
              onOpenCompra={(id) => { setSelectedCompraId(id); setRoute('compras-detail'); }}
              onDeleted={() => { setSelectedLogisticaId(null); setRoute('logistica'); }}
            />
          )}
          {route === 'caja-chica' && (
            <CajaChicaScreen
              onOpen={(row) => { setSelectedCajaChicaId(row.id); setRoute('caja-chica-detail'); }}
              onNew={() => { setSelectedCajaChicaId(null); setRoute('caja-chica-nuevo'); }}
            />
          )}
          {route === 'caja-chica-nuevo' && (
            <CajaChicaDetailScreen
              isCreate
              onBack={() => setRoute('caja-chica')}
              onSaved={(id) => { setSelectedCajaChicaId(id); setRoute('caja-chica-detail'); }}
            />
          )}
          {route === 'caja-chica-editar' && (
            <CajaChicaDetailScreen
              movimientoId={selectedCajaChicaId}
              startEditing
              onBack={() => setRoute('caja-chica-detail')}
              onSaved={(id) => { setSelectedCajaChicaId(id); setRoute('caja-chica-detail'); }}
              onDeleted={() => { setSelectedCajaChicaId(null); setRoute('caja-chica'); }}
            />
          )}
          {route === 'caja-chica-detail' && (
            <CajaChicaDetailScreen
              movimientoId={selectedCajaChicaId}
              onBack={() => setRoute('caja-chica')}
              onDeleted={() => { setSelectedCajaChicaId(null); setRoute('caja-chica'); }}
            />
          )}
          {route === 'aprobaciones' && (
            <AprobacionesScreen onOpen={(a) => {
              // Toda aprobación (presupuesto o remito) referencia un expediente.
              // Abrimos el expediente correspondiente (por su publicId).
              const exp = a && a.expedienteId
                ? EXPEDIENTES.find((e) => e.publicId === a.expedienteId)
                : null;
              setSelectedExpedienteId(exp ? exp.id : null);
              setRoute('expedientes-detail');
            }} />
          )}
          {route === 'ajustes-inventario' && (
            <AjustesEntregaCard onOpen={(a) => {
              const exp = a && a.expedienteId
                ? EXPEDIENTES.find((e) => e.publicId === a.expedienteId)
                : null;
              setSelectedExpedienteId(exp ? exp.id : null);
              setRoute('expedientes-detail');
            }} />
          )}
          {route === 'finanzas' && (
            <FinanzasScreen
              onOpenDoc={(kind, src) => { setSelectedDoc({ kind, id: src.id }); setRoute('finanzas-doc-detail'); }}
              onNew={(docType, ladoTab) => {
                const lado = ladoTab === 'pagos' ? 'pagos' : 'cobros';
                if (docType === 'nc' || docType === 'nd') {
                  setNewDoc({ clase: docType === 'nd' ? 'ND' : 'NC', lado });
                  setRoute('finanzas-nota-nueva');
                } else if (docType === 'facturas' && lado === 'cobros') {
                  setRoute('finanzas-factura-nueva');
                } else if (docType === 'facturas' && lado === 'pagos') {
                  setRoute('finanzas-facrec-nueva');
                } else if (docType === 'recibos') {
                  setRoute('finanzas-recibo-nueva');
                } else if (docType === 'pagos') {
                  setRoute('finanzas-pago-nueva');
                }
              }}
              prefilter={listPrefilter && listPrefilter.module === 'finanzas' ? listPrefilter : null}
            />
          )}
          {route === 'finanzas-nota-nueva' && newDoc && (
            <NotaScreen
              isCreate
              clase={newDoc.clase}
              lado={newDoc.lado}
              backLabel="Volver a Finanzas"
              onBack={() => setRoute('finanzas')}
              onSaved={(id) => { setSelectedDoc({ kind: 'nota', id }); setRoute('finanzas-doc-detail'); }}
            />
          )}
          {route === 'finanzas-factura-nueva' && (
            <FacturaScreen
              isCreate
              backLabel="Volver a Finanzas"
              onBack={() => setRoute('finanzas')}
              onSaved={(id) => { setSelectedDoc({ kind: 'factura', id }); setRoute('finanzas-doc-detail'); }}
            />
          )}
          {route === 'finanzas-recibo-nueva' && (
            <ReciboScreen
              isCreate
              backLabel="Volver a Finanzas"
              onBack={() => setRoute('finanzas')}
              onSaved={(id) => { setSelectedDoc({ kind: 'recibo', id }); setRoute('finanzas-doc-detail'); }}
            />
          )}
          {route === 'finanzas-pago-nueva' && (
            <PagoFinScreen
              isCreate
              backLabel="Volver a Finanzas"
              onBack={() => setRoute('finanzas')}
              onSaved={(id) => { setSelectedDoc({ kind: 'pago', id }); setRoute('finanzas-doc-detail'); }}
            />
          )}
          {route === 'finanzas-facrec-nueva' && (
            <FacturaRecibidaFinScreen
              isCreate
              backLabel="Volver a Finanzas"
              onBack={() => setRoute('finanzas')}
              onSaved={(id) => { setSelectedDoc({ kind: 'facturaRecibida', id }); setRoute('finanzas-doc-detail'); }}
            />
          )}
          {route === 'finanzas-doc-detail' && selectedDoc && (() => {
            // FIX (P10, screens/documentos/facturas.jsx|recibos.jsx): además de
            // abrirse desde Finanzas (Cobros/Pagos → onOpenDoc), esta ruta ahora
            // también se abre desde las listas standalone de Documentos
            // (Facturas/Recibos sin expediente). `selectedDoc.origin` distingue
            // el origen ('facturas'/'recibos' vs sin origin = Finanzas) para que
            // "Volver" regrese a la lista de la que salió el usuario, no siempre
            // a Finanzas.
            const origin = selectedDoc.origin;
            const backRoute = origin === 'facturas' ? 'facturas' : origin === 'recibos' ? 'recibos' : 'finanzas';
            const backLabel = origin === 'facturas' ? 'Volver a Facturas' : origin === 'recibos' ? 'Volver a Recibos' : 'Volver a Finanzas';
            const back = () => setRoute(backRoute);
            const k = selectedDoc.kind, id = selectedDoc.id;
            // P8: factura/recibo pasan a UN solo componente parametrizado por fuente
            // (screens/finanzas/factura.jsx y recibo.jsx — antes FacturaFinScreen/
            // FacturaScreen y ReciboFinScreen/ReciboScreen elegidos acá a mano). La
            // fuente la decide el propio componente: con `expediente` es fuente
            // expediente, sin él (solo `facturaId`/`reciboId`) es standalone.
            if (k === 'factura') {
              const f = (window.FACTURAS || []).find((x) => x.id === id);
              const exp = f ? EXPEDIENTES.find((e) => e.id === f.expedienteId) : null;
              if (f && !exp) {
                return <FacturaScreen facturaId={id} backLabel={backLabel} onBack={back} />;
              }
              const fresh = exp && window.MicosResolveExpedienteDocs ? window.MicosResolveExpedienteDocs(exp) : exp;
              const idx = fresh && fresh.facturas ? Math.max(0, fresh.facturas.findIndex((x) => x.id === id)) : 0;
              return exp ? <FacturaScreen expediente={exp} facturaIndex={idx} backLabel={backLabel} onBack={back}
                onDelete={() => setDocDelete({ coll: 'facturas', id, label: f ? `Factura ${f.tipoFactura}-${f.numero}` : 'Factura' })} /> : null;
            }
            if (k === 'recibo') {
              const r = (window.RECIBOS || []).find((x) => x.id === id);
              const exp = r ? EXPEDIENTES.find((e) => e.id === r.expedienteId) : null;
              if (r && !exp) {
                return <ReciboScreen reciboId={id} backLabel={backLabel} onBack={back} />;
              }
              return exp ? <ReciboScreen expediente={exp} backLabel={backLabel} onBack={back}
                onDelete={() => setDocDelete({ coll: 'recibos', id, label: (r && numeroReciboMostrar(r)) || 'Recibo' })} /> : null;
            }
            if (k === 'nota') {
              return <NotaScreen notaId={id} backLabel="Volver a Finanzas" onBack={back} />;
            }
            if (k === 'facturaRecibida') {
              const p = (window.PAGOS || []).find((x) => x.id === id);
              return p ? <FacturaRecibidaScreen pago={p} backLabel="Volver a Finanzas" onBack={back}
                onDelete={() => setDocDelete({ coll: 'pagos', id, label: (p.facturaRecibida && p.facturaRecibida.codigo) || p.publicId })} /> : null;
            }
            if (k === 'pago') {
              const p = (window.PAGOS || []).find((x) => x.id === id);
              return p ? <PagoComprobanteScreen pago={p} backLabel="Volver a Finanzas" onBack={back}
                onDelete={() => setDocDelete({ coll: 'pagos', id, label: p.publicId })} /> : null;
            }
            return null;
          })()}
          {/* P8: mueren finanzas-cobro-detail/finanzas-pago-detail (CobroDetailScreen/
              PagoDetailScreen) y sus 6 sub-rutas (finanzas-cobro-factura/-recibo/
              -docextra, finanzas-pago-factura/-comprobante/-docextra) — bloque
              inalcanzable desde la UI real (FinanzasScreen nunca invocaba onOpenCobro/
              onOpenPago, solo onOpenDoc; ver header de screens/finanzas/lista.jsx).
              PagoDocExtraScreen muere con ellas: su única ruta era esta, sin otro
              caller en todo el repo (grep confirmado). */}

          {![
            'expedientes-list', 'expedientes-detail', 'expedientes-nuevo',
            'presupuesto', 'orden-compra', 'remito', 'factura', 'recibo',
            'documentacion-medica', 'documentacion-extra',
            'dashboard', 'particulares', 'medicos', 'entidades', 'inventario',
            'compras', 'compras-detail', 'logistica', 'logistica-detail',
            'caja-chica', 'caja-chica-detail', 'caja-chica-nuevo', 'caja-chica-editar', 'aprobaciones', 'ajustes-inventario',
            'finanzas', 'finanzas-doc-detail', 'finanzas-nota-nueva', 'finanzas-factura-nueva', 'finanzas-recibo-nueva', 'finanzas-pago-nueva', 'finanzas-facrec-nueva',
            'inventario-detail', 'inventario-nuevo', 'inventario-editar', 'inventario-historial',
            'inventario-activo-detail', 'inventario-historial-alquileres', 'inventario-activo-nuevo',
            'compras-nuevo', 'compras-editar', 'logistica-nuevo', 'logistica-editar',
            'entidades-detail', 'medicos-detail', 'particulares-detail',
            'entidades-nuevo', 'entidades-editar', 'medicos-nuevo', 'medicos-editar',
            'particulares-nuevo', 'particulares-editar',
            'configuracion', 'proveedores', 'proveedores-detail',
            'proveedores-nuevo', 'proveedores-editar',
            'presupuestos', 'ordenes-compra', 'remitos', 'facturas', 'recibos',
          ].includes(route) && (
            <RouteFallback onRedirect={() => setRoute('dashboard')} />
          )}
          </>}
        </main>
      </div>
      <DocumentPrintViewer />
      {docDelete && (
        <ConfirmDeleteModal
          title="¿Eliminar este documento?"
          itemName={docDelete.label}
          description="Se va a borrar de Finanzas. Esta acción no se puede deshacer."
          confirmLabel="Sí, eliminar"
          onCancel={() => setDocDelete(null)}
          onConfirm={() => { if (window.MicosStore) window.MicosStore.remove(docDelete.coll, docDelete.id); setDocDelete(null); setRoute('finanzas'); }}
        />
      )}
      <StoreErrorToast />
    </div>
  );
}

// Cualquier ruta desconocida redirige a Panel general (default de la app).
function RouteFallback({ onRedirect }) {
  React.useEffect(() => { onRedirect(); }, []);
  return null;
}

// ─── Toast global de error de escritura ('micos:store-error') ──────
// store.js hace rollback del cambio optimista cuando un create/update/remove/
// setSingleton es rechazado por el backend (RLS/columnas/red) y dispara este
// evento — acá se muestra SIEMPRE, a nivel de toda la app, sin depender de que
// cada pantalla lo cablee a mano. Antes un guardado fallido no avisaba nada
// (BUGS-caza-01-07-2026.md #20/#22, Lote 1 · 01/07/2026).
function StoreErrorToast() {
  const [err, setErr] = React.useState(null);
  React.useEffect(() => {
    let hideTimer = null;
    const onErr = (e) => {
      setErr((e && e.detail) || null);
      clearTimeout(hideTimer);
      hideTimer = setTimeout(() => setErr(null), 5200);
    };
    window.addEventListener('micos:store-error', onErr);
    return () => { window.removeEventListener('micos:store-error', onErr); clearTimeout(hideTimer); };
  }, []);
  if (!err) return null;
  const ACTION_LABEL = { create: 'crear', update: 'guardar', remove: 'eliminar', setSingleton: 'guardar' };
  const label = ACTION_LABEL[err.action] || 'guardar';
  return (
    <div className="fixed bottom-6 right-6 z-[70] flex items-center gap-3 rounded-xl px-4 py-3 animate-fade-in-up"
      style={{ background: 'var(--card)', border: '1px solid color-mix(in oklab, #e5484d 45%, transparent)', boxShadow: '0 18px 50px -14px rgba(8, 20, 24, 0.5)', maxWidth: 'min(92vw, 420px)' }}>
      <span className="inline-flex items-center justify-center rounded-lg flex-shrink-0"
        style={{ width: 30, height: 30, background: 'color-mix(in oklab, #e5484d 16%, transparent)', color: '#e5484d' }}>
        <IconX size={16} />
      </span>
      <p className="text-[12.5px] font-bold leading-snug" style={{ color: 'var(--foreground)' }}>
        No se pudo {label} — el cambio NO se guardó (se revirtió). Reintentá.
      </p>
    </div>
  );
}

// ─── Banner de hidratación parcial (P5, D4) ──────────────────────────────
// Algunas colecciones no se pudieron hidratar (MicosStore.hydrate() devolvió `failed`
// no vacío) pero al menos una sí — la app monta igual con lo que llegó (mejor parcial
// que pantalla en blanco) y avisa arriba de TODO el contenido qué faltó, con botón para
// reintentar SOLO la hidratación (no recarga la página, no pierde la navegación actual).
const HYD_COLLECTION_LABELS = {
  expedientes: 'Expedientes', particulares: 'Particulares', medicos: 'Médicos',
  entidades: 'Entidades', inventario: 'Inventario', compras: 'Compras',
  logistica: 'Logística', cajaChica: 'Caja chica', aprobaciones: 'Aprobaciones (margen)',
  ajustesEntrega: 'Ajustes de entrega', cobros: 'Cobros', pagos: 'Pagos',
  usuarios: 'Usuarios', proveedores: 'Proveedores', presupuestos: 'Presupuestos',
  ordenesCompra: 'Órdenes de compra', remitos: 'Remitos', facturas: 'Facturas',
  notasCredito: 'Notas de crédito', notasDebito: 'Notas de débito', recibos: 'Recibos',
  documentos: 'Documentos', companyConfig: 'Configuración de la empresa',
};
function HydrationWarningBanner({ failed, onRetry }) {
  const [retrying, setRetrying] = React.useState(false);
  const handleRetry = () => {
    setRetrying(true);
    Promise.resolve(onRetry()).then(() => setRetrying(false), () => setRetrying(false));
  };
  const labels = (failed || []).map((k) => HYD_COLLECTION_LABELS[k] || k).join(', ');
  return (
    <div className="flex items-center gap-3 px-4 sm:px-6 lg:px-8 py-2.5"
      style={{ background: 'color-mix(in oklab, var(--warning) 12%, transparent)', borderBottom: '1px solid color-mix(in oklab, var(--warning) 35%, transparent)' }}>
      <span className="inline-flex items-center justify-center rounded-lg flex-shrink-0"
        style={{ width: 26, height: 26, background: 'color-mix(in oklab, var(--warning) 22%, transparent)', color: 'var(--warning)' }}>
        <IconX size={14} />
      </span>
      <p className="text-[12.5px] font-bold leading-snug flex-1" style={{ color: 'var(--foreground)' }}>
        No se pudo cargar: {labels}. El resto de los datos en esta pantalla son reales.
      </p>
      <button type="button" className="btn btn-outline" style={{ height: 32, fontSize: 12, padding: '0 12px', flexShrink: 0 }}
        onClick={handleRetry} disabled={retrying}>
        {retrying ? 'Reintentando…' : 'Reintentar'}
      </button>
    </div>
  );
}

// ─── Pantalla de error honesto de hidratación (P5, D4) ───────────────────
// Se monta en vez de <App/> cuando MicosStore.hydrate() RECHAZA (fallo TOTAL — todas
// las colecciones fallaron, ver el header de hydrate() en core/store.js: típicamente
// el container de Supabase caído). Mismo design system que el resto (tokens + .btn):
// card centrada, ícono, el mensaje para el usuario, el error técnico chico abajo (para
// debug) y un botón Reintentar que vuelve a llamar la hidratación desde cero.
function HydrationErrorScreen({ error, onRetry }) {
  const [retrying, setRetrying] = React.useState(false);
  return (
    <div className="flex min-h-screen items-center justify-center px-4" style={{ background: 'var(--background)' }}>
      <div className="w-full text-center" style={{
        maxWidth: 420, background: 'var(--card)', border: '1px solid var(--border)',
        borderRadius: 20, padding: '36px 32px', boxShadow: '0 18px 50px -14px rgba(8, 20, 24, 0.5)',
      }}>
        <span className="inline-flex items-center justify-center rounded-full mx-auto mb-5"
          style={{ width: 52, height: 52, background: 'color-mix(in oklab, var(--danger) 16%, transparent)', color: 'var(--danger)' }}>
          <IconX size={24} />
        </span>
        <h1 className="text-[17px] font-extrabold mb-2" style={{ color: 'var(--foreground)' }}>
          No pude conectar con la base de datos
        </h1>
        <p className="text-[13px] leading-relaxed mb-4" style={{ color: 'var(--muted-foreground)' }}>
          El servicio de datos local (Supabase) puede estar caído — probá{' '}
          <code className="font-mono" style={{ fontSize: 12 }}>docker ps | grep supabase</code>.
        </p>
        <p className="text-left font-mono mb-6" style={{
          fontSize: 11, lineHeight: 1.5, color: 'var(--muted-foreground)', background: 'var(--surface)',
          border: '1px solid var(--border)', borderRadius: 10, padding: '10px 12px', wordBreak: 'break-word',
        }}>
          {(error && error.message) || 'Error desconocido'}
        </p>
        <button type="button" className="btn btn-primary" style={{ width: '100%' }}
          disabled={retrying}
          onClick={() => { setRetrying(true); onRetry(); }}>
          {retrying ? 'Reintentando…' : 'Reintentar'}
        </button>
      </div>
    </div>
  );
}

// ─── Gate de hidratación (D4, "hidratación honesta") ─────────────────────
// Poblamos los datos reales (MicosStore.hydrate() -> fetch a /api) ANTES del primer
// render, así las pantallas (que leen window.X de forma síncrona) ven datos reales.
// P5: ya NO hay semilla que mostrar si la hidratación falla (data-*.js murieron), así
// que "montar igual" dejaría la app vacía sin avisar — eso es exactamente lo que D4
// prohíbe. hydrate() (core/store.js) distingue 3 casos:
//   · Éxito total (`{failed: []}`)   → monta <App/> sin banner.
//   · Éxito parcial (`{failed:[..]}`) → monta <App/> con el banner de arriba.
//   · Fallo total (rechaza)          → NO monta <App/>: pantalla de error honesta con
//     Reintentar (vuelve a llamar este mismo gate).
// Un solo root reusado entre reintentos (crear un createRoot nuevo por reintento tira
// el warning de React de "container ya usado").
let _micosRoot = null;
function _micosGetRoot() {
  if (!_micosRoot) _micosRoot = ReactDOM.createRoot(document.getElementById('root'));
  return _micosRoot;
}
function _bootMicosApp() {
  if (!(window.MicosStore && typeof window.MicosStore.hydrate === 'function')) {
    _micosGetRoot().render(<App hydrationWarnings={[]} />);
    return;
  }
  window.MicosStore.hydrate()
    .then(function (res) {
      _micosGetRoot().render(<App hydrationWarnings={(res && res.failed) || []} />);
    })
    .catch(function (e) {
      console.error('[micos] hydrate falló del todo:', e);
      _micosGetRoot().render(<HydrationErrorScreen error={e} onRetry={_bootMicosApp} />);
    });
}
_bootMicosApp();
