SPA para la gestión de Casas de Paz de una iglesia cristiana. El proyecto cubre autenticación, casas, miembros, reuniones, asistencia y visitantes. La feature de autenticación está implementada con arquitectura hexagonal.
| Área | Herramienta |
|---|---|
| UI | React 19 |
| Lenguaje | TypeScript |
| Build y desarrollo | Vite |
| Routing | React Router 7 |
| Estado del servidor | TanStack Query |
| Formularios | React Hook Form |
| Validación | Zod |
| Cliente HTTP | Axios |
| Mocks HTTP | MSW |
| Testing | Vitest + Testing Library |
| Calidad | ESLint + Prettier |
| Paquetes | pnpm |
- Node.js 20 o superior.
- pnpm 9 o superior.
- Backend disponible en
http://localhost:3000para trabajar sin mocks.
pnpm install
cp .env.example .env.local
pnpm devLa aplicación queda disponible en http://localhost:5173.
# URL del backend sin /api/v1.
VITE_API_URL=http://localhost:3000
# true: intercepta las peticiones con MSW.
# false: utiliza el backend real.
VITE_ENABLE_MOCKS=trueVite solo expone al navegador variables que comienzan con VITE_. Reinicia el
servidor de desarrollo después de cambiar .env.local.
VITE_ENABLE_MOCKS=trueMSW simula login, refresh, usuario actual y logout. En este modo, la pantalla de login muestra el aviso de credenciales demo.
VITE_API_URL=http://localhost:3000
VITE_ENABLE_MOCKS=falseEl frontend agrega automáticamente el prefijo /api/v1. Para el usuario de
desarrollo provisto por el backend:
Correo: admin@peacehouses.test
Contraseña: password123
pnpm dev # Servidor de desarrollo
pnpm build # Typecheck y build de producción
pnpm preview # Previsualiza el build
pnpm typecheck # Verificación de TypeScript
pnpm lint # Verificación de ESLint
pnpm lint:fix # ESLint con correcciones automáticas
pnpm format # Aplica Prettier
pnpm format:check # Comprueba el formato sin modificar archivos
pnpm test # Ejecuta todos los tests una vez
pnpm test:watch # Tests en modo watch
pnpm test:coverage # Reporte de coberturaAntes de entregar cambios se recomienda ejecutar:
pnpm format:check
pnpm typecheck
pnpm lint
pnpm test
pnpm buildsrc/
├── app/ # Router, providers y composición global
├── components/ # Componentes y layouts reutilizables
├── features/ # Features con reglas y adaptadores propios
│ └── auth/ # Autenticación hexagonal
├── lib/ # Configuración compartida y utilidades
├── mocks/ # Handlers y worker de MSW
├── styles/ # Estilos globales
├── test/ # Setup global de Vitest
├── views/ # Vistas asociadas a rutas
└── main.tsx # Entrada de la aplicación
Los prototipos HTML originales de escritorio y móvil se conservan en
docs/prototypes/. Son referencias de diseño independientes del build de Vite.
Los consumidores externos deben importar autenticación desde su API pública:
import { AuthProvider, useAuth, useLogin } from '@/features/auth'La composición concreta es una excepción y solo debe importarse desde la raíz de providers:
import { authDependencies } from '@/features/auth/composition/authDependencies'src/features/auth/
├── domain/
│ ├── entities/ # User y Role
│ ├── errors/ # AuthError
│ ├── models/ # Credentials, tokens y sesión
│ └── ports/ # Contratos de repositorios
├── application/
│ ├── Login.ts
│ ├── RestoreSession.ts
│ └── Logout.ts
├── infrastructure/
│ ├── http/ # Axios, DTO, schemas y mappers
│ └── storage/ # Tokens en memoria/localStorage
├── presentation/
│ ├── context/ # Estado y provider de React
│ ├── errors/ # Mensajes seguros para la UI
│ ├── guards/ # Rutas públicas y protegidas
│ ├── hooks/ # useAuth y useLogin
│ └── validation/ # Validación del formulario
├── composition/ # Ensamblaje de puertos y adaptadores
└── index.ts # API pública de la feature
domain ← application
domain ← infrastructure
domain ← presentation
application + infrastructure + presentation ← composition
Reglas:
- El dominio no importa React, Axios, almacenamiento ni configuración.
- Los casos de uso dependen de puertos del dominio.
- Infraestructura implementa los puertos y traduce HTTP al dominio.
- Presentación administra estado de UI y ejecuta casos de uso mediante
AuthServices. - Composición es el único lugar que construye implementaciones concretas.
LoginView
→ useLogin
→ AuthProvider
→ Login
→ AxiosAuthRepository
→ POST /auth/login
→ validación Zod
→ mapper DTO a dominio
→ BrowserTokenRepository
El backend devuelve el access token con el nombre token:
{
"success": true,
"data": {
"token": "jwt",
"refreshToken": "opaque-token",
"user": {}
}
}El mapper lo convierte internamente a accessToken. Los modelos del dominio no
dependen de los nombres utilizados por el backend.
Access token → memoria
Refresh token → localStorage (hop.refreshToken)
El access token se pierde al recargar la página. Si existe refresh token,
RestoreSession renueva los tokens y consulta /auth/me.
El cliente autenticado:
- Agrega
Authorization: Bearer <accessToken>. - Ante un 401 solicita tokens nuevos.
- Comparte una sola llamada de refresh entre peticiones concurrentes.
- Reintenta cada petición original una sola vez.
- Limpia la sesión y notifica a React si el refresh falla.
No se intenta refresh automático para:
/auth/login
/auth/refresh
/auth/logout
/auth/me sí es un recurso protegido y puede activar refresh.
Logout es best-effort: intenta invalidar el refresh token en el backend y siempre elimina la sesión local, incluso si hay un error de red.
Las respuestas Axios entran como datos desconocidos. Infraestructura:
- Valida
{ success, data }o{ success, error }. - Valida los DTO con Zod.
- Convierte DTO a modelos del dominio mediante mappers.
- Convierte errores HTTP en
AuthError.
Las respuestas malformadas producen INVALID_RESPONSE. La UI traduce los
códigos conocidos a mensajes seguros y no muestra detalles técnicos.
Los tests están colocados junto al código que verifican e incluyen:
- casos de uso con puertos simulados;
- adaptadores Axios y almacenamiento;
- refresh concurrente y prevención de ciclos;
- DTO malformados;
- provider y mensajes de presentación;
- formulario de login;
- integración completa
UI → casos de uso → Axios → MSW → storage.
- El access token no se persiste.
- El refresh token se guarda actualmente en
localStorageporque ese es el contrato soportado por el backend. - No deben registrarse tokens o credenciales en consola.
- Para mayor protección frente a XSS, una evolución futura puede mover el refresh
token a una cookie
HttpOnly,SecureySameSite, coordinando el cambio con el backend.
- Usa
@/para importar desdesrc/. - Consume cada feature desde su
index.ts; evita imports profundos, excepto en la raíz de composición. - Mantén las reglas de negocio fuera de componentes React.
- Define contratos externos como DTO y conviértelos al dominio.
- Coloca los tests junto al archivo o flujo probado.
- Registra nuevas vistas en
src/app/router.tsx.