erp-core/docs/05-user-stories/mgn-001/US-MGN-001-001-001-login-con-email-password.md

10 KiB

US-MGN-001-001-001: Login con Email y Contraseña

RF Asociado: RF-MGN-001-001 Módulo: MGN-001 - Fundamentos Epic: Autenticación y Seguridad Prioridad: P0 (MVP) Story Points: 5 Sprint: Sprint 1 Estado: Ready for Development Fecha: 2025-11-24


User Story

Como usuario del sistema (administrador, contador, vendedor, etc.), Quiero autenticarme con mi email y contraseña, Para acceder de forma segura a las funcionalidades del ERP según mi rol y permisos.


Descripción Detallada

Esta user story cubre el flujo principal de autenticación del sistema ERP. El usuario ingresa sus credenciales (email + contraseña) y el sistema valida:

  1. Que el email exista en el tenant correcto (multi-tenancy)
  2. Que la contraseña sea correcta (bcrypt hash)
  3. Que el usuario esté activo
  4. Que el tenant esté activo

Si todo es válido, el sistema genera un JWT token con claims (user_id, tenant_id, roles, permissions) y lo retorna al frontend para ser almacenado y usado en requests subsiguientes.


Criterios de Aceptación

Escenario 1: Login exitoso con credenciales válidas

Dado que soy un usuario con cuenta activa en el tenant "acme-corp", Cuando ingreso mi email "juan@acme.com" y contraseña correcta, Entonces el sistema me autentica y retorna un JWT token válido que contiene mi user_id, tenant_id, roles y permissions.

Escenario 2: Login fallido por credenciales inválidas

Dado que ingreso un email existente pero contraseña incorrecta, Cuando intento hacer login, Entonces el sistema retorna error 401 "Credenciales inválidas" e incrementa el contador de intentos fallidos en mi usuario.

Escenario 3: Cuenta bloqueada por múltiples intentos fallidos

Dado que tengo 5 intentos fallidos previos en los últimos 30 minutos, Cuando intento hacer login nuevamente, Entonces el sistema retorna error 429 "Cuenta bloqueada temporalmente. Intente en X minutos" sin validar la contraseña.

Escenario 4: Usuario inactivo

Dado que mi cuenta tiene status='inactive', Cuando intento hacer login con credenciales correctas, Entonces el sistema retorna error 403 "Cuenta inactiva. Contacte al administrador".

Escenario 5: Tenant inactivo

Dado que el tenant tiene status='inactive', Cuando cualquier usuario de ese tenant intenta hacer login, Entonces el sistema retorna error 403 "Tenant suspendido. Contacte soporte".

Escenario 6: Email no existe en el tenant

Dado que ingreso un email que no existe en el tenant actual, Cuando intento hacer login, Entonces el sistema retorna error 401 "Credenciales inválidas" (sin revelar si el email existe o no).


Reglas de Negocio

  • RN-1: Email debe ser único por tenant (no globalmente). Usuario "juan@acme.com" puede existir en tenant A y B sin conflicto.
  • RN-2: Contraseña debe tener mínimo 8 caracteres, 1 mayúscula, 1 número, 1 carácter especial.
  • RN-3: JWT expira en 8 horas. Refresh token expira en 30 días.
  • RN-4: Máximo 5 intentos fallidos antes de bloqueo temporal de 30 minutos.
  • RN-5: Contraseña debe hashearse con bcrypt (cost factor 12) antes de almacenar.
  • RN-6: Después de login exitoso, failed_login_attempts se resetea a 0.

Tareas Técnicas (Checklist de Implementación)

Backend

  • Crear endpoint: POST /api/v1/auth/login
  • Implementar método: AuthService.login(email, password, subdomain)
  • Implementar método: AuthService.validateUser(email, password, tenantId)
  • Implementar método: AuthService.generateTokens(user)
  • Crear DTO: LoginDto con validaciones @IsEmail, @IsString, @MinLength
  • Crear DTO: TokenResponseDto (accessToken, refreshToken, expiresIn)
  • Implementar validaciones de negocio (RN-1 a RN-6)
  • Agregar unit tests para AuthService (8 test cases)
  • Agregar integration tests para POST /auth/login (6 test cases)
  • Documentar endpoint en Swagger con ejemplos y respuestas de error
  • Implementar rate limiting con Redis (max 10 req/min por IP)
  • Registrar intentos de login en auth.login_history

Frontend

  • Crear componente: LoginForm.tsx
  • Crear página: LoginPage.tsx
  • Implementar formulario con React Hook Form + Zod validation
  • Crear API client: authApi.login(email, password)
  • Implementar Zustand store: useAuthStore (setUser, setTokens, logout)
  • Agregar feedback visual: loading spinner, success redirect, error messages
  • Implementar "Recordar sesión" (localStorage vs sessionStorage)
  • Agregar component tests para LoginForm (5 test cases)
  • Agregar e2e test: "should login successfully with valid credentials" (Playwright)
  • Implementar manejo de errores por código HTTP (401, 403, 429)

Database

  • Verificar tabla: auth.users (email, password_hash, status, failed_login_attempts, last_failed_login)
  • Verificar tabla: auth.tenants (subdomain, status)
  • Verificar tabla: auth.login_history (user_id, tenant_id, ip_address, user_agent, success, timestamp)
  • Validar índice: idx_users_email_tenant para búsquedas rápidas
  • Validar índice: idx_login_history_timestamp para auditoría
  • Validar RLS policy: tenant_isolation_users

Mockups / Wireframes

Pantalla de Login:

  • Layout centrado con card de 400px de ancho
  • Logo del ERP en la parte superior
  • Formulario con 2 campos:
    • Email (input type="email", placeholder="usuario@empresa.com", autocomplete="email")
    • Contraseña (input type="password", con botón "mostrar/ocultar", autocomplete="current-password")
  • Checkbox "Recordar sesión"
  • Botón "Iniciar Sesión" (primary, full width, con loading state)
  • Link "¿Olvidaste tu contraseña?" (redirige a reset password)
  • Footer con versión del sistema y copyright

Estados:

  • Loading: Botón con spinner "Iniciando sesión...", inputs deshabilitados
  • Success: Redirect inmediato a /dashboard (sin mostrar mensaje)
  • Error: Alert rojo con mensaje de error arriba del formulario, con icono de error
  • Locked: Alert naranja "Cuenta bloqueada. Intente en X minutos" con countdown timer

Casos de Prueba (Test Scenarios)

Pruebas Funcionales

  1. TC-001: Login exitoso

    • Input: email="admin@acme.com", password="Admin123!"
    • Expected: JWT token retornado, redirect a /dashboard, failed_attempts=0
  2. TC-002: Email inválido

    • Input: email="notfound@acme.com", password="Admin123!"
    • Expected: Error 401 "Credenciales inválidas"
  3. TC-003: Contraseña incorrecta

    • Input: email="admin@acme.com", password="WrongPass"
    • Expected: Error 401, failed_attempts incrementado, registro en login_history
  4. TC-004: Cuenta bloqueada

    • Input: email con 5 failed_attempts en últimos 30 minutos
    • Expected: Error 429 "Cuenta bloqueada temporalmente"
  5. TC-005: Usuario inactivo

    • Input: email="inactive@acme.com", password="Admin123!"
    • Expected: Error 403 "Cuenta inactiva"
  6. TC-006: Tenant isolation

    • Input: email="user@tenant-a.com" desde subdomain tenant-b
    • Expected: Error 401 (no encuentra usuario en tenant-b)
  7. TC-007: Reset de failed_attempts después de login exitoso

    • Input: usuario con 3 failed_attempts previos hace login exitoso
    • Expected: failed_attempts resetea a 0

Pruebas No Funcionales

  1. Performance: Response time < 300ms (incluyendo bcrypt hash comparison)
  2. Seguridad:
    • JWT firmado con secret seguro de 256+ bits
    • bcrypt con cost factor 12
    • Rate limiting: max 10 login attempts por IP/minuto
    • No revelar en errores si email existe o no
  3. Usabilidad:
    • Foco automático en campo email al cargar
    • Enter en cualquier campo ejecuta submit
    • Tab navigation funciona correctamente

Dependencias

  • User Stories bloqueantes: Ninguna (es el punto de entrada del sistema)
  • Módulos requeridos: Ninguno (es parte de MGN-001 Fundamentos)
  • Datos maestros necesarios:
    • Tenant creado con subdomain configurado
    • Al menos 1 usuario con rol administrador creado (seed data)

Notas de Implementación

  • JWT secret debe estar en variable de entorno JWT_SECRET (nunca en código)
  • Subdomain se obtiene del header Host o de query param ?tenant=xxx (para desarrollo local)
  • Implementar rate limiting con Redis: max 10 login attempts por IP/minuto
  • Logging detallado de intentos de login (exitosos y fallidos) para auditoría y detección de ataques
  • NUNCA retornar en el error si el email existe o no (previene user enumeration attacks)
  • Considerar agregar CAPTCHA después de 3 intentos fallidos (Fase 2)
  • Considerar 2FA/MFA opcional (Fase 2)

Estimación Detallada

Tarea Estimación
Backend Development 3 horas
Frontend Development 3 horas
Testing (Unit + Integration + E2E) 2 horas
Code Review 1 hora
TOTAL 9 horas

Equivalente: 5 Story Points


Definition of Done (DoD)

  • Código backend implementado según ET-BACKEND-MGN-001-001
  • Código frontend implementado según ET-FRONTEND-MGN-001-001
  • Unit tests escritos y pasando (cobertura > 80%)
  • Integration tests escritos y pasando
  • E2E test "should login successfully" pasando
  • Code review completado y aprobado por Tech Lead
  • Documentación Swagger actualizada con ejemplos
  • Sin vulnerabilidades de seguridad (SonarQube green)
  • Rate limiting configurado y testeado
  • Merge a branch develop completado
  • QA manual completado
  • Product Owner ha validado flujo de login

Referencias