workspace/projects/gamilit/docs/01-fase-alcance-inicial/EAI-001-fundamentos/requerimientos/RF-AUTH-002-estados-cuenta.md
rckrdmrd ea1879f4ad feat: Initial workspace structure with multi-level Git configuration
- 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>
2025-12-08 10:44:23 -06:00

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:

  1. auth_management.profilesapps/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


📝 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

  • pendingsuspended (no tiene sentido suspender cuenta no verificada)
  • pendingbanned (no tiene sentido banear cuenta no verificada)
  • inactivesuspended (usuario ya desactivó voluntariamente)
  • suspendedinactive (confusión de estados)
  • banned → cualquier otro (irreversible)

RF-AUTH-002.4: Validación de Estado

El sistema DEBE validar estado en:

  1. Login:
// Bloquear login si status != 'active'
if (user.status !== 'active') {
  throw new UnauthorizedException(
    `Cuenta ${user.status}. ${getStatusMessage(user.status)}`
  );
}
  1. Middleware en cada request:
// UserStatusMiddleware valida en cada request autenticado
if (user.status !== 'active') {
  throw new ForbiddenException('Cuenta inactiva');
}
  1. 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:

  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 pendingactive
  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 activesuspended
  7. 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"
      }
    }
    
  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 activeinactive
  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:

// 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_status existe con 5 valores
  • Columna profiles.status usa 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

  • UserStatusMiddleware valida 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

Regulaciones


📅 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