/* App shell — sidebar nav, top bar, mobile bottom nav */ const NAV = [ { id: "today", label: "Hoy", icon: "home" }, { id: "metrics", label: "Métricas", icon: "metrics" }, { id: "activities", label: "Actividades", icon: "activity" }, { id: "agents", label: "Coach", icon: "coach" }, { id: "plans", label: "Planes", icon: "plan" }, { id: "knowledge", label: "Biblioteca", icon: "book" }, { id: "settings", label: "Configuración", icon: "settings" }, ]; const Sidebar = ({ route, setRoute, density, onboardingActive, user, inDemo }) => { const name = inDemo ? "Demo" : (user?.name || (user?.email ? user.email.split("@")[0] : "usuario")); const initial = (name?.[0] || "·").toUpperCase(); const sub = inDemo ? "modo demo" : (user?.email || "self-hosted"); // Suscripción al singleton de uploads para mostrar un badge "N" sobre el ítem // "Biblioteca" cuando hay archivos procesando o pendientes (incluso si el // user está en otra ruta). Re-render por subscribe/notify. const [, setTick] = React.useState(0); React.useEffect(() => { if (typeof window === "undefined" || typeof window.__af_uploadQueue__?.subscribe !== "function") return undefined; return window.__af_uploadQueue__.subscribe(() => setTick((t) => t + 1)); }, []); const uploadActive = (typeof window !== "undefined" && window.__af_uploadQueue__) ? window.__af_uploadQueue__.activeCount() : 0; return ( ); }; const MobileNav = ({ route, setRoute }) => { const [open, setOpen] = React.useState(false); const goto = (id) => { setRoute(id); setOpen(false); }; const current = NAV.find(n => n.id === route); return ( <> {/* Top mobile bar with hamburger + current screen */}
{current && } {current?.label}
{/* Slide-in drawer (overlay + panel) */}
setOpen(false)} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)", opacity: open ? 1 : 0, transition: "opacity 0.2s ease", }} />
); }; const TopBar = ({ title, subtitle, breadcrumb, actions, density, setDensity }) => { return (
{breadcrumb && (
{breadcrumb}
)}

{title}

{subtitle && (
{subtitle}
)}
{/* Density toggle oculto — el dato `compact` queda como default fijo * en app.jsx. Si más adelante hace falta un toggle, vive en el panel * de Preferencias (Cmd/Ctrl+.) sin ensuciar la topbar. */} {actions}
); }; const EmptyState = ({ icon = "info", title, body, action }) => (
{title}
{body &&
{body}
} {action}
); const Skel = ({ w = "100%", h = 14, r = 4 }) => (
); Object.assign(window, { Sidebar, MobileNav, TopBar, EmptyState, Skel, NAV });