/* ============================================================ FLOWSHIELD — Shell: Sidebar + Topbar ============================================================ */ function Sidebar({ active, onNav, theme }) { usePlan(); // re-render del nav al cambiar de plan const op = window.NAV.filter(n => n.group === 'op'); const cfg = window.NAV.filter(n => n.group === 'cfg'); const [act, setAct] = useStateL(null); // {running, today, total} useEffectL(() => { let alive = true; const load = () => window.fsApi('/api/results/activity', { method:'GET' }) .then(d => { if (alive) setAct(d); }).catch(() => {}); load(); const t = setInterval(load, 20000); return () => { alive = false; clearInterval(t); }; }, [active]); const Item = ({ n }) => { const isActive = active === n.id; const locked = window.fsModuleAllowed && !window.fsModuleAllowed(n.id); return ( ); }; return ( ); } /* Selector de proyecto activo (alcance transversal por cliente). Vive en la cabecera para que siempre sepas en qué engagement estás y qué reporting saldrá. */ function ProjectPicker({ activeProject, onProject }) { const [projs, setProjs] = useStateL(null); const [open, setOpen] = useStateL(false); const ref = useRefL(null); useEffectL(() => { let alive = true; window.fsApi('/api/projects', { method:'GET' }) .then(d => { if (!alive) return; const list = (d && d.projects) || []; setProjs(list); if (activeProject && !list.some(p => p.id === activeProject)) onProject(''); // proyecto borrado → Todos }) .catch(() => { if (alive) setProjs([]); }); return () => { alive = false; }; }, []); useEffectL(() => { const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h); }, []); const list = projs || []; const cur = list.find(p => p.id === activeProject) || null; const pick = (id) => { onProject(id); setOpen(false); }; return (