// AUTH — login, registro, recuperar, confirmar email window.AuthPages = {}; // Guarda resultado anónimo pendiente (si hay) al API después del login async function savePendingResult() { const raw = localStorage.getItem('sd_pending_result'); if (!raw) return; try { const pending = JSON.parse(raw); const tok = localStorage.getItem('sd_token'); if (!tok) return; const BASE = window.API_BASE || '/simuladordocente/api'; await fetch(`${BASE}/resultados/anonimo`, { method: 'POST', headers: { Authorization: `Bearer ${tok}`, 'Content-Type': 'application/json' }, body: JSON.stringify(pending), }); localStorage.removeItem('sd_pending_result'); } catch {} } window.AuthPages.Login = function Login({ navigate, store }) { const [email, setEmail] = React.useState(''); const [pass, setPass] = React.useState(''); const [err, setErr] = React.useState(''); const [loading, setLoading] = React.useState(false); const submit = async (e) => { e.preventDefault(); setErr(''); if (!email.includes('@') || pass.length < 4) { setErr('Verifica tu correo y contraseña.'); return; } setLoading(true); try { await store.login(email, pass); // Guardar resultado anónimo pendiente si existe await savePendingResult(); navigate('/dashboard'); } catch (ex) { setErr(ex.message || 'Credenciales incorrectas'); } finally { setLoading(false); } }; return (

Inicia sesión

Accede a tu panel y simuladores.

setEmail(e.target.value)} />
setPass(e.target.value)} />
{err &&
{err}
}
{e.preventDefault();navigate('/recuperar');}} className="muted">¿Olvidaste tu contraseña? {e.preventDefault();navigate('/registro');}}>Crear cuenta
); }; window.AuthPages.Registro = function Registro({ navigate }) { const BASE = window.API_BASE || '/simuladordocente/api'; const [step, setStep] = React.useState(1); const [data, setData] = React.useState({ nombre: '', email: '', pass: '' }); const [err, setErr] = React.useState(''); const [loading, setLoading] = React.useState(false); const submit = async (e) => { e.preventDefault(); setErr(''); if (data.pass.length < 8) { setErr('La contraseña debe tener al menos 8 caracteres.'); return; } setLoading(true); try { const r = await fetch(`${BASE}/auth/registro`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nombre: data.nombre, email: data.email, password: data.pass }), }); const d = await r.json(); if (!r.ok) { setErr(d.error || 'Error al registrarse'); setLoading(false); return; } setStep(2); } catch { setErr('No se pudo conectar al servidor. Intenta de nuevo.'); } setLoading(false); }; if (step === 2) { return (
📬

Revisa tu correo

Enviamos un enlace de confirmación a {data.email}.

Haz clic en el enlace del correo para activar tu cuenta. Revisa también la carpeta de spam.

¿No llegó el correo?
{localStorage.getItem('sd_pending_result') && (
✓ Tu resultado del diagnóstico se guardará automáticamente al iniciar sesión en este mismo navegador.
)}
); } return (

Crea tu cuenta

Empieza con muestras gratis. Pago solo cuando estés listo.

setData({...data, nombre: e.target.value})} />
setData({...data, email: e.target.value})} />
setData({...data, pass: e.target.value})} />
Usa letras, números y al menos un símbolo (Ej. Mi.Clave2024!)
{err &&
{err}
}
¿Ya tienes cuenta? {e.preventDefault();navigate('/login');}} style={{color:'var(--ink)', fontWeight: 600}}>Inicia sesión
); }; window.AuthPages.Recuperar = function Recuperar({ navigate }) { const BASE = window.API_BASE || '/simuladordocente/api'; const [sent, setSent] = React.useState(false); const [email, setEmail] = React.useState(''); const [loading, setLoading] = React.useState(false); const submit = async (e) => { e.preventDefault(); setLoading(true); try { await fetch(`${BASE}/auth/recuperar`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }), }); } catch {} setLoading(false); setSent(true); }; if (sent) { return (
📨

Enlace enviado

Si {email} existe en nuestro sistema, recibirás un correo con el enlace para restablecer tu contraseña.

Revisa también tu carpeta de spam.

); } return (

Recupera tu contraseña

Te enviaremos un enlace para crear una nueva contraseña.

setEmail(e.target.value)} />
{e.preventDefault();navigate('/login');}} style={{color:'var(--ink)', fontWeight: 600}}>← Volver
); }; // Página: restablecer contraseña (llegó desde el link del email) window.AuthPages.Reset = function Reset({ navigate, token }) { const BASE = window.API_BASE || '/simuladordocente/api'; const [pass, setPass] = React.useState(''); const [pass2, setPass2] = React.useState(''); const [msg, setMsg] = React.useState(''); const [ok, setOk] = React.useState(false); const [loading, setLoading] = React.useState(false); const submit = async (e) => { e.preventDefault(); if (pass !== pass2) { setMsg('Las contraseñas no coinciden.'); return; } if (pass.length < 4) { setMsg('Mínimo 4 caracteres.'); return; } setLoading(true); setMsg(''); try { const r = await fetch(`${BASE}/auth/reset`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, password: pass }), }); const d = await r.json(); if (!r.ok) { setMsg(d.error || 'Error al restablecer.'); } else { setOk(true); } } catch { setMsg('Error de conexión.'); } setLoading(false); }; if (ok) { return (

Contraseña actualizada

Ya puedes iniciar sesión con tu nueva contraseña.

); } return (

Nueva contraseña

Elige una contraseña segura para tu cuenta.

setPass(e.target.value)} />
setPass2(e.target.value)} />
{msg &&
{msg}
}
); }; // Página: cuenta confirmada (redirige aquí el backend) window.AuthPages.Confirmado = function Confirmado({ navigate, error }) { if (error) { return (

Enlace inválido

El enlace de confirmación es inválido o ya expiró. Intenta registrarte de nuevo.

); } return (

¡Cuenta confirmada!

Tu correo ha sido verificado. Ya puedes acceder a tu panel.

); }; function AuthShell({ children, navigate }) { return (
{e.preventDefault();navigate('/');}}>

Practica como en el examen real.

Simuladores construidos con la estructura, dificultad y bloques temáticos del examen USICAMM. Pago único por materia, acceso permanente.

13 simuladores
$150 por materia
intentos
{children}
); }