);
}
// Guarda el token de acceso en el vault (memoria, nunca disco) y refleja al
// usuario en Redux. Vive aquí y se repite (idéntica) en vita-root.jsx porque
// es la única pieza que ambos archivos necesitan de la respuesta de auth, y
// duplicar 4 líneas es más simple y robusto que inventar un acoplamiento
// oculto entre dos scripts que Babel carga por separado.
function guardarSesionDesdeRespuesta(dispatch, resp) {
window.VitaCore.contenedor.vault.guardar({
accessToken: resp.accessToken,
expiraEn: Date.now() + resp.expiresIn * 1000,
});
const { sesion } = window.VitaCore.estado.acciones;
dispatch(sesion.sesionIniciada(sesion.usuarioSesionDesdeBackend(resp.usuario)));
}
// Cada rol tiene su propia app (páginas .html separadas, mismo patrón que ya
// existía para el panel médico). Si quien se loguea no es Paciente, esta
// pestaña no es la suya: se navega de verdad (recarga completa) a la app de
// su rol. El token de acceso vive en memoria de proceso — no sobrevive a un
// window.location.href de todas formas — así que la app destino simplemente
// vuelve a pedirlo con la cookie de refresh (ver el guardado que hace
// vita-root.jsx / lo que haga el equivalente en cada app al arrancar).
const RUTA_POR_ROL = { Medico: '/medico', Administrador: '/admin' };
function redirigirSiNoEsPaciente(rol) {
const destino = RUTA_POR_ROL[rol];
if (destino) { window.location.href = destino; return true; }
return false;
}
// Traduce los errores de RTK Query/el backend a algo que un paciente entienda.
function mapAuthError(e) {
const status = e && e.status;
const msg = e && e.data && e.data.message;
const primero = Array.isArray(msg) ? msg[0] : msg;
if (status === 401) return 'Correo o contraseña incorrectos.';
if (status === 409) return primero || 'Ya existe una cuenta con este correo.';
if (status === 'FETCH_ERROR' || status === 0) return 'No pudimos conectar con Vita. Revisa tu conexión.';
return primero || 'Algo salió mal. Intenta de nuevo.';
}
function AuthScreen({ t, persona, onAuthed, onToggleTheme, onFaceScan, onShowSecurity }) {
const { useDispatch, api } = window.VitaCore.estado;
const dispatch = useDispatch();
const [ingresar] = api.auth.useIngresarMutation();
const [registrar] = api.auth.useRegistrarMutation();
const [mode, setMode] = React.useState('login'); // login | signup
const [name, setName] = React.useState('');
const [email, setEmail] = React.useState('');
const [pw, setPw] = React.useState('');
const [showPw, setShowPw] = React.useState(false);
const [terms, setTerms] = React.useState(false);
const [touched, setTouched] = React.useState({});
const [submit, setSubmit] = React.useState(null); // null | 'form' | 'face'
const [authError, setAuthError] = React.useState(null);
const accent = persona.accent;
const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
const pwOk = pw.length >= 6;
const nameOk = name.trim().length >= 2;
const valid = mode === 'login' ? (emailOk && pwOk) : (nameOk && emailOk && pwOk && terms);
const err = (k) => touched[k] || touched._submit;
const emailErr = err('email') && email && !emailOk ? 'Correo no válido' : null;
const pwErr = err('pw') && pw && !pwOk ? 'Mínimo 6 caracteres' : null;
const nameErr = mode === 'signup' && err('name') && name && !nameOk ? 'Cuéntanos tu nombre' : null;
const go = async (kind) => {
if (kind === 'face') { onFaceScan && onFaceScan({ mode: 'login', method: 'face' }); return; }
setTouched(s => ({ ...s, _submit: true }));
setAuthError(null);
if (!valid) return;
if (mode === 'login') {
setSubmit('form');
try {
const resp = await ingresar({ email, password: pw }).unwrap();
guardarSesionDesdeRespuesta(dispatch, resp);
if (!redirigirSiNoEsPaciente(resp.usuario.rol)) onAuthed(resp.usuario);
} catch (e) {
setAuthError(mapAuthError(e));
} finally {
setSubmit(null);
}
return;
}
// Signup: la cuenta se crea de una vez contra el backend real. El
// escaneo facial que sigue es un paso cosmético de bienvenida — no una
// condición para que la cuenta exista.
const partes = name.trim().split(/\s+/);
const nombre = partes[0];
const apellido = partes.slice(1).join(' ') || partes[0];
setSubmit('form');
try {
const resp = await registrar({ email, password: pw, nombre, apellido }).unwrap();
guardarSesionDesdeRespuesta(dispatch, resp);
onFaceScan && onFaceScan({ enroll: true, usuario: resp.usuario });
} catch (e) {
setAuthError(mapAuthError(e));
} finally {
setSubmit(null);
}
};
const switchMode = (m) => { setMode(m); setTouched({}); setAuthError(null); };
// Layout partido: marca a la izquierda, formulario a la derecha.
// Por debajo de 900px el panel de marca desaparece (no aporta y roba
// espacio vertical al formulario, que es lo único accionable) y la
// identidad se resuelve con un logo compacto sobre los campos.
const anchoVentana = useAnchoVentana();
const partido = anchoVentana >= 900;
const p = t.paleta;
const VENTAJAS = [
['heart', 'Historia clínica viva', 'Vita la construye y la mantiene al día por ti.'],
['mic', 'Consulta hablando', 'Sin formularios: le cuentas y ella registra.'],
['shield', 'Tus datos, tuyos', 'Cifrado de extremo a extremo. Tú decides quién ve qué.'],
];
return (
{partido && (
VITA
Tu salud, cuidada todos los días.
La primera plataforma de prevención en salud por voz para América Latina.