/* ============================================================ FLOWSHIELD — App shell + routing ============================================================ */ function App() { const [authed, setAuthed] = useStateL(null); // null = comprobando sesión const [user, setUser] = useStateL(null); const [section, setSection] = useStateL(() => localStorage.getItem('fs_section') || 'dashboard'); const [cmd, setCmd] = useStateL(false); const [redInit, setRedInit] = useStateL(null); usePlan(); // re-render al cambiar de plan (gating por módulo). DEBE ir antes de cualquier return condicional. // Sesión basada en cookie httpOnly: preguntamos al backend quién somos. useEffectL(() => { let alive = true; window.fsAuth.me() .then(d => { if (!alive) return; setUser(d.email); if (d.plan) window.fsPlan.sync(d.plan); setAuthed(true); }) .catch(() => { if (alive) setAuthed(false); }); return () => { alive = false; }; }, []); useEffectL(() => { localStorage.setItem('fs_section', section); }, [section]); useEffectL(() => { const h = (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); setCmd(c=>!c); } if (e.key === 'Escape') setCmd(false); }; window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, []); const auth = (d) => { setUser(d && d.email ? d.email : 'operador'); if (d && d.plan) window.fsPlan.sync(d.plan); setAuthed(true); }; const logout = () => { window.fsAuth.logout(); setAuthed(false); setUser(null); }; const nav = (id) => { setSection(id); setCmd(false); if (id !== 'redteam') setRedInit(null); window.scrollTo(0,0); const m=document.getElementById('fs-main'); if(m) m.scrollTop=0; }; // One-click re-scan: map a recorded Red Team scan back to its tool + prefill. const relaunch = (scan) => { if (!scan || scan.module !== 'redteam') { nav(scan && scan.module === 'blueteam' ? 'blueteam' : scan && scan.module === 'compliance' ? 'normativa' : 'redteam'); return; } const eng = window.REDTEAM_ENGINES || {}; const want = '/api/redteam/' + scan.tool; const id = Object.keys(eng).find(k => eng[k] && eng[k].endpoint === want); if (!id) { nav('redteam'); return; } let tool = null; (window.REDTEAM_TABS || []).forEach(tab => (tab.sections || []).forEach(sec => (sec.tools || []).forEach(t => { if (t.id === id) tool = t; }))); tool = tool || { id, name: scan.tool, icon: 'scan', color: 'red' }; setRedInit({ ...tool, prefill: scan.params || {} }); setSection('redteam'); setCmd(false); window.scrollTo(0,0); const m = document.getElementById('fs-main'); if (m) m.scrollTop = 0; }; if (authed === null) { return
verificando sesión…
; } if (!authed) return ; const theme = (window.NAV.find(n=>n.id===section)||{}).theme || 'fs'; const gate = (id, el) => (window.fsModuleAllowed && !window.fsModuleAllowed(id)) ? : el; const screens = { dashboard: , redteam: , blueteam: gate('blueteam', ), normativa: gate('normativa', ), findings: , surface: , scans: , reports: , handlers: , integrations: , settings: , }; return (
setCmd(true)} />
{screens[section] || screens.dashboard}
setCmd(false)} onNav={nav} />
); } ReactDOM.createRoot(document.getElementById('root')).render();