feat: modulo de proyectos frontend (Next 16 + Bootstrap 5)

This commit is contained in:
Nurfog committed 2026-10-13 17:32:55 -03:00
commit 2dc41ed4fa
43 files changed
+5894

No files matched your search

+133
View File
@@ -0,0 +1,133 @@
"use client";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useAuth } from "@/lib/auth";
/**
* Acceso al módulo. Valida contra sige_sam_v3.usuario en el backend
* (POST /api/proyectos/auth/login), igual que el resto de módulos SAM.
*
* Patrón visual de sam-portal / SistemaCalidad: sidenav azul con la marca
* a la izquierda y la tarjeta de acceso a la derecha.
*/
export default function LoginPage() {
const router = useRouter();
const { user, loading, login } = useAuth();
const [idUsuario, setIdUsuario] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false);
// Si ya hay sesión, no tiene sentido mostrar el login.
useEffect(() => {
if (!loading && user) router.replace("/inbox");
}, [loading, user, router]);
async function handleSubmit(event: React.FormEvent) {
event.preventDefault();
if (submitting) return;
setError(null);
setSubmitting(true);
try {
await login(idUsuario, password);
router.push("/inbox");
} catch (err) {
setError(err instanceof Error ? err.message : "Error al ingresar");
setSubmitting(false);
}
}
return (
<div className="login-page">
<div className="container-fluid">
<div className="row min-vh-100">
{/* Marca */}
<div className="col-md-5 sidenav d-flex flex-column justify-content-center">
<div className="login-main-text text-center">
<i className="bx bx-briefcase" style={{ fontSize: "5rem" }} />
<h1 className="fw-bold mt-3">Proyectos</h1>
<p className="mb-0" style={{ opacity: 0.8 }}>
Gestión de proyectos · SAM
</p>
<p style={{ opacity: 0.5, fontSize: "0.85rem" }}>
Organismo Técnico de Capacitación
</p>
</div>
</div>
{/* Formulario */}
<div className="col-md-7 d-flex align-items-center justify-content-center">
<div className="lp-card p-4 p-md-5" style={{ maxWidth: 420, width: "100%" }}>
<h2 className="h5 fw-bold mb-1">Acceso al sistema</h2>
<p className="small mb-4" style={{ color: "var(--inv-muted)" }}>
Use su RUT y clave del sistema central (SIGE).
</p>
<form onSubmit={handleSubmit}>
{error && (
<div className="alert alert-danger d-flex align-items-center gap-2 py-2" role="alert">
<i className="bx bx-error" />
<span className="small">{error}</span>
</div>
)}
<div className="mb-3">
<label htmlFor="idUsuario" className="form-label">
Usuario (RUT o legajo)
</label>
<input
type="text"
id="idUsuario"
className="form-control"
autoComplete="username"
value={idUsuario}
onChange={(event) => setIdUsuario(event.target.value)}
required
autoFocus
/>
</div>
<div className="mb-4">
<label htmlFor="password" className="form-label">
Clave
</label>
<input
type="password"
id="password"
className="form-control"
autoComplete="current-password"
value={password}
onChange={(event) => setPassword(event.target.value)}
required
/>
</div>
<button
type="submit"
className="btn btn-accent w-100 py-2 d-flex align-items-center justify-content-center gap-2"
disabled={submitting}
>
{submitting ? (
<>
<span className="spinner-border spinner-border-sm" role="status" />
Verificando…
</>
) : (
<>
<i className="bx bx-log-in" />
Ingresar
</>
)}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
}