- Configure workspace Git repository with comprehensive .gitignore - Add Odoo as submodule for ERP reference code - Include documentation: SETUP.md, GIT-STRUCTURE.md - Add gitignore templates for projects (backend, frontend, database) - Structure supports independent repos per project/subproject level Workspace includes: - core/ - Reusable patterns, modules, orchestration system - projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.) - knowledge-base/ - Reference code and patterns (includes Odoo submodule) - devtools/ - Development tools and templates - customers/ - Client implementations template 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
18 KiB
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
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:
auth_management.profiles→apps/database/ddl/schemas/auth_management/tables/03-profiles.sql:17- Columna:
status auth_management.user_status NOT NULL DEFAULT 'pending'
- Columna:
🗄️ Funciones:
auth_management.verify_user_status()→ Valida que usuario puede accederauth_management.suspend_user()→ Admin suspende usuarioauth_management.ban_user()→ Admin banea usuario permanentementeauth_management.reactivate_user()→ Reactiva usuario inactive/suspended
🗄️ Triggers:
trg_profiles_status_change→ Audita cambios de estado enaudit_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(interfaceUserProfile) - Componentes:
apps/frontend/src/components/ui/UserStatusBadge.tsxapps/frontend/src/components/admin/UserManagementPanel.tsxapps/frontend/src/components/admin/SuspendUserModal.tsx
Mapeo Completo
📊 Ver mapeo completo: Requerimientos → Implementación
📝 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:
- Login:
// Bloquear login si status != 'active'
if (user.status !== 'active') {
throw new UnauthorizedException(
`Cuenta ${user.status}. ${getStatusMessage(user.status)}`
);
}
- Middleware en cada request:
// UserStatusMiddleware valida en cada request autenticado
if (user.status !== 'active') {
throw new ForbiddenException('Cuenta inactiva');
}
- Frontend (validación temprana):
// 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:
- Usuario recibe email de verificación
- Usuario hace click en link de verificación
- Sistema valida token de verificación
- Sistema actualiza
profiles.statusdepending→active - Sistema audita cambio en
audit_logs - Sistema envía email de bienvenida
- 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:
- Admin navega a panel de gestión de usuarios
- Admin selecciona usuario a suspender
- Sistema muestra modal de suspensión
- Admin ingresa:
- Razón de suspensión (texto obligatorio)
- Duración sugerida (7, 14, 30 días, indefinido)
- Evidencia (opcional: screenshots, reportes)
- Admin confirma suspensión
- Sistema actualiza
profiles.statusdeactive→suspended - Sistema registra en
audit_logs:{ "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" } } - Sistema envía notificación al usuario con razón
- 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:
- Usuario navega a Configuración → Cuenta
- Usuario hace click en "Desactivar mi cuenta"
- Sistema muestra modal de confirmación:
- Explica que es temporal
- Datos no se eliminan
- Puede reactivar en cualquier momento
- Usuario confirma ingresando password
- Sistema valida password
- Sistema actualiza
profiles.statusdeactive→inactive - Sistema audita cambio
- Sistema envía email de confirmación
- Sistema cierra sesión del usuario
Resultado: Cuenta desactivada temporalmente
Reactivación:
- Usuario intenta hacer login
- Sistema detecta status =
inactive - Sistema muestra botón "Reactivar cuenta"
- Usuario hace click
- Sistema actualiza status a
active - Usuario accede normalmente
🔐 Consideraciones de Seguridad
1. Prevención de Bypass
Problema: Usuario suspendido podría intentar acceder vía API directamente
Solución:
// 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:
-- 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:
// 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:
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
- ENUM
user_statusexiste con 5 valores - Columna
profiles.statususa el ENUM - Default es
'pending'
AC-002: Transiciones Validadas
- Solo transiciones permitidas pueden ejecutarse
- Transiciones prohibidas lanzan exception
- Trigger audita todos los cambios
AC-003: Middleware Activo
UserStatusMiddlewarevalida en cada request- Excepciones configuradas para paths específicos
- Frontend redirige según status
AC-004: Notificaciones Enviadas
- Usuario recibe notificación al cambiar status
- Email enviado con razón y próximos pasos
- Notificación visible en UI
AC-005: Admin Panel Funcional
- Admin puede suspender/banear usuarios
- Razón obligatoria al suspender/banear
- Historial de cambios visible
- Admin puede levantar suspensión
AC-006: Usuario Puede Desactivar
- Botón "Desactivar cuenta" en configuración
- Modal de confirmación con password
- Reactivación simple desde login
🧪 Testing
Test Case 1: Usuario pending no puede acceder
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
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
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
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 - Estados interactúan con roles
- 📄 RF-NOT-001: Tipos de Notificaciones - Notificaciones de cambio de estado
- 📄 RF-AUD-001: Registro de Acciones - Auditoría de cambios
Regulaciones
- GDPR Article 17: Right to Erasure - Derecho al olvido
- GDPR Article 20: Right to Data Portability - 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