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>
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:
- Ver confirmación "¿Cerrar esta sesión?"
- Al confirmar, invalidar ese token JWT
- Ver mensaje "Sesión cerrada"
- La sesión desaparece de la lista
- 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:
- Ver confirmación "¿Cerrar todas las sesiones excepto la actual?"
- Al confirmar, invalidar todos los tokens excepto el actual
- Ver mensaje "X sesiones cerradas"
- 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:
- Recibir email de notificación con:
- Dispositivo y ubicación
- Fecha y hora
- Link para cerrar sesión si no fui yo
- (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:
- Invalidar mi token actual
- Limpiar localStorage/cookies
- Ser redirigido a la página de login
- 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
SessionServicecreateSession()validateSession()refreshAccessToken()terminateSession()terminateAllOtherSessions()getActiveSessions()isNewDevice()
- Service
DeviceDetectionServiceparseUserAgent()getDeviceType()getOS()getBrowser()
- Service
GeolocationServicegetLocationFromIP()- 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
-
Refresh Token Rotation:
- Cada refresh invalida el token anterior
- Previene replay attacks
-
httpOnly Cookies:
- Refresh token en httpOnly cookie
- No accesible desde JavaScript
- Previene XSS
-
Secure Cookies:
- Flag
Secure(solo HTTPS) - Flag
SameSite=Strict
- Flag
-
JTI (JWT ID):
- Cada access token tiene ID único
- Permite invalidación específica
-
Device Fingerprinting:
- Detectar cambios sospechosos
- Alertar al usuario
-
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)
});