trading-platform/docs/02-definicion-modulos/OQI-001-fundamentos-auth/historias-usuario/US-AUTH-012-session-management.md
rckrdmrd c1b5081208 feat(ml): Complete FASE 11 - BTCUSD update and comprehensive documentation alignment
ML Engine Updates:
- Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records
- Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence)
- Backtest results: +176.71R profit with aggressive_filter strategy

Documentation Consolidation:
- Created docs/99-analisis/_MAP.md index with 13 new analysis documents
- Consolidated inventories: removed duplicates from orchestration/inventarios/
- Updated ML_INVENTORY.yml with BTCUSD metrics and training results
- Added execution reports: FASE11-BTCUSD, correction issues, alignment validation

Architecture & Integration:
- Updated all module documentation with NEXUS v3.4 frontmatter
- Fixed _MAP.md indexes across all folders
- Updated orchestration plans and traces

Files: 229 changed, 5064 insertions(+), 1872 deletions(-)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 09:31:29 -06:00

23 KiB

id title type status priority epic story_points created_date updated_date
US-AUTH-012 Gestion de Sesiones User Story To Do Alta OQI-001 5 2025-12-05 2026-01-04

US-AUTH-012: Gestión de Sesiones

Version: 1.0.0 Fecha: 2025-12-05 Estado: Pendiente Story Points: 5 Prioridad: P1 (Alta) Épica: OQI-001


Historia de Usuario

Como usuario de Trading Platform Quiero poder ver y gestionar mis sesiones activas en diferentes dispositivos Para tener control sobre dónde está abierta mi cuenta y poder cerrar sesiones remotamente por seguridad


Criterios de Aceptación

AC-001: Página de sesiones

Dado que estoy autenticado Cuando accedo a Configuración > Seguridad > Sesiones Entonces debería ver una lista de todas mis sesiones activas

AC-002: Información de cada sesión

Dado que estoy viendo mis sesiones activas Cuando veo una sesión en la lista Entonces debería ver:

  • Tipo de dispositivo (Computadora, Móvil, Tablet)
  • Sistema operativo (Windows, macOS, iOS, Android, Linux)
  • Navegador (Chrome, Safari, Firefox, etc.)
  • Ubicación aproximada (Ciudad, País)
  • Dirección IP
  • Fecha y hora de último acceso
  • Indicador de "Sesión actual" si es la sesión activa
  • Botón "Cerrar sesión" (excepto para sesión actual)

AC-003: Sesión actual destacada

Dado que estoy viendo mis sesiones Cuando identifico mi sesión actual Entonces debería estar:

  • Marcada claramente como "Esta sesión"
  • En la parte superior de la lista
  • Con un badge o color distintivo
  • Sin botón de "Cerrar sesión"

AC-004: Cerrar una sesión individual

Dado que veo una sesión que no reconozco Cuando hago click en "Cerrar sesión" Entonces debería:

  1. Ver confirmación "¿Cerrar esta sesión?"
  2. Al confirmar, invalidar ese token JWT
  3. Ver mensaje "Sesión cerrada"
  4. La sesión desaparece de la lista
  5. Si esa sesión intenta hacer requests, recibe 401 Unauthorized

AC-005: Cerrar todas las demás sesiones

Dado que quiero cerrar sesión en todos mis dispositivos Cuando hago click en "Cerrar todas las demás sesiones" Entonces debería:

  1. Ver confirmación "¿Cerrar todas las sesiones excepto la actual?"
  2. Al confirmar, invalidar todos los tokens excepto el actual
  3. Ver mensaje "X sesiones cerradas"
  4. Solo quedar mi sesión actual en la lista

AC-006: Detección de dispositivo

Dado que inicio sesión desde diferentes dispositivos Cuando reviso mis sesiones Entonces debería ver íconos apropiados:

  • 💻 Computadora de escritorio
  • 📱 Teléfono móvil
  • 📋 Tablet
  • 🌐 Desconocido

AC-007: Geolocalización

Dado que inicio sesión desde diferentes ubicaciones Cuando reviso mis sesiones Entonces debería ver ubicación aproximada basada en IP:

  • "San Francisco, Estados Unidos"
  • "Ciudad de México, México"
  • "Madrid, España"
  • Si no se puede determinar: "Ubicación desconocida"

AC-008: Alerta de sesión sospechosa

Dado que inicio sesión desde un dispositivo o ubicación nueva Cuando completo el login Entonces debería:

  1. Recibir email de notificación con:
    • Dispositivo y ubicación
    • Fecha y hora
    • Link para cerrar sesión si no fui yo
  2. (Opcional) Ver notificación in-app

AC-009: Historial de sesiones

Dado que quiero ver sesiones pasadas Cuando veo la sección de historial Entonces debería ver últimas 20 sesiones incluyendo:

  • Sesiones activas (verde)
  • Sesiones cerradas manualmente (gris)
  • Sesiones expiradas (naranja)
  • Con timestamps de inicio y fin

AC-010: Auto-expiración de sesiones

Dado que una sesión lleva 30 días sin actividad Cuando esa sesión intenta hacer un request Entonces debería:

  • Recibir 401 Unauthorized
  • Ser redirigido a login
  • Desaparecer de la lista de sesiones activas

AC-011: Sesión sin "Recordarme"

Dado que inicié sesión sin marcar "Recordarme" Cuando pasan 24 horas Entonces esa sesión debería expirar automáticamente

AC-012: Sesión con "Recordarme"

Dado que inicié sesión con "Recordarme" marcado Cuando paso tiempo sin usar la app Entonces la sesión debería permanecer activa hasta 30 días

AC-013: Refresh tokens

Dado que tengo una sesión activa Cuando mi access token expira (15 minutos) Entonces debería:

  • Usar refresh token automáticamente
  • Obtener nuevo access token
  • Continuar usando la app sin interrupciones

AC-014: Cerrar sesión actual

Dado que quiero cerrar mi sesión actual Cuando hago click en "Cerrar sesión" en el header Entonces debería:

  1. Invalidar mi token actual
  2. Limpiar localStorage/cookies
  3. Ser redirigido a la página de login
  4. No poder acceder a rutas protegidas

Mockup

┌─────────────────────────────────────────────────────────────┐
│  Configuración > Seguridad > Sesiones                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Sesiones activas                                           │
│  Gestiona dónde está abierta tu cuenta                     │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  💻 Chrome en macOS               🟢 Esta sesión    │   │
│  │                                                      │   │
│  │  San Francisco, Estados Unidos • 201.45.67.89       │   │
│  │  Última actividad: Hace 2 minutos                   │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  📱 Safari en iPhone                  [Cerrar sesión]│   │
│  │                                                      │   │
│  │  Ciudad de México, México • 189.203.45.12           │   │
│  │  Última actividad: Hace 3 horas                     │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  💻 Firefox en Windows               [Cerrar sesión]│   │
│  │                                                      │   │
│  │  Madrid, España • 85.123.45.67                      │   │
│  │  Última actividad: Hace 2 días                      │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │      🚪 Cerrar todas las demás sesiones              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ────────────────────────────────────────────────────────  │
│                                                             │
│  Historial de sesiones                                     │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  📋 Chrome en iPad               ⏱️ Sesión expirada  │   │
│  │  Barcelona, España                                   │   │
│  │  15 Nov 2025 - 18 Nov 2025                          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  📱 Chrome en Android            ✓ Cerrada manual    │   │
│  │  Bogotá, Colombia                                    │   │
│  │  10 Nov 2025 - 12 Nov 2025                          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Modal de confirmación:
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│              ⚠️ Cerrar todas las demás sesiones            │
│                                                             │
│  Esto cerrará sesión en todos tus otros dispositivos.      │
│  Solo permanecerá activa tu sesión actual.                 │
│                                                             │
│  Se cerrarán 2 sesiones.                                   │
│                                                             │
│  ┌──────────────────────┐  ┌──────────────────────┐        │
│  │      Cancelar        │  │  Cerrar sesiones     │        │
│  └──────────────────────┘  └──────────────────────┘        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Email de nueva sesión:
┌─────────────────────────────────────────────────────────────┐
│  De: Trading Platform Security <security@trading.com>           │
│  Asunto: Nueva sesión iniciada en tu cuenta                │
│                                                             │
│  Hola Juan,                                                 │
│                                                             │
│  Se inició sesión en tu cuenta desde un nuevo dispositivo: │
│                                                             │
│  📱 Safari en iPhone                                        │
│  📍 Ciudad de México, México                                │
│  🕐 5 de Diciembre, 2025 a las 14:30 CST                   │
│  🌐 IP: 189.203.45.12                                      │
│                                                             │
│  ¿Fuiste tú?                                                │
│  Si reconoces esta actividad, puedes ignorar este email.   │
│                                                             │
│  ¿No fuiste tú?                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │         Cerrar esta sesión inmediatamente            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  También puedes cambiar tu contraseña desde:               │
│  Configuración > Seguridad > Cambiar contraseña            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Tareas Técnicas

Database (DB)

  • Tabla user_sessions:
    CREATE TABLE user_sessions (
      id UUID PRIMARY KEY,
      user_id UUID REFERENCES users(id),
      refresh_token VARCHAR(512) UNIQUE NOT NULL,
      access_token_jti VARCHAR(255), -- JWT ID del access token actual
      device_type VARCHAR(50), -- 'desktop', 'mobile', 'tablet'
      device_name VARCHAR(255), -- 'Chrome on macOS'
      os VARCHAR(100),
      browser VARCHAR(100),
      ip_address VARCHAR(45),
      location_city VARCHAR(100),
      location_country VARCHAR(100),
      user_agent TEXT,
      remember_me BOOLEAN DEFAULT false,
      last_activity TIMESTAMP DEFAULT NOW(),
      expires_at TIMESTAMP NOT NULL,
      created_at TIMESTAMP DEFAULT NOW(),
      INDEX idx_user_expires (user_id, expires_at),
      INDEX idx_refresh_token (refresh_token),
      INDEX idx_last_activity (last_activity)
    );
    
  • Tabla session_history:
    CREATE TABLE session_history (
      id UUID PRIMARY KEY,
      user_id UUID REFERENCES users(id),
      device_name VARCHAR(255),
      ip_address VARCHAR(45),
      location_city VARCHAR(100),
      location_country VARCHAR(100),
      status VARCHAR(50), -- 'active', 'closed_manual', 'expired'
      started_at TIMESTAMP NOT NULL,
      ended_at TIMESTAMP,
      created_at TIMESTAMP DEFAULT NOW(),
      INDEX idx_user_started (user_id, started_at)
    );
    

Backend (BE)

  • Modificar login para crear sesión:
    • Generar access token (JWT, 15 min)
    • Generar refresh token (aleatorio, 30 días)
    • Parsear user agent
    • Obtener geolocalización de IP
    • Guardar sesión en DB
    • Detectar si es nuevo dispositivo/ubicación
    • Enviar email si es sospechoso
  • Endpoint GET /api/v1/auth/sessions
    • Listar sesiones activas del usuario
    • Marcar sesión actual
    • Ordenar por last_activity DESC
  • Endpoint GET /api/v1/auth/sessions/history
    • Últimas 20 sesiones (activas + cerradas)
  • Endpoint DELETE /api/v1/auth/sessions/:id
    • Cerrar sesión específica
    • Invalidar tokens
    • Registrar en historial
  • Endpoint DELETE /api/v1/auth/sessions/others
    • Cerrar todas excepto actual
  • Endpoint POST /api/v1/auth/refresh
    • Validar refresh token
    • Generar nuevo access token
    • Actualizar last_activity
  • Endpoint POST /api/v1/auth/logout
    • Cerrar sesión actual
    • Invalidar tokens
  • Service SessionService
    • createSession()
    • validateSession()
    • refreshAccessToken()
    • terminateSession()
    • terminateAllOtherSessions()
    • getActiveSessions()
    • isNewDevice()
  • Service DeviceDetectionService
    • parseUserAgent()
    • getDeviceType()
    • getOS()
    • getBrowser()
  • Service GeolocationService
    • getLocationFromIP()
    • Usar: ipapi.co, ip-api.com, o MaxMind GeoIP2
  • Librería: ua-parser-js (user agent parsing)
  • Cron job: Limpiar sesiones expiradas diariamente
  • Tests unitarios (15 casos)
  • Tests de integración (10 escenarios)

Frontend (FE)

  • Página Settings/Security/Sessions.tsx
  • Componente SessionCard.tsx
  • Componente SessionHistoryCard.tsx
  • Modal de confirmación de cierre
  • Servicio de auto-refresh de tokens
    • Interceptor de Axios/Fetch
    • Detectar 401
    • Llamar a /refresh
    • Reintentar request original
  • Almacenamiento de tokens:
    • Access token en memoria (state)
    • Refresh token en httpOnly cookie (más seguro)
  • Tests con React Testing Library

Testing (QA)

  • E2E: Ver sesiones activas
  • E2E: Cerrar sesión individual
  • E2E: Cerrar todas las demás sesiones
  • E2E: Auto-refresh de access token
  • E2E: Sesión expira después de 30 días
  • E2E: Email de nueva sesión
  • E2E: Login desde múltiples dispositivos
  • Test de seguridad: Refresh token rotation
  • Test de seguridad: Session fixation
  • Performance: Lista de sesiones < 300ms

Dependencias

  • Bloqueantes:
    • US-AUTH-002: Login genera tokens
    • Servicio de geolocalización (ipapi.co o similar)

Definition of Ready (DoR)

  • Mockups aprobados
  • API contract definido
  • Estrategia de tokens definida (access + refresh)
  • Servicio de geolocalización seleccionado
  • Esquema de base de datos revisado

Definition of Done (DoD)

  • Código implementado y revisado
  • Tests unitarios con 80%+ cobertura
  • Tests de integración pasando
  • Tests E2E implementados
  • Auto-refresh de tokens funcional
  • Geolocalización funcional
  • Device detection funcional
  • Email de alertas configurado
  • Cron job de limpieza configurado
  • Documentación actualizada
  • QA aprobado en staging
  • Deploy a producción exitoso

Notas Técnicas

Token Strategy: Access + Refresh

Access Token (JWT):

  • Duración: 15 minutos
  • Almacenado en: Memoria (React state)
  • Incluye: user_id, email, role, jti (JWT ID)
  • Se envía en header: Authorization: Bearer <token>

Refresh Token:

  • Duración: 24 horas (sin "Recordarme") o 30 días (con "Recordarme")
  • Almacenado en: httpOnly cookie
  • Es un string aleatorio (crypto.randomBytes)
  • Se guarda hasheado en DB
  • Rotation: Cada refresh genera nuevo token

JWT Structure

{
  "jti": "session-uuid", // JWT ID (unique)
  "sub": "user-uuid",
  "email": "user@example.com",
  "role": "user",
  "iat": 1234567890,
  "exp": 1234568790 // +15 min
}

Auto-Refresh Flow

// Axios interceptor
axios.interceptors.response.use(
  response => response,
  async error => {
    if (error.response?.status === 401) {
      // Access token expiró, intentar refresh
      try {
        const { accessToken } = await refreshTokens();
        // Actualizar token en memoria
        setAccessToken(accessToken);
        // Reintentar request original
        error.config.headers.Authorization = `Bearer ${accessToken}`;
        return axios.request(error.config);
      } catch (refreshError) {
        // Refresh falló, redirect a login
        window.location.href = '/login';
      }
    }
    return Promise.reject(error);
  }
);

User Agent Parsing

import UAParser from 'ua-parser-js';

const parser = new UAParser(userAgent);
const result = parser.getResult();

const deviceInfo = {
  device_type: result.device.type || 'desktop', // mobile, tablet, desktop
  device_name: `${result.browser.name} on ${result.os.name}`,
  os: `${result.os.name} ${result.os.version}`,
  browser: `${result.browser.name} ${result.browser.version}`
};

Geolocation Service

// Opción 1: ipapi.co (gratuito, 30k requests/mes)
const response = await fetch(`https://ipapi.co/${ip}/json/`);
const data = await response.json();

const location = {
  city: data.city,
  country: data.country_name,
  latitude: data.latitude,
  longitude: data.longitude
};

// Opción 2: ip-api.com (gratuito, 45 requests/min)
// Opción 3: MaxMind GeoIP2 (pago, más preciso)

New Device Detection

async function isNewDevice(userId, deviceName, ipAddress) {
  const existingSession = await db.userSessions.findFirst({
    where: {
      user_id: userId,
      device_name: deviceName,
      ip_address: ipAddress,
      created_at: { gte: thirtyDaysAgo }
    }
  });

  return !existingSession;
}

Refresh Token Rotation

Cada vez que se usa un refresh token, se genera uno nuevo:

async function refreshAccessToken(oldRefreshToken) {
  // 1. Validar refresh token
  const session = await findSessionByRefreshToken(oldRefreshToken);

  if (!session || session.expires_at < new Date()) {
    throw new Error('Invalid or expired refresh token');
  }

  // 2. Generar nuevo access token
  const accessToken = generateJWT(session.user_id);

  // 3. Generar nuevo refresh token
  const newRefreshToken = crypto.randomBytes(64).toString('hex');

  // 4. Actualizar sesión
  await db.userSessions.update({
    where: { id: session.id },
    data: {
      refresh_token: hashToken(newRefreshToken),
      access_token_jti: accessToken.jti,
      last_activity: new Date()
    }
  });

  // 5. Devolver tokens
  return { accessToken, refreshToken: newRefreshToken };
}

Security Considerations

  1. Refresh Token Rotation:

    • Cada refresh invalida el token anterior
    • Previene replay attacks
  2. httpOnly Cookies:

    • Refresh token en httpOnly cookie
    • No accesible desde JavaScript
    • Previene XSS
  3. Secure Cookies:

    • Flag Secure (solo HTTPS)
    • Flag SameSite=Strict
  4. JTI (JWT ID):

    • Cada access token tiene ID único
    • Permite invalidación específica
  5. Device Fingerprinting:

    • Detectar cambios sospechosos
    • Alertar al usuario
  6. Rate Limiting:

    • Limitar requests a /refresh
    • Prevenir brute force

Environment Variables

JWT_SECRET=your-secret-key-256-bits
JWT_ACCESS_EXPIRY=15m
JWT_REFRESH_EXPIRY_SHORT=24h # sin "Recordarme"
JWT_REFRESH_EXPIRY_LONG=30d # con "Recordarme"
GEOLOCATION_API_KEY=your-api-key # si usas servicio de pago

Cron Job: Cleanup

// Ejecutar diariamente a las 3 AM
cron.schedule('0 3 * * *', async () => {
  // Eliminar sesiones expiradas
  await db.userSessions.deleteMany({
    where: {
      expires_at: { lt: new Date() }
    }
  });

  // Mover a historial
  // (opcional, si no usas soft deletes)
});

Requerimientos Relacionados

Especificaciones Relacionadas