// Estado global: sesión, carrito, pruebas, premium // Llama a la API real cuando está disponible; usa seed como fallback. window.useHashRoute = function useHashRoute() { const [hash, setHash] = React.useState(() => window.location.hash.slice(1) || '/'); React.useEffect(() => { const onHash = () => { setHash(window.location.hash.slice(1) || '/'); window.scrollTo({ top: 0, behavior: 'instant' }); }; window.addEventListener('hashchange', onHash); return () => window.removeEventListener('hashchange', onHash); }, []); const navigate = React.useCallback((to) => { window.location.hash = to; }, []); return [hash, navigate]; }; window.useAppStore = function useAppStore() { const BASE = window.API_BASE || '/simuladordocente/api'; // ── Sesión (decodificada del JWT o demo) ────────────────── const [session, setSession] = React.useState(() => { const tok = localStorage.getItem('sd_token'); if (!tok) return null; try { const p = JSON.parse(atob(tok.split('.')[1].replace(/-/g,'+').replace(/_/g,'/'))); if ((p.exp || 0) < Date.now() / 1000) { localStorage.removeItem('sd_token'); return null; } return p; } catch { return null; } }); // ── Pruebas (API → seed fallback) ──────────────────────── const [pruebas, setPruebas] = React.useState(window.SD_SEED.PRUEBAS); // ── Premium IDs ─────────────────────────────────────────── const [premiumIds, setPremiumIds] = React.useState(() => { const raw = localStorage.getItem('sd_premium_override'); return raw ? JSON.parse(raw) : []; }); // ── Carrito (local) ─────────────────────────────────────── const [cart, setCart] = React.useState(() => { const raw = localStorage.getItem('sd_cart'); return raw ? JSON.parse(raw) : []; }); // Fetch catálogo on mount React.useEffect(() => { fetch(`${BASE}/pruebas`) .then(r => r.ok ? r.json() : null) .then(d => { if (d?.pruebas?.length) setPruebas(d.pruebas); }) .catch(() => {}); }, []); // Fetch accesos cuando hay sesión real React.useEffect(() => { if (!session) { setPremiumIds([]); return; } const tok = localStorage.getItem('sd_token'); if (!tok) { setPremiumIds(session.premium || []); return; } fetch(`${BASE}/me/accesos`, { headers: { Authorization: `Bearer ${tok}` } }) .then(r => r.ok ? r.json() : null) .then(d => { if (d?.accesos) setPremiumIds(d.accesos); }) .catch(() => { setPremiumIds(session.premium || []); }); }, [session?.sub]); React.useEffect(() => { localStorage.setItem('sd_cart', JSON.stringify(cart)); }, [cart]); React.useEffect(() => { localStorage.setItem('sd_premium_override', JSON.stringify(premiumIds)); }, [premiumIds]); // ── Auth ────────────────────────────────────────────────── const login = async (email, password) => { const r = await fetch(`${BASE}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); const d = await r.json(); if (!r.ok) throw new Error(d.error || 'Error al iniciar sesión'); localStorage.setItem('sd_token', d.token); const payload = JSON.parse(atob(d.token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/'))); setSession(payload); return d; }; // Demo sin contraseña (botón Tweaks) const loginDemo = (email) => { localStorage.removeItem('sd_token'); const u = window.SD_SEED.USUARIO_DEMO; setSession({ ...u, email: email || u.email, sub: u.id }); setPremiumIds(u.premium || []); }; const logout = () => { localStorage.removeItem('sd_token'); setSession(null); setPremiumIds([]); }; // ── Carrito ─────────────────────────────────────────────── const addToCart = (id) => setCart(c => c.includes(id) ? c : [...c, id]); const removeFromCart = (id) => setCart(c => c.filter(x => x !== id)); const clearCart = () => setCart([]); // ── Premium ─────────────────────────────────────────────── const grantPremium = (ids) => setPremiumIds(p => Array.from(new Set([...p, ...ids]))); const hasPremium = (id) => premiumIds.includes(id); // Confirmar pago: llama API real si hay token, local si es demo const confirmarPago = async (pagoId) => { const tok = localStorage.getItem('sd_token'); if (tok && pagoId) { try { const r = await fetch(`${BASE}/checkout/confirmar`, { method: 'POST', headers: { Authorization: `Bearer ${tok}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ pago_id: pagoId }), }); const d = await r.json(); if (d?.accesos) { grantPremium(d.accesos); clearCart(); return; } } catch {} } // Fallback demo grantPremium(cart); clearCart(); }; return { session, login, loginDemo, logout, pruebas, cart, addToCart, removeFromCart, clearCart, hasPremium, premiumIds, grantPremium, confirmarPago, }; }; window.formatPrice = (n) => '$' + n.toLocaleString('es-MX') + ' MXN';