feat(auth): login JWT en API y las 4 apps

- Modelo Usuario + seed desde lista de vendedores (18 usuarios, bcrypt cost 12)
- POST /api/auth/login + /api/auth/me; middleware Bearer en todo /api
  excepto health, login y webhook Monday; flag AUTH_ENABLED
- Username = nombre completo normalizado (case/acentos insensible),
  password inicial Frasa2026, admin vía ADMIN_PASSWORD
- Apps: LoginScreen + AuthGate (StudioBlank), token en localStorage,
  header Authorization en todas las llamadas, logout en 401
- Tests: api 136, cotizador 71, alta 65, cobranza 71, agente-ia 13, landing 4
This commit is contained in:
2026-08-27 08:20:25 +00:00
parent b39fe6334f
commit de11af985e
40 changed files with 2085 additions and 38 deletions

View File

@@ -2,6 +2,8 @@
const API_URL = import.meta.env.VITE_API_URL ?? 'http://192.168.10.123:3000/api'
// TODO produccion: '/api' same-origin detras de NGINX
import { conAuth, manejar401 } from './auth'
/**
* Error HTTP del API (respuesta con status >= 400, p. ej. validación o 404).
* NO amerita fallback offline: el servidor respondió y su respuesta manda.
@@ -26,10 +28,14 @@ export function esErrorDeRed(err: unknown): boolean {
/** Llama al API Gateway; lanza ErrorApi con el mensaje del servidor si el status no es 2xx */
export async function request<T>(ruta: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${API_URL}${ruta}`, {
headers: { 'Content-Type': 'application/json' },
...init,
})
const res = await fetch(
`${API_URL}${ruta}`,
conAuth({
headers: { 'Content-Type': 'application/json' },
...init,
}),
)
manejar401(res)
if (!res.ok) {
let mensaje = `Error ${res.status} al llamar ${ruta}`
try {