# RF-AUTH-002: Estados de Cuenta de Usuario ## 📋 Metadata | Campo | Valor | |-------|-------| | **ID** | RF-AUTH-002 | | **Módulo** | Autenticación y Autorización | | **Prioridad** | Alta | | **Estado** | ✅ Implementado | | **Versión** | 1.0 | | **Fecha creación** | 2025-11-07 | | **Última actualización** | 2025-11-07 | ## 🔗 Referencias ### Especificación Técnica 📐 [ET-AUTH-002: Gestión de Estados de Cuenta](../../02-especificaciones-tecnicas/01-autenticacion-autorizacion/ET-AUTH-002-estados-cuenta.md) ### Implementación DDL 🗄️ **ENUM Canónico:** - **Ubicación:** `apps/database/ddl/00-prerequisites.sql:34-36` - **Tipo:** `auth_management.user_status` - **Valores:** `active`, `inactive`, `suspended`, `banned`, `pending` 🗄️ **Tablas que usan el ENUM:** 1. `auth_management.profiles` → `apps/database/ddl/schemas/auth_management/tables/03-profiles.sql:17` - Columna: `status auth_management.user_status NOT NULL DEFAULT 'pending'` 🗄️ **Funciones:** - `auth_management.verify_user_status()` → Valida que usuario puede acceder - `auth_management.suspend_user()` → Admin suspende usuario - `auth_management.ban_user()` → Admin banea usuario permanentemente - `auth_management.reactivate_user()` → Reactiva usuario inactive/suspended 🗄️ **Triggers:** - `trg_profiles_status_change` → Audita cambios de estado en `audit_logs` ### Backend 💻 **Implementación:** - **Enum:** `apps/backend/src/modules/auth/enums/user-status.enum.ts` - **Middleware:** `apps/backend/src/modules/auth/middleware/user-status.middleware.ts` - **Service:** `apps/backend/src/modules/auth/services/user-management.service.ts` - **DTOs:** `apps/backend/src/modules/auth/dto/update-user-status.dto.ts` ### Frontend 🎨 **Componentes:** - **Types:** `apps/frontend/src/types/auth.types.ts` (interface `UserProfile`) - **Componentes:** - `apps/frontend/src/components/ui/UserStatusBadge.tsx` - `apps/frontend/src/components/admin/UserManagementPanel.tsx` - `apps/frontend/src/components/admin/SuspendUserModal.tsx` ### Mapeo Completo 📊 [Ver mapeo completo: Requerimientos → Implementación](../../03-desarrollo/base-de-datos/MAPEO-REQUERIMIENTOS-IMPLEMENTACION.md#12-estados-de-cuenta-de-usuario) --- ## 📝 Descripción del Requerimiento ### Contexto Las cuentas de usuario requieren gestión de su ciclo de vida completo, desde el registro inicial hasta posibles situaciones de suspensión o baneo. Esta gestión es crítica para: - Seguridad del sistema (suspender cuentas comprometidas) - Cumplimiento de políticas (banear usuarios que violan términos) - Experiencia de usuario (permitir desactivación temporal voluntaria) - Verificación de email (estado pendiente hasta confirmación) ### Necesidad del Negocio **Problema:** Sin un sistema de estados bien definido: - No se puede verificar email antes de dar acceso completo - No hay manera de suspender temporalmente cuentas problemáticas - Usuarios no pueden desactivar su cuenta voluntariamente - No hay diferenciación entre suspensión reversible y baneo permanente **Solución:** Implementar un sistema de 5 estados que modela el ciclo de vida completo de una cuenta, con transiciones controladas y auditadas. --- ## 🎯 Requerimiento Funcional ### RF-AUTH-002.1: Estados Disponibles El sistema **DEBE** soportar exactamente 5 estados de cuenta: #### 1. Pendiente (`pending`) **Descripción:** Cuenta recién creada, email no verificado **Características:** - Estado inicial al registrarse - Usuario no puede acceder al sistema - Se envía email de verificación automáticamente - Expira después de 7 días si no se verifica **Acceso Permitido:** - ❌ Dashboard principal - ❌ Ejercicios - ✅ Página de verificación de email - ✅ Reenviar email de verificación **Transiciones:** - → `active`: Al verificar email - → (eliminación automática): Después de 7 días sin verificar --- #### 2. Activo (`active`) **Descripción:** Cuenta verificada, acceso completo al sistema **Características:** - Email verificado exitosamente - Acceso completo según rol asignado - Puede participar en todas las funcionalidades **Acceso Permitido:** - ✅ Todo el sistema según rol **Transiciones:** - → `inactive`: Usuario desactiva su propia cuenta - → `suspended`: Admin suspende la cuenta - → `banned`: Admin banea la cuenta --- #### 3. Inactivo (`inactive`) **Descripción:** Usuario desactivó temporalmente su cuenta **Características:** - Desactivación voluntaria por el usuario - Datos conservados intactos - Reversible por el propio usuario en cualquier momento - No recibe notificaciones **Acceso Permitido:** - ❌ Dashboard y funcionalidades principales - ✅ Página de reactivación de cuenta - ✅ Descargar datos personales (GDPR compliance) **Transiciones:** - → `active`: Usuario reactiva su cuenta - → (eliminación voluntaria): Usuario solicita eliminación de cuenta --- #### 4. Suspendido (`suspended`) **Descripción:** Admin suspendió la cuenta (reversible) **Características:** - Suspensión por admin debido a comportamiento inapropiado - **REVERSIBLE** (diferencia clave con `banned`) - Requiere revisión de admin para levantar suspensión - Usuario recibe notificación con razón de suspensión **Acceso Permitido:** - ❌ Todo el sistema - ✅ Ver notificación de suspensión con razón - ✅ Contactar soporte **Restricciones:** - No puede iniciar sesión - No recibe notificaciones del sistema - No aparece en búsquedas de usuarios **Transiciones:** - → `active`: Admin levanta suspensión - → `banned`: Admin decide hacer baneo permanente **Duración:** - Típicamente: 7-30 días - Requiere revisión periódica por admins --- #### 5. Baneado (`banned`) **Descripción:** Admin baneó la cuenta **permanentemente** **Características:** - Baneo por violación grave de términos de servicio - **IRREVERSIBLE** (no hay transición de vuelta) - Email y username quedan bloqueados - Datos se conservan por auditoría pero cuenta inaccesible **Acceso Permitido:** - ❌ Todo el sistema - ✅ Ver notificación de baneo con razón **Restricciones:** - No puede iniciar sesión - No puede crear nueva cuenta con mismo email - Username queda reservado permanentemente **Razones Comunes:** - Suplantación de identidad - Acoso a otros usuarios - Actividad fraudulenta - Violación reiterada de políticas --- ### RF-AUTH-002.2: Flujo de Estados ``` ┌──────────────┐ │ REGISTRO │ └──────┬───────┘ │ ▼ pending ──(verificar email)──> active │ │ │ ├──(usuario desactiva)──> inactive │ │ │ │ │ └──(reactiva)──> active │ │ │ ├──(admin suspende)──> suspended │ │ │ │ │ ├──(admin levanta)──> active │ │ └──(admin decide)──> banned │ │ └────(7 días)───> ∅ └──(admin banea)──> banned (eliminación) │ └──(permanente)──> ∅ ``` --- ### RF-AUTH-002.3: Reglas de Transición #### Transiciones Permitidas | Estado Actual | Puede Transicionar A | Quién Puede Hacerlo | Requiere | |---------------|---------------------|---------------------|----------| | `pending` | `active` | Usuario | Verificar email | | `pending` | ∅ (eliminación) | Sistema | 7 días sin verificar | | `active` | `inactive` | Usuario | Confirmación | | `active` | `suspended` | super_admin | Razón documentada | | `active` | `banned` | super_admin | Razón grave documentada | | `inactive` | `active` | Usuario | Click en reactivar | | `suspended` | `active` | super_admin | Revisión completada | | `suspended` | `banned` | super_admin | Decisión justificada | | `banned` | ∅ | N/A | Irreversible | #### Transiciones Prohibidas - ❌ `pending` → `suspended` (no tiene sentido suspender cuenta no verificada) - ❌ `pending` → `banned` (no tiene sentido banear cuenta no verificada) - ❌ `inactive` → `suspended` (usuario ya desactivó voluntariamente) - ❌ `suspended` → `inactive` (confusión de estados) - ❌ `banned` → cualquier otro (irreversible) --- ### RF-AUTH-002.4: Validación de Estado El sistema **DEBE** validar estado en: 1. **Login:** ```typescript // Bloquear login si status != 'active' if (user.status !== 'active') { throw new UnauthorizedException( `Cuenta ${user.status}. ${getStatusMessage(user.status)}` ); } ``` 2. **Middleware en cada request:** ```typescript // UserStatusMiddleware valida en cada request autenticado if (user.status !== 'active') { throw new ForbiddenException('Cuenta inactiva'); } ``` 3. **Frontend (validación temprana):** ```typescript // Redirigir según estado if (user.status === 'pending') { navigate('/verify-email'); } else if (user.status === 'suspended') { navigate('/account-suspended'); } else if (user.status === 'banned') { navigate('/account-banned'); } ``` --- ## 📊 Casos de Uso ### UC-AUTH-003: Usuario verifica email tras registro **Actor:** Estudiante (nuevo usuario) **Precondiciones:** Usuario registrado, status = `pending` **Flujo:** 1. Usuario recibe email de verificación 2. Usuario hace click en link de verificación 3. Sistema valida token de verificación 4. Sistema actualiza `profiles.status` de `pending` → `active` 5. Sistema audita cambio en `audit_logs` 6. Sistema envía email de bienvenida 7. Usuario redirigido a dashboard **Resultado:** Usuario con status `active` puede acceder al sistema **Variantes:** - Token expirado (>24h): Sistema reenvía nuevo email - Token inválido: Mostrar error, ofrecer reenvío --- ### UC-ADMIN-002: Admin suspende cuenta de usuario **Actor:** Super Admin **Precondiciones:** Usuario con status `active`, comportamiento inapropiado detectado **Flujo:** 1. Admin navega a panel de gestión de usuarios 2. Admin selecciona usuario a suspender 3. Sistema muestra modal de suspensión 4. Admin ingresa: - Razón de suspensión (texto obligatorio) - Duración sugerida (7, 14, 30 días, indefinido) - Evidencia (opcional: screenshots, reportes) 5. Admin confirma suspensión 6. Sistema actualiza `profiles.status` de `active` → `suspended` 7. Sistema registra en `audit_logs`: ```json { "action": "update", "resource_type": "user_status", "resource_id": "user_id", "details": { "old_status": "active", "new_status": "suspended", "reason": "Acoso a otros usuarios", "evidence": ["screenshot_url"], "suspended_by": "admin_id", "review_date": "2025-11-14" } } ``` 8. Sistema envía notificación al usuario con razón 9. Sistema cierra todas las sesiones activas del usuario **Resultado:** Usuario suspendido no puede acceder, admin puede revisar suspensión --- ### UC-AUTH-004: Usuario desactiva su propia cuenta **Actor:** Estudiante **Precondiciones:** Usuario con status `active` **Flujo:** 1. Usuario navega a Configuración → Cuenta 2. Usuario hace click en "Desactivar mi cuenta" 3. Sistema muestra modal de confirmación: - Explica que es temporal - Datos no se eliminan - Puede reactivar en cualquier momento 4. Usuario confirma ingresando password 5. Sistema valida password 6. Sistema actualiza `profiles.status` de `active` → `inactive` 7. Sistema audita cambio 8. Sistema envía email de confirmación 9. Sistema cierra sesión del usuario **Resultado:** Cuenta desactivada temporalmente **Reactivación:** 1. Usuario intenta hacer login 2. Sistema detecta status = `inactive` 3. Sistema muestra botón "Reactivar cuenta" 4. Usuario hace click 5. Sistema actualiza status a `active` 6. Usuario accede normalmente --- ## 🔐 Consideraciones de Seguridad ### 1. Prevención de Bypass **Problema:** Usuario suspendido podría intentar acceder vía API directamente **Solución:** ```typescript // Middleware en CADA request autenticado @Injectable() export class UserStatusMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { const user = req.user; // Excepciones: endpoints de reactivación const allowedPaths = ['/auth/reactivate', '/auth/status']; if (allowedPaths.includes(req.path)) { return next(); } // Bloquear si no es active if (user.status !== 'active') { throw new ForbiddenException( `Account is ${user.status}. Access denied.` ); } next(); } } ``` ### 2. Auditoría Obligatoria Todo cambio de estado **DEBE** ser auditado: ```sql -- Trigger automático CREATE TRIGGER trg_audit_status_change AFTER UPDATE OF status ON auth_management.profiles FOR EACH ROW EXECUTE FUNCTION audit_logging.log_status_change(); ``` ### 3. Rate Limiting en Reactivación Prevenir abuso de reactivación/desactivación: ```typescript // Máximo 3 reactivaciones por día const reactivations = await getReactivationCount(userId, 'today'); if (reactivations >= 3) { throw new TooManyRequestsException( 'Maximum reactivations reached for today' ); } ``` ### 4. Notificación Obligatoria Usuario **DEBE** ser notificado de cualquier cambio de estado no iniciado por él: ```typescript if (oldStatus !== newStatus && changedBy !== userId) { await notificationService.send({ userId, type: 'system_announcement', priority: 'high', title: `Account status changed to ${newStatus}`, body: reason, }); } ``` --- ## ✅ Criterios de Aceptación ### AC-001: ENUM Implementado - [x] ENUM `user_status` existe con 5 valores - [x] Columna `profiles.status` usa el ENUM - [x] Default es `'pending'` ### AC-002: Transiciones Validadas - [x] Solo transiciones permitidas pueden ejecutarse - [x] Transiciones prohibidas lanzan exception - [x] Trigger audita todos los cambios ### AC-003: Middleware Activo - [x] `UserStatusMiddleware` valida en cada request - [x] Excepciones configuradas para paths específicos - [x] Frontend redirige según status ### AC-004: Notificaciones Enviadas - [x] Usuario recibe notificación al cambiar status - [x] Email enviado con razón y próximos pasos - [x] Notificación visible en UI ### AC-005: Admin Panel Funcional - [x] Admin puede suspender/banear usuarios - [x] Razón obligatoria al suspender/banear - [x] Historial de cambios visible - [x] Admin puede levantar suspensión ### AC-006: Usuario Puede Desactivar - [x] Botón "Desactivar cuenta" en configuración - [x] Modal de confirmación con password - [x] Reactivación simple desde login --- ## 🧪 Testing ### Test Case 1: Usuario pending no puede acceder ```typescript test('Pending user cannot access protected endpoints', async () => { const user = await createUser({ status: 'pending' }); const token = await getAuthToken(user); const response = await request(app.getHttpServer()) .get('/dashboard') .set('Authorization', `Bearer ${token}`); expect(response.status).toBe(403); expect(response.body.message).toContain('pending'); }); ``` ### Test Case 2: Admin puede suspender usuario ```typescript test('Admin can suspend user with reason', async () => { const admin = await createUser({ role: 'super_admin' }); const user = await createUser({ status: 'active' }); await loginAs(admin); const response = await api.post(`/admin/users/${user.id}/suspend`, { reason: 'Inappropriate behavior', duration: 14, }); expect(response.status).toBe(200); const updatedUser = await getUser(user.id); expect(updatedUser.status).toBe('suspended'); // Verificar auditoría const auditLog = await getLatestAuditLog(user.id, 'user_status'); expect(auditLog.details.reason).toBe('Inappropriate behavior'); }); ``` ### Test Case 3: Usuario puede desactivar y reactivar ```typescript test('User can deactivate and reactivate account', async () => { const user = await createUser({ status: 'active' }); await loginAs(user); // Desactivar const deactivateResponse = await api.post('/auth/deactivate', { password: user.password, }); expect(deactivateResponse.status).toBe(200); let updatedUser = await getUser(user.id); expect(updatedUser.status).toBe('inactive'); // Reactivar const reactivateResponse = await api.post('/auth/reactivate'); expect(reactivateResponse.status).toBe(200); updatedUser = await getUser(user.id); expect(updatedUser.status).toBe('active'); }); ``` ### Test Case 4: Banned user cannot login ```typescript test('Banned user cannot login', async () => { const user = await createUser({ status: 'banned' }); const response = await request(app.getHttpServer()) .post('/auth/login') .send({ email: user.email, password: user.password }); expect(response.status).toBe(401); expect(response.body.message).toContain('banned'); }); ``` --- ## 📚 Referencias Adicionales ### Documentos Relacionados - 📄 [RF-AUTH-001: Sistema de Roles](./RF-AUTH-001-roles.md) - Estados interactúan con roles - 📄 [RF-NOT-001: Tipos de Notificaciones](../../06-notificaciones/RF-NOT-001-tipos-notificaciones.md) - Notificaciones de cambio de estado - 📄 [RF-AUD-001: Registro de Acciones](../../08-auditoria-configuracion/RF-AUD-001-registro-acciones.md) - Auditoría de cambios ### Regulaciones - [GDPR Article 17: Right to Erasure](https://gdpr-info.eu/art-17-gdpr/) - Derecho al olvido - [GDPR Article 20: Right to Data Portability](https://gdpr-info.eu/art-20-gdpr/) - Exportar datos --- ## 📅 Historial de Cambios | Versión | Fecha | Autor | Cambios | |---------|-------|-------|---------| | 1.0 | 2025-11-07 | Database Team | Creación inicial del requerimiento | --- **Documento:** `docs/01-requerimientos/01-autenticacion-autorizacion/RF-AUTH-002-estados-cuenta.md` **Ruta relativa desde docs/:** `01-requerimientos/01-autenticacion-autorizacion/RF-AUTH-002-estados-cuenta.md`