/* ============================================
App — Router between public site and admin
============================================ */
/* Global helpers for toasts and navigation */
window.toast = (msg, kind = "success") => {
window.dispatchEvent(new CustomEvent("app-toast", { detail: { msg, kind } }));
};
window.navTo = (page) => {
window.dispatchEvent(new CustomEvent("app-nav", { detail: page }));
};
function ToastStack() {
const [items, setItems] = useState([]);
useEffect(() => {
const handler = (e) => {
const id = Date.now() + Math.random();
setItems(arr => [...arr, { id, ...e.detail }]);
setTimeout(() => setItems(arr => arr.filter(i => i.id !== id)), 3400);
};
window.addEventListener("app-toast", handler);
return () => window.removeEventListener("app-toast", handler);
}, []);
const icon = (k) => k === "warn" ? "AlertTriangle" : k === "info" ? "Info" : "CheckCircle2";
return (
{items.map(it => (
{it.msg}
))}
);
}
function PublicSite({ onAdminClick }) {
const [active, setActive] = useState("inicio");
const onNav = (id) => {
setActive(id);
const el = document.getElementById(id);
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
};
useEffect(() => {
const sections = ["inicio", "nosotros", "servicios", "galeria", "contacto"];
const obs = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting && e.intersectionRatio > 0.3) setActive(e.target.id);
});
}, { threshold: [0.3, 0.6] });
sections.forEach(id => {
const el = document.getElementById(id);
if (el) obs.observe(el);
});
return () => obs.disconnect();
}, []);
return (
<>
>
);
}
function AdminApp({ user, onExit, onLogout }) {
const [page, setPage] = useState("dashboard");
const [drawer, setDrawer] = useState(false);
const [notifs, setNotifs] = useState([]);
useEffect(() => { window.scrollTo(0, 0); }, [page]);
useEffect(() => {
const handler = (e) => setPage(e.detail);
window.addEventListener("app-nav", handler);
return () => window.removeEventListener("app-nav", handler);
}, []);
useEffect(() => {
const fetchNotifs = async () => {
const data = await window.API.getNotificaciones();
setNotifs(data);
};
fetchNotifs();
const id = setInterval(fetchNotifs, 5 * 60 * 1000);
return () => clearInterval(id);
}, []);
const Module = {
dashboard: Dashboard,
produccion: Produccion,
stock: Stock,
ventas: VentasModulo,
asociacion: Asociacion,
terceros: ServiciosTerceros,
gastos: Gastos,
cheques: Cheques,
rent: Rentabilidad,
empleados: Empleados,
logistica: Logistica,
maquinaria: Maquinaria,
}[page] || Dashboard;
return (
setDrawer(false)} />
setDrawer(true)} notificaciones={notifs} onNav={setPage} />
);
}
function App() {
const isPanelSubdomain = window.location.hostname.startsWith("panel.");
const params = new URLSearchParams(window.location.search);
const resetToken = params.get("token");
const resetEmail = params.get("email");
// Si es subdominio panel.* o viene con token de reset → ir directo al login
const [mode, setMode] = useState(() => {
if (window.API.getToken()) return "admin";
if (resetToken || isPanelSubdomain) return "login";
return "public";
});
const [user, setUser] = useState(() => window.API.getUser());
useEffect(() => {
if (mode !== "public") window.scrollTo(0, 0);
}, [mode]);
useEffect(() => {
const handler = () => {
setMode(isPanelSubdomain ? "login" : "public");
setUser(null);
};
window.addEventListener("msa-unauthorized", handler);
return () => window.removeEventListener("msa-unauthorized", handler);
}, [isPanelSubdomain]);
const handleLogin = (u) => { setUser(u); setMode("admin"); };
const handleLogout = async () => {
await window.API.logout();
setUser(null);
setMode(isPanelSubdomain ? "login" : "public");
};
if (mode === "login") return (
setMode("public")}
resetToken={resetToken}
resetEmail={resetEmail}
/>
);
if (mode === "admin") return (
setMode(isPanelSubdomain ? "login" : "public")} onLogout={handleLogout} />
);
return {
if (window.API.getToken()) setMode("admin");
else setMode("login");
}} />;
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render();