import { useEffect, useMemo, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { AlertCircle, ArrowLeft, ArrowRight, Lock, Mail, Wand2 } from "lucide-react"; import { motion } from "framer-motion"; import { useAuth } from "../lib/auth"; import { api } from "../lib/api"; import { Spinner } from "../components/ui"; import { InstallAppPrompt } from "../components/InstallAppPrompt"; import { revealStagger, revealUp } from "../lib/motion"; import { useReducedMotion } from "../lib/useReducedMotion"; import { BrandMark } from "../components/BrandMark"; const DEMO = import.meta.env.DEV || import.meta.env.VITE_DEMO_UI === "1"; type DemoUser = { email: string; name: string; role: string; avatar_color: string }; /** * Qué ve cada quien. Está escrito en el idioma del salón y no en el del sistema: * quien entra a probar no sabe qué es un rol ni un tenant, pero sí sabe * perfectamente quién hace qué en su negocio. * * Los rótulos no asumen género. No es un tecnicismo de estilo: la cuenta demo de * empleado incluye a Carolina y a Diego, así que «tus muchachas» dejaba fuera a una * de las dos tarjetas que la propia pantalla muestra debajo. */ const GRUPOS = [ { role: "owner", titulo: "Como quien manda", detalle: "Ves todo: la agenda, las cuentas del día, tu gente y tus clientas.", // Azul primario: es el camino principal, y comparte color con el CTA y la marca. color: "var(--ld-azul)", limite: 1, }, { role: "employee", titulo: "Como alguien de tu equipo", detalle: "Ve nomás su día y sus propias cuentas. Nada que configurar.", color: "var(--ld-naranja)", limite: 2, }, { role: "admin", titulo: "Como quien lleva varios salones", detalle: "Da de alta negocios y los ve todos juntos.", color: "var(--ld-purpura)", limite: 1, }, ] as const; export function LoginPage() { const { login, user } = useAuth(); const navigate = useNavigate(); const reduced = useReducedMotion(); const [email, setEmail] = useState(DEMO ? "owner@agendamax.demo" : ""); const [password, setPassword] = useState(DEMO ? "demo1234" : ""); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [pendiente, setPendiente] = useState(null); const [demoUsers, setDemoUsers] = useState([]); useEffect(() => { if (user) navigate("/", { replace: true }); }, [user, navigate]); useEffect(() => { if (!DEMO) return; api.auth .demoUsers() .then((r) => setDemoUsers(r.users)) .catch(() => {}); }, []); const grupos = useMemo( () => GRUPOS.map((g) => ({ ...g, cuentas: demoUsers.filter((u) => u.role === g.role).slice(0, g.limite), })).filter((g) => g.cuentas.length > 0), [demoUsers] ); const submit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setLoading(true); try { await login(email, password); navigate("/", { replace: true }); } catch (err: any) { setError(err.message || "No pudimos iniciar sesión"); } finally { setLoading(false); } }; // Acceso de un clic, solo demo. `DEMO` es una constante de build, así que esta rama // entera —y con ella la contraseña— se elimina del bundle de producción por // dead-code elimination. Por eso la contraseña vive aquí dentro y no arriba. const quickLogin = DEMO ? async (mail: string) => { setEmail(mail); setPassword("demo1234"); setError(null); setPendiente(mail); setLoading(true); try { await login(mail, "demo1234"); navigate("/", { replace: true }); } catch (err: any) { setError(err.message || "No pudimos iniciar sesión"); } finally { setLoading(false); setPendiente(null); } } : undefined; return (
{/* `ld-gutter` en lugar de `safe-x px-5`: ver el comentario de la clase en index.css — combinar las dos dejaba el padding lateral en 0. */}
Volver {/* ---- Columna izquierda: entrar de un clic ---- */}
AgendaMax

Entra sin llenar nada.

Escoge con qué ojos quieres verlo. Cada opción abre el mismo salón, pero desde el lugar de una persona distinta.

{DEMO && (
{grupos.map((g) => (
{g.cuentas.map((u) => ( ))}
))}
)}
{/* ---- Columna derecha: formulario manual ---- Visible sin interacción a propósito: visual-audit, responsive-audit y pwa-e2e rellenan estos campos justo después del goto. Colapsarlo tras un toggle rompería los tres. */}

Ya tengo mi cuenta

Entra con tu correo y tu contraseña.

setEmail(e.target.value)} placeholder="tucorreo@salon.com" autoComplete="email" required />
setPassword(e.target.value)} placeholder="••••••••" autoComplete="current-password" required />
{error && (
{error}
)}
{DEMO && (

Salón de práctica · todas las cuentas usan{" "} demo1234

)}
); }