[TASK-2026-01-25-FRONTEND-ANALYSIS] docs: Add frontend specifications and user stories
- Add 5 frontend specification documents (ET-*-frontend.md): - ET-AUTH-006: Authentication module frontend spec - ET-ML-008: ML Signals module frontend spec - ET-LLM-007: LLM Agent module frontend spec - ET-PFM-008: Portfolio Manager frontend spec (design) - ET-MKT-003: Marketplace frontend spec (design) - Add 8 new user stories: - US-AUTH-013: Global logout - US-AUTH-014: Device management - US-ML-008: Ensemble signal view - US-ML-009: ICT analysis view - US-ML-010: Multi-symbol scan - US-LLM-011: Execute trade from chat - US-PFM-013: Rebalance alerts - US-PFM-014: PDF report generation - Update task index with completed analysis Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3be1d324db
commit
cdec253b02
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,514 @@
|
||||
---
|
||||
id: "US-AUTH-013"
|
||||
title: "Logout Global"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Alta"
|
||||
epic: "OQI-001"
|
||||
story_points: 3
|
||||
created_date: "2026-01-25"
|
||||
updated_date: "2026-01-25"
|
||||
---
|
||||
|
||||
# US-AUTH-013: Logout Global
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2026-01-25
|
||||
**Estado:** Pendiente
|
||||
**Story Points:** 3
|
||||
**Prioridad:** P1 (Alta)
|
||||
**Épica:** [OQI-001](../_MAP.md)
|
||||
|
||||
---
|
||||
|
||||
## Historia de Usuario
|
||||
|
||||
**Como** usuario de Trading Platform
|
||||
**Quiero** poder cerrar sesión en todos mis dispositivos simultáneamente
|
||||
**Para** asegurarme de que mi cuenta está completamente segura, especialmente si creo que ha sido comprometida o si pierdo un dispositivo
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
### AC-001: Opción de logout global en perfil
|
||||
|
||||
**Dado** que estoy autenticado y en mi perfil
|
||||
**Cuando** accedo a Configuración > Seguridad > Sesiones
|
||||
**Entonces** debería ver un botón "Cerrar sesión en todos los dispositivos" o similar
|
||||
|
||||
### AC-002: Confirmación de logout global
|
||||
|
||||
**Dado** que quiero cerrar sesión en todos mis dispositivos
|
||||
**Cuando** hago click en el botón de logout global
|
||||
**Entonces** debería ver un modal de confirmación que indique:
|
||||
- "Esto cerrará sesión en TODOS tus dispositivos"
|
||||
- "Incluida esta sesión actual"
|
||||
- "Tendrás que volver a iniciar sesión"
|
||||
- Botón "Cancelar" y "Cerrar todas las sesiones"
|
||||
|
||||
### AC-003: Invalidación de todos los tokens
|
||||
|
||||
**Dado** que confirmo el logout global
|
||||
**Cuando** se ejecuta la acción
|
||||
**Entonces** debería:
|
||||
1. Invalidar TODOS los tokens JWT de todas las sesiones
|
||||
2. Invalidar TODOS los refresh tokens
|
||||
3. Marcar todas las sesiones como "closed" en la DB
|
||||
4. No permitir que ningún token anterior funcione
|
||||
|
||||
### AC-004: Redirección a login
|
||||
|
||||
**Dado** que logout global se completó exitosamente
|
||||
**Cuando** se cierre la sesión
|
||||
**Entonces** debería:
|
||||
1. Mostrar mensaje "Sesión cerrada en todos los dispositivos"
|
||||
2. Redirigir a la página de login después de 2 segundos
|
||||
3. Limpiar localStorage y cookies
|
||||
4. No permitir acceso a rutas protegidas
|
||||
|
||||
### AC-005: Email de notificación
|
||||
|
||||
**Dado** que ejecuté logout global
|
||||
**Cuando** se cierre la sesión
|
||||
**Entonces** debería recibir email en la dirección registrada indicando:
|
||||
- "Se cerró sesión en todos tus dispositivos"
|
||||
- Fecha y hora
|
||||
- Si no fuiste tú, link para cambiar contraseña
|
||||
|
||||
### AC-006: Impossibilidad de continuar con tokens antiguos
|
||||
|
||||
**Dado** que hice logout global
|
||||
**Cuando** intento usar un token anterior en cualquier dispositivo
|
||||
**Entonces** debería recibir:
|
||||
- Código 401 Unauthorized
|
||||
- Mensaje "Sesión inválida"
|
||||
- Redirección a login
|
||||
|
||||
### AC-007: Operación atómica
|
||||
|
||||
**Dado** que inicio logout global
|
||||
**Cuando** hay una falla de red durante el proceso
|
||||
**Entonces** debería:
|
||||
- Reintentar automáticamente 3 veces
|
||||
- Si continúa fallando, mostrar error al usuario
|
||||
- Los tokens deben estar invalidados (garantizar atomicidad)
|
||||
|
||||
### AC-008: Auditoria y registro
|
||||
|
||||
**Dado** que ejecuté logout global
|
||||
**Cuando** se complete la acción
|
||||
**Entonces** debería:
|
||||
- Quedar registrado en logs de auditoria
|
||||
- Incluir timestamp, user_id, tipo de acción
|
||||
- Ser visible en historial de sesiones como "Cerrado por logout global"
|
||||
|
||||
---
|
||||
|
||||
## 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 │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 🔒 CERRAR SESIÓN EN TODOS LOS DISPOSITIVOS │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ⚠️ Esto cerrará sesión en esta y todas las otras │
|
||||
│ sesiones. Tendrás que volver a iniciar sesión. │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
Modal de confirmación:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ⚠️ Cerrar sesión en todos los dispositivos │
|
||||
│ │
|
||||
│ Esto cerrará sesión INMEDIATAMENTE en: │
|
||||
│ │
|
||||
│ • Esta sesión (Chrome en macOS) │
|
||||
│ • Safari en iPhone │
|
||||
│ • Firefox en Windows │
|
||||
│ │
|
||||
│ Se cerrarán 3 sesiones en total. │
|
||||
│ │
|
||||
│ Si no fuiste tú, considera cambiar tu contraseña después. │
|
||||
│ │
|
||||
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
||||
│ │ Cancelar │ │ Cerrar todo │ │
|
||||
│ └──────────────────────┘ └──────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
Email de notificación:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ De: Trading Platform Security <security@trading.com> │
|
||||
│ Asunto: Se cerró sesión en todos tus dispositivos │
|
||||
│ │
|
||||
│ Hola Juan, │
|
||||
│ │
|
||||
│ Se cerró sesión en TODOS tus dispositivos. │
|
||||
│ │
|
||||
│ 🕐 25 de Enero, 2026 a las 14:30 CST │
|
||||
│ 📍 Desde: San Francisco, Estados Unidos │
|
||||
│ │
|
||||
│ ¿Fuiste tú? │
|
||||
│ Si reconoces esta actividad, puedes ignorar este email. │
|
||||
│ │
|
||||
│ ¿No fuiste tú? │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Cambiar contraseña inmediatamente │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Para seguridad adicional, considera: │
|
||||
│ • Verificar tu email de recuperación │
|
||||
│ • Revisar actividades recientes │
|
||||
│ • Habilitar autenticación de dos factores │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
Mensaje después de logout:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ✓ Sesión cerrada en todos los dispositivos │
|
||||
│ │
|
||||
│ Se cerrará sesión automáticamente en 2 segundos... │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tareas Técnicas
|
||||
|
||||
### Database (DB)
|
||||
|
||||
- [ ] Tabla `user_sessions` (ya existe en US-AUTH-012)
|
||||
- Verificar columnas: `expires_at`, `status`
|
||||
- Agregar índice: `idx_user_active` (user_id, WHERE status = 'active')
|
||||
|
||||
- [ ] Tabla `session_audit_log`:
|
||||
```sql
|
||||
CREATE TABLE session_audit_log (
|
||||
id UUID PRIMARY KEY,
|
||||
user_id UUID REFERENCES users(id) NOT NULL,
|
||||
action VARCHAR(100) NOT NULL, -- 'logout_global', 'logout_single', etc.
|
||||
session_id UUID REFERENCES user_sessions(id),
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
INDEX idx_user_created (user_id, created_at),
|
||||
INDEX idx_action_created (action, created_at)
|
||||
);
|
||||
```
|
||||
|
||||
### Backend (BE)
|
||||
|
||||
- [ ] Endpoint `POST /api/v1/auth/logout-all`
|
||||
- Autenticación requerida
|
||||
- Parámetro: `confirmation: boolean`
|
||||
- Respuesta: `{ success: boolean, message: string, redirectUrl: string }`
|
||||
- Rate limit: 1 request/min por usuario
|
||||
|
||||
- [ ] Service `LogoutService`
|
||||
- `logoutGlobal(userId: string)`
|
||||
- Buscar todas las sesiones activas del usuario
|
||||
- Invalidar todos los tokens
|
||||
- Marcar sesiones como 'closed'
|
||||
- Registrar en auditoria
|
||||
- Retornar conteo de sesiones cerradas
|
||||
|
||||
- [ ] Invalidación de tokens:
|
||||
- Crear tabla/cache de tokens invalidados (blacklist)
|
||||
- Middleware: verificar si token está en blacklist
|
||||
- Usar Redis para expiración automática
|
||||
|
||||
- [ ] Email notification:
|
||||
- Template: `logout-all.html`
|
||||
- Enviar a dirección registrada
|
||||
- Incluir: fecha, hora, ubicación, link de seguridad
|
||||
- Service: `EmailService.sendLogoutAllNotification()`
|
||||
|
||||
- [ ] Logging y auditoria:
|
||||
- Registrar en `session_audit_log`
|
||||
- Incluir: timestamp, user_id, acción, IP, user_agent
|
||||
- Service: `AuditService.logLogoutAll()`
|
||||
|
||||
- [ ] Tests unitarios (8 casos)
|
||||
- Logout exitoso
|
||||
- Logout con error de DB
|
||||
- Email enviado correctamente
|
||||
- Tokens invalidados
|
||||
- Auditoria registrada
|
||||
- Rate limiting
|
||||
- Concurrencia (múltiples requests simultáneos)
|
||||
- Validación de autenticación
|
||||
|
||||
- [ ] Tests de integración (5 escenarios)
|
||||
- Logout global desde múltiples dispositivos
|
||||
- Verificar 401 con tokens anteriores
|
||||
- Email recibido
|
||||
- Auditoria registrada
|
||||
- Redirección correcta
|
||||
|
||||
### Frontend (FE)
|
||||
|
||||
- [ ] Componente `LogoutAllButton.tsx`
|
||||
- Botón de logout global
|
||||
- En Settings > Security > Sessions
|
||||
|
||||
- [ ] Modal `LogoutAllConfirmationModal.tsx`
|
||||
- Mostrar lista de sesiones que se cerrarán
|
||||
- Advertencia clara
|
||||
- Botones Cancelar/Confirmar
|
||||
|
||||
- [ ] Integración en `Sessions.tsx`
|
||||
- Agregar botón y modal
|
||||
- Manejar respuesta de API
|
||||
- Mostrar mensaje de éxito
|
||||
- Redirigir a login
|
||||
|
||||
- [ ] Servicio `AuthService`
|
||||
- Método `logoutAll(): Promise<{ success: boolean }>`
|
||||
- Llamar a POST /api/v1/auth/logout-all
|
||||
- Limpiar tokens locales
|
||||
- Redirigir a login
|
||||
|
||||
- [ ] Tests con React Testing Library
|
||||
- Renderizar componente
|
||||
- Click en botón
|
||||
- Mostrar modal
|
||||
- Enviar confirmación
|
||||
- Verificar redirección
|
||||
|
||||
### Testing (QA)
|
||||
|
||||
- [ ] E2E: Logout global exitoso
|
||||
- [ ] E2E: Modal de confirmación
|
||||
- [ ] E2E: Email recibido
|
||||
- [ ] E2E: Tokens invalidados en todos los dispositivos
|
||||
- [ ] E2E: Redirección a login
|
||||
- [ ] E2E: Auditoria registrada
|
||||
- [ ] Test de concurrencia: Múltiples logouts globales simultáneos
|
||||
- [ ] Test de seguridad: Tokens antiguos no funcionan
|
||||
- [ ] Performance: Logout global < 500ms
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
- **Bloqueantes:**
|
||||
- US-AUTH-012: Gestión de Sesiones (para tablas y flujos)
|
||||
- Servicio de email configurado
|
||||
|
||||
- **Relacionadas:**
|
||||
- US-AUTH-002: Login genera tokens
|
||||
- US-AUTH-014: Cambio de contraseña por seguridad
|
||||
|
||||
---
|
||||
|
||||
## Definition of Ready (DoR)
|
||||
|
||||
- [ ] Mockups aprobados
|
||||
- [ ] API contract definido
|
||||
- [ ] Estrategia de invalidación de tokens definida
|
||||
- [ ] Template de email diseñado
|
||||
- [ ] Esquema de auditoria revisado
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done (DoD)
|
||||
|
||||
- [ ] Código implementado y revisado
|
||||
- [ ] Tests unitarios con 80%+ cobertura
|
||||
- [ ] Tests de integración pasando
|
||||
- [ ] Tests E2E implementados
|
||||
- [ ] Email de notificación funcional
|
||||
- [ ] Auditoria registrada correctamente
|
||||
- [ ] Rate limiting configurado
|
||||
- [ ] Tokens invalidados en todos los dispositivos
|
||||
- [ ] Redirección a login funcional
|
||||
- [ ] Documentación actualizada
|
||||
- [ ] QA aprobado en staging
|
||||
- [ ] Deploy a producción exitoso
|
||||
|
||||
---
|
||||
|
||||
## Notas Técnicas
|
||||
|
||||
### Estrategia de Invalidación de Tokens
|
||||
|
||||
**Opción 1: Token Blacklist (Recomendado)**
|
||||
```typescript
|
||||
// En Redis, crear entrada para cada token invalidado
|
||||
await redis.setex(
|
||||
`token_blacklist:${jti}`,
|
||||
tokenExpirationTime,
|
||||
'revoked'
|
||||
);
|
||||
|
||||
// En middleware, verificar si token está en blacklist
|
||||
const isBlacklisted = await redis.exists(`token_blacklist:${jti}`);
|
||||
if (isBlacklisted) {
|
||||
throw new UnauthorizedException('Token revoked');
|
||||
}
|
||||
```
|
||||
|
||||
**Opción 2: Token Version (Alternativa)**
|
||||
```typescript
|
||||
// En tabla users, guardar version_token
|
||||
// Al hacer logout global, incrementar version
|
||||
UPDATE users SET token_version = token_version + 1 WHERE id = ?
|
||||
|
||||
// En JWT, incluir token_version
|
||||
const token = generateJWT({
|
||||
sub: user.id,
|
||||
token_version: user.token_version,
|
||||
...
|
||||
});
|
||||
|
||||
// En middleware, validar que token_version coincida
|
||||
const user = await findUser(jti.sub);
|
||||
if (token.token_version !== user.token_version) {
|
||||
throw new UnauthorizedException('Token revoked');
|
||||
}
|
||||
```
|
||||
|
||||
Recomendamos Opción 1 (Redis) por precisión inmediata.
|
||||
|
||||
### Flujo de Logout Global
|
||||
|
||||
```
|
||||
1. Usuario hace click en "Cerrar sesión en todos los dispositivos"
|
||||
↓
|
||||
2. Frontend muestra modal de confirmación
|
||||
↓
|
||||
3. Usuario confirma
|
||||
↓
|
||||
4. Frontend envía POST /api/v1/auth/logout-all
|
||||
↓
|
||||
5. Backend:
|
||||
a. Verifica autenticación
|
||||
b. Busca todas las sesiones del usuario
|
||||
c. Marca todas como 'closed'
|
||||
d. Invalida todos los tokens en Redis/Cache
|
||||
e. Registra acción en auditoria
|
||||
f. Envía email de notificación
|
||||
↓
|
||||
6. Frontend recibe respuesta exitosa
|
||||
↓
|
||||
7. Frontend limpia tokens locales
|
||||
↓
|
||||
8. Frontend muestra mensaje "Sesión cerrada"
|
||||
↓
|
||||
9. Frontend redirige a /login después de 2 segundos
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```typescript
|
||||
// Usar express-rate-limit o similar
|
||||
const logoutAllLimiter = rateLimit({
|
||||
windowMs: 60 * 1000, // 1 minuto
|
||||
max: 1, // 1 request por minuto
|
||||
message: 'Solo puedes hacer logout global 1 vez por minuto'
|
||||
});
|
||||
|
||||
app.post('/api/v1/auth/logout-all', logoutAllLimiter, logoutAllHandler);
|
||||
```
|
||||
|
||||
### Transacción en DB
|
||||
|
||||
```typescript
|
||||
async function logoutGlobal(userId: string) {
|
||||
return await db.$transaction(async (tx) => {
|
||||
// 1. Buscar sesiones
|
||||
const sessions = await tx.userSessions.findMany({
|
||||
where: { user_id: userId, status: 'active' }
|
||||
});
|
||||
|
||||
// 2. Marcar como closed
|
||||
await tx.userSessions.updateMany({
|
||||
where: { user_id: userId, status: 'active' },
|
||||
data: { status: 'closed', expires_at: new Date() }
|
||||
});
|
||||
|
||||
// 3. Registrar auditoria
|
||||
await tx.sessionAuditLog.create({
|
||||
data: {
|
||||
user_id: userId,
|
||||
action: 'logout_global',
|
||||
created_at: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
return sessions.length;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Email Template
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; }
|
||||
.container { max-width: 600px; margin: 0 auto; }
|
||||
.header { background-color: #f44336; color: white; padding: 20px; }
|
||||
.content { padding: 20px; }
|
||||
.warning { background-color: #fff3cd; padding: 10px; border-left: 4px solid #ffc107; }
|
||||
.button { background-color: #2196f3; color: white; padding: 10px 20px; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Sesión cerrada en todos tus dispositivos</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>Hola {{ user_name }},</p>
|
||||
<p>Se cerró sesión en TODOS tus dispositivos.</p>
|
||||
<div class="warning">
|
||||
<strong>Detalles:</strong><br>
|
||||
Fecha: {{ date }} a las {{ time }} {{ timezone }}<br>
|
||||
Ubicación: {{ location }}<br>
|
||||
</div>
|
||||
<p>¿Fuiste tú? Si reconoces esta actividad, puedes ignorar este email.</p>
|
||||
<p>¿No fuiste tú? Considera cambiar tu contraseña inmediatamente:</p>
|
||||
<a href="{{ password_reset_url }}" class="button">Cambiar contraseña</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Requerimientos Relacionados
|
||||
|
||||
- [RF-AUTH-007: Logout Global](../requerimientos/RF-AUTH-007-logout-global.md)
|
||||
|
||||
## Especificaciones Relacionadas
|
||||
|
||||
- [ET-AUTH-002: JWT Tokens](../especificaciones/ET-AUTH-002-jwt.md)
|
||||
- [ET-AUTH-003: Database](../especificaciones/ET-AUTH-003-database.md)
|
||||
- [ET-AUTH-006: Session Management](../especificaciones/ET-AUTH-006-sessions.md)
|
||||
@ -0,0 +1,505 @@
|
||||
---
|
||||
id: "US-AUTH-014"
|
||||
title: "Gestión de Dispositivos"
|
||||
type: "User Story"
|
||||
status: "To Do"
|
||||
priority: "Media"
|
||||
epic: "OQI-001"
|
||||
story_points: 5
|
||||
created_date: "2026-01-25"
|
||||
updated_date: "2026-01-25"
|
||||
---
|
||||
|
||||
# US-AUTH-014: Gestión de Dispositivos
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2026-01-25
|
||||
**Estado:** Pendiente
|
||||
**Story Points:** 5
|
||||
**Prioridad:** P2 (Media)
|
||||
**Épica:** [OQI-001](../_MAP.md)
|
||||
|
||||
---
|
||||
|
||||
## Historia de Usuario
|
||||
|
||||
**Como** usuario de Trading Platform
|
||||
**Quiero** ver y administrar los dispositivos donde tengo sesión activa
|
||||
**Para** mantener control total sobre mi cuenta y detectar accesos no autorizados
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
### AC-001: Listado de dispositivos activos
|
||||
|
||||
**Dado** que estoy autenticado
|
||||
**Cuando** accedo a Configuración > Seguridad > Dispositivos
|
||||
**Entonces** debería ver una lista de todos mis dispositivos con sesión activa
|
||||
|
||||
### AC-002: Información de cada dispositivo
|
||||
|
||||
**Dado** que estoy viendo mi lista de dispositivos
|
||||
**Cuando** observo cada dispositivo listado
|
||||
**Entonces** debería ver:
|
||||
- Icono del navegador (Chrome, Safari, Firefox, etc.)
|
||||
- Sistema operativo (Windows, macOS, iOS, Android, Linux)
|
||||
- Modelo/Nombre del dispositivo (si está disponible)
|
||||
- Ubicación aproximada (Ciudad, País)
|
||||
- Dirección IP
|
||||
- Fecha y hora de última actividad
|
||||
- Badge "Dispositivo actual" si es la sesión activa
|
||||
|
||||
### AC-003: Cierre de sesión individual
|
||||
|
||||
**Dado** que identifico un dispositivo que no reconozco
|
||||
**Cuando** hago click en "Cerrar sesión"
|
||||
**Entonces** debería:
|
||||
1. Ver diálogo de confirmación
|
||||
2. Al confirmar, cerrar sesión en ese dispositivo
|
||||
3. Ver notificación "Dispositivo desconectado"
|
||||
4. El dispositivo desaparece de la lista
|
||||
5. Si ese dispositivo intenta hacer request, recibe 401 Unauthorized
|
||||
|
||||
### AC-004: Información detallada del navegador
|
||||
|
||||
**Dado** que quiero investigar un dispositivo sospechoso
|
||||
**Cuando** hago click en el dispositivo para ver más detalles
|
||||
**Entonces** debería ver:
|
||||
- Versión exacta del navegador
|
||||
- Versión exacta del SO
|
||||
- User Agent (para auditoría)
|
||||
- Historial de actividad en últimas 7 días (gráfico de línea)
|
||||
- Ubicaciones desde las que se accedió
|
||||
- Primer acceso registrado
|
||||
- Último acceso registrado
|
||||
|
||||
### AC-005: Indicador de dispositivo actual
|
||||
|
||||
**Dado** que estoy viendo mis dispositivos
|
||||
**Cuando** identifico el dispositivo desde el cual estoy accediendo
|
||||
**Entonces** debería estar claramente marcado con:
|
||||
- Badge "🟢 Este dispositivo"
|
||||
- Ubicado al inicio de la lista
|
||||
- Color diferenciado (verde o azul)
|
||||
- SIN botón "Cerrar sesión"
|
||||
|
||||
### AC-006: Ordenes de dispositivos
|
||||
|
||||
**Dado** que tengo múltiples dispositivos activos
|
||||
**Cuando** veo la lista
|
||||
**Entonces** debería:
|
||||
- Mostrar "Este dispositivo" primero
|
||||
- Los demás ordenados por última actividad (descendente)
|
||||
- Mostrar fecha/hora relativa (Hace 2 min, Hace 3 horas, etc.)
|
||||
|
||||
### AC-007: Detección de navegadores
|
||||
|
||||
**Dado** que accedo desde diferentes navegadores
|
||||
**Cuando** reviso mis dispositivos
|
||||
**Entonces** debería ver iconos apropiados:
|
||||
- 🌐 Chrome
|
||||
- 🔴 Firefox
|
||||
- 🧭 Safari
|
||||
- 🌌 Edge
|
||||
- 📱 Mobile browsers
|
||||
- ❓ Desconocido
|
||||
|
||||
### AC-008: Datos de sesión activa
|
||||
|
||||
**Dado** que tengo una sesión activa
|
||||
**Cuando** veo los detalles del dispositivo
|
||||
**Entonces** debería ver:
|
||||
- Token actual (primeros 10 caracteres) para referencia
|
||||
- Fecha de creación de la sesión
|
||||
- Próxima fecha de expiración
|
||||
- Último refresh de token (si aplica)
|
||||
|
||||
### AC-009: Patrón de actividad
|
||||
|
||||
**Dado** que quiero monitorear actividad
|
||||
**Cuando** hago click en "Ver actividad" de un dispositivo
|
||||
**Entonces** debería ver:
|
||||
- Gráfico de actividad por hora/día
|
||||
- Número de requests por período
|
||||
- Últimas 10 acciones (endpoint, timestamp)
|
||||
- Horarios de pico de uso
|
||||
|
||||
### AC-010: Alertas de nuevos dispositivos
|
||||
|
||||
**Dado** que inicio sesión desde un nuevo dispositivo
|
||||
**Cuando** completo el login
|
||||
**Entonces** debería:
|
||||
1. Ver notificación in-app "Nuevo dispositivo conectado"
|
||||
2. Recibir email con detalles: navegador, SO, ubicación, IP
|
||||
3. Link en email para cerrar sesión inmediatamente si no fui yo
|
||||
|
||||
---
|
||||
|
||||
## Mockup
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Configuración > Seguridad > Dispositivos │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Dispositivos activos │
|
||||
│ Administra dónde está abierta tu sesión │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 🌐 Chrome en Windows 🟢 Este │ │
|
||||
│ │ dispositivo │ │
|
||||
│ │ San Francisco, Estados Unidos • 201.45.67.89 │ │
|
||||
│ │ Última actividad: Hace 2 minutos │ │
|
||||
│ │ │ │
|
||||
│ │ [Ver más detalles] │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 📱 Safari en iPhone [Cerrar] │ │
|
||||
│ │ │ │
|
||||
│ │ Ciudad de México, México • 189.203.45.12 │ │
|
||||
│ │ Última actividad: Hace 3 horas │ │
|
||||
│ │ │ │
|
||||
│ │ [Ver más detalles] │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 🔴 Firefox en Linux [Cerrar] │ │
|
||||
│ │ │ │
|
||||
│ │ Madrid, España • 85.123.45.67 │ │
|
||||
│ │ Última actividad: Hace 2 días │ │
|
||||
│ │ │ │
|
||||
│ │ [Ver más detalles] │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ──────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ Información de seguridad │
|
||||
│ • Total de sesiones activas: 3 │
|
||||
│ • Última sesión nueva: 3 horas atrás │
|
||||
│ • Si ves un dispositivo desconocido, ciérralo │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
|
||||
Modal de detalles:
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ Detalles del dispositivo │
|
||||
│ │
|
||||
│ 🌐 Chrome 130.0.0.0 │
|
||||
│ Windows 11 (Build 23631) │
|
||||
│ │
|
||||
│ Ubicación: │
|
||||
│ San Francisco, Estados Unidos (37.7749°, -122.4194°) │
|
||||
│ │
|
||||
│ IP: 201.45.67.89 │
|
||||
│ ISP: Verizon Communications │
|
||||
│ │
|
||||
│ Sesión: │
|
||||
│ Creada: 25 Ene 2026, 14:30 CST │
|
||||
│ Última actividad: Hace 2 minutos │
|
||||
│ Próxima expiración: 24 Feb 2026, 14:30 CST │
|
||||
│ Token: abc123def... │
|
||||
│ │
|
||||
│ Actividad (últimas 24 horas): │
|
||||
│ [Gráfico de línea con actividad por hora] │
|
||||
│ │
|
||||
│ Últimos requests: │
|
||||
│ 14:45 - GET /api/portfolio │
|
||||
│ 14:43 - GET /api/market/BTC │
|
||||
│ 14:41 - POST /api/orders │
|
||||
│ │
|
||||
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
||||
│ │ Volver │ │ Cerrar sesión │ │
|
||||
│ └──────────────────────┘ └──────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
|
||||
Modal de confirmación:
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ⚠️ Cerrar sesión en dispositivo? │
|
||||
│ │
|
||||
│ Se cerrará la sesión de: │
|
||||
│ 📱 Safari en iPhone │
|
||||
│ Ciudad de México, México │
|
||||
│ │
|
||||
│ Esta acción no se puede deshacer. El dispositivo │
|
||||
│ deberá iniciar sesión nuevamente. │
|
||||
│ │
|
||||
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
||||
│ │ Cancelar │ │ Cerrar sesión │ │
|
||||
│ └──────────────────────┘ └──────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
|
||||
Email de nuevo dispositivo:
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ De: Trading Platform Security <security@trading.com> │
|
||||
│ Asunto: Nuevo dispositivo conectado a tu cuenta │
|
||||
│ │
|
||||
│ Hola Juan, │
|
||||
│ │
|
||||
│ Se inició sesión en tu cuenta desde un nuevo │
|
||||
│ dispositivo: │
|
||||
│ │
|
||||
│ 🌐 Chrome en Windows │
|
||||
│ 📍 San Francisco, Estados Unidos │
|
||||
│ 🕐 25 de Enero, 2026 a las 14:30 CST │
|
||||
│ 🌐 IP: 201.45.67.89 │
|
||||
│ │
|
||||
│ ¿Fuiste tú? │
|
||||
│ Si reconoces esta actividad, puedes ignorar este │
|
||||
│ email. │
|
||||
│ │
|
||||
│ ¿No fuiste tú? │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ Cerrar sesión en este dispositivo │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Ver todos mis dispositivos: │
|
||||
│ [https://trading.example.com/settings/devices] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tareas Técnicas
|
||||
|
||||
### Database (DB)
|
||||
|
||||
- [ ] Tabla `user_sessions` (ver US-AUTH-012):
|
||||
- Verificar campos: device_type, device_name, browser, os, ip_address, location_city, location_country
|
||||
- Agregar si falta: user_agent (TEXT para auditoría)
|
||||
- Agregar si falta: last_activity_details (JSONB para tracking fino)
|
||||
|
||||
- [ ] Tabla `device_activity_log`:
|
||||
```sql
|
||||
CREATE TABLE device_activity_log (
|
||||
id UUID PRIMARY KEY,
|
||||
session_id UUID REFERENCES user_sessions(id) ON DELETE CASCADE,
|
||||
endpoint VARCHAR(255),
|
||||
method VARCHAR(10), -- GET, POST, PUT, DELETE, etc.
|
||||
status_code INTEGER,
|
||||
response_time_ms INTEGER,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
INDEX idx_session_created (session_id, created_at),
|
||||
INDEX idx_created (created_at)
|
||||
);
|
||||
```
|
||||
|
||||
### Backend (BE)
|
||||
|
||||
- [ ] Endpoint `GET /api/auth/devices`
|
||||
- Listar todos los dispositivos activos del usuario
|
||||
- Marcar dispositivo actual
|
||||
- Ordenar por última actividad DESC
|
||||
- Incluir: device_type, device_name, os, browser, ip, location, last_activity
|
||||
- Response: HTTP 200
|
||||
|
||||
- [ ] Endpoint `GET /api/auth/devices/:id`
|
||||
- Obtener detalles completos de un dispositivo
|
||||
- Incluir: user_agent, histórico de actividad (7 días), primero/último acceso
|
||||
- Incluir: gráfico de actividad por hora
|
||||
- Incluir: últimos 10 requests
|
||||
- Response: HTTP 200 o 404
|
||||
|
||||
- [ ] Endpoint `DELETE /api/auth/devices/:id`
|
||||
- Cerrar sesión en dispositivo específico
|
||||
- Invalidar tokens asociados
|
||||
- Loguear la acción en audit trail
|
||||
- Enviar email al usuario
|
||||
- Response: HTTP 204 o 401/404
|
||||
|
||||
- [ ] Service `DeviceService`
|
||||
- `getAllDevices(userId)`
|
||||
- `getDeviceDetails(userId, deviceId)`
|
||||
- `terminateDevice(userId, deviceId)`
|
||||
- `isCurrentDevice(userId, sessionId)`
|
||||
- `getDeviceActivitySummary(deviceId, days)`
|
||||
- `getLastNRequests(deviceId, limit)`
|
||||
|
||||
- [ ] Service `DeviceDetectionService` (mejorado)
|
||||
- `parseUserAgent()` - ya existe en US-AUTH-012
|
||||
- `extractBrowserIcon()` - nuevo
|
||||
- `getDeviceFingerprint()` - nuevo para consistencia
|
||||
|
||||
- [ ] Logging de actividad
|
||||
- Hook/middleware para loguear cada request en `device_activity_log`
|
||||
- Incluir: endpoint, método, status code, tiempo de respuesta
|
||||
- Asociar a session_id actual
|
||||
|
||||
- [ ] Email de notificación
|
||||
- Template "Nuevo dispositivo conectado"
|
||||
- Incluir link para cerrar sesión desde email
|
||||
- Usar SendGrid o similar
|
||||
|
||||
- [ ] Tests unitarios (12 casos)
|
||||
- [ ] Tests de integración (8 escenarios)
|
||||
|
||||
### Frontend (FE)
|
||||
|
||||
- [ ] Página `Settings/Security/Devices.tsx` (nueva)
|
||||
- [ ] Componente `DeviceCard.tsx` (nuevo)
|
||||
- [ ] Componente `DeviceDetailsModal.tsx` (nuevo)
|
||||
- [ ] Modal de confirmación de cierre
|
||||
- [ ] Gráfico de actividad (line chart con lightweight-charts)
|
||||
- [ ] Tabla de últimos requests
|
||||
- [ ] Función para obtener icono de navegador
|
||||
- [ ] Badge "Este dispositivo" con lógica de sesión actual
|
||||
- [ ] Ordenamiento automático por última actividad
|
||||
- [ ] Tests con React Testing Library
|
||||
|
||||
### Testing (QA)
|
||||
|
||||
- [ ] E2E: Ver lista de dispositivos
|
||||
- [ ] E2E: Ver detalles de dispositivo
|
||||
- [ ] E2E: Cerrar sesión en dispositivo individual
|
||||
- [ ] E2E: Confirmar cierre desde modal
|
||||
- [ ] E2E: Dispositivo desaparece después de cerrar
|
||||
- [ ] E2E: Email se envía en nuevo dispositivo
|
||||
- [ ] Performance: Lista de dispositivos < 300ms
|
||||
- [ ] Performance: Detalles de dispositivo < 500ms
|
||||
- [ ] Seguridad: No mostrar tokens completos
|
||||
- [ ] Seguridad: Solo usuario propietario puede ver/cerrar
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
- **Bloqueantes:**
|
||||
- US-AUTH-012: Gestión de Sesiones (tabla user_sessions)
|
||||
- Sistema de geolocalización (de US-AUTH-012)
|
||||
|
||||
- **Relacionadas:**
|
||||
- US-AUTH-002: Login (sesión inicial)
|
||||
- US-AUTH-013: Cambio de contraseña (seguridad complementaria)
|
||||
|
||||
---
|
||||
|
||||
## Definition of Ready (DoR)
|
||||
|
||||
- [ ] API contract definido y aprobado
|
||||
- [ ] Mockups validados con UX
|
||||
- [ ] Esquema de BD detallado
|
||||
- [ ] Servicio de email configurado
|
||||
- [ ] Estructura de logs de actividad definida
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done (DoD)
|
||||
|
||||
- [ ] Endpoint GET /api/auth/devices implementado
|
||||
- [ ] Endpoint GET /api/auth/devices/:id implementado
|
||||
- [ ] Endpoint DELETE /api/auth/devices/:id implementado
|
||||
- [ ] Tabla device_activity_log creada
|
||||
- [ ] Logging de actividad funcional
|
||||
- [ ] Página Settings/Security/Devices.tsx implementada
|
||||
- [ ] Componentes DeviceCard y DeviceDetailsModal implementados
|
||||
- [ ] Gráfico de actividad funcional
|
||||
- [ ] Email de notificación configurable
|
||||
- [ ] Tests unitarios con 80%+ cobertura
|
||||
- [ ] Tests de integración pasando
|
||||
- [ ] Tests E2E implementados
|
||||
- [ ] Documentación actualizada
|
||||
- [ ] QA aprobado en staging
|
||||
- [ ] Deploy a producción exitoso
|
||||
|
||||
---
|
||||
|
||||
## Notas Técnicas
|
||||
|
||||
### Device Identification
|
||||
|
||||
```typescript
|
||||
interface DeviceInfo {
|
||||
id: string; // session_id
|
||||
browser: string; // "Chrome 130"
|
||||
os: string; // "Windows 11"
|
||||
device_type: "desktop" | "mobile" | "tablet" | "unknown";
|
||||
device_name: string; // "Chrome on Windows"
|
||||
ip_address: string;
|
||||
location: {
|
||||
city: string;
|
||||
country: string;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
};
|
||||
user_agent: string;
|
||||
is_current: boolean;
|
||||
last_activity: Date;
|
||||
created_at: Date;
|
||||
expires_at: Date;
|
||||
}
|
||||
```
|
||||
|
||||
### Browser Icons Mapping
|
||||
|
||||
```typescript
|
||||
const BROWSER_ICONS = {
|
||||
'Chrome': '🌐',
|
||||
'Firefox': '🔴',
|
||||
'Safari': '🧭',
|
||||
'Edge': '🌌',
|
||||
'Opera': '⭕',
|
||||
'Mobile': '📱',
|
||||
'Unknown': '❓'
|
||||
};
|
||||
```
|
||||
|
||||
### Activity Graph Data
|
||||
|
||||
```typescript
|
||||
interface ActivityDataPoint {
|
||||
hour: number; // 0-23
|
||||
date: string; // "2026-01-25"
|
||||
request_count: number;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
// Graficar: últimas 24 horas con actividad por hora
|
||||
// Líneas suave, mostrar puntos de máxima actividad
|
||||
```
|
||||
|
||||
### Current Device Detection
|
||||
|
||||
El dispositivo "actual" se detecta comparando:
|
||||
1. Session token actual en localStorage/cookie
|
||||
2. Session ID del request
|
||||
3. El primer dispositivo con `expires_at > now()` y session_id === actual es "Este dispositivo"
|
||||
|
||||
### Email Trigger
|
||||
|
||||
Se dispara email de "Nuevo dispositivo" cuando:
|
||||
```typescript
|
||||
const isNewDevice = !existingSession ||
|
||||
(existingSession.device_name !== currentDevice.device_name) ||
|
||||
(existingSession.ip_address !== currentDevice.ip_address);
|
||||
```
|
||||
|
||||
### Audit Trail
|
||||
|
||||
Cada acción se registra:
|
||||
```
|
||||
- Dispositivo X cerrado por usuario Y
|
||||
- Timestamp: 2026-01-25 14:30:00
|
||||
- Reason: manual_close
|
||||
- IP del usuario que cerró: admin_ip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Requerimientos Relacionados
|
||||
|
||||
- [RF-AUTH-007: Gestión de Dispositivos](../requerimientos/RF-AUTH-007-devices.md)
|
||||
|
||||
## Especificaciones Relacionadas
|
||||
|
||||
- [ET-AUTH-002: JWT Tokens](../especificaciones/ET-AUTH-002-jwt.md)
|
||||
- [ET-AUTH-003: Database](../especificaciones/ET-AUTH-003-database.md)
|
||||
- [ET-AUTH-006: Session Management](../especificaciones/ET-AUTH-006-sessions.md)
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,442 @@
|
||||
---
|
||||
id: "US-ML-008"
|
||||
title: "Ver Señal Ensemble"
|
||||
type: "User Story"
|
||||
status: "Pending"
|
||||
priority: "Alta"
|
||||
epic: "OQI-006"
|
||||
project: "trading-platform"
|
||||
story_points: 5
|
||||
created_date: "2026-01-25"
|
||||
updated_date: "2026-01-25"
|
||||
---
|
||||
|
||||
# US-ML-008: Ver Señal Ensemble
|
||||
|
||||
## Metadata
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | US-ML-008 |
|
||||
| **Épica** | OQI-006 - Señales ML y Predicciones |
|
||||
| **Módulo** | ml-signals |
|
||||
| **Prioridad** | P0 (Alta) |
|
||||
| **Story Points** | 5 |
|
||||
| **Sprint** | Sprint 8 |
|
||||
| **Estado** | Pendiente |
|
||||
| **Asignado a** | Por asignar |
|
||||
|
||||
---
|
||||
|
||||
## Historia de Usuario
|
||||
|
||||
**Como** trader/inversor,
|
||||
**quiero** ver la señal combinada (ensemble) de múltiples modelos ML con sus contribuciones individuales,
|
||||
**para** tomar decisiones más informadas y confiar en predicciones robustas de múltiples fuentes.
|
||||
|
||||
## Descripción Detallada
|
||||
|
||||
El usuario debe poder ver una vista integrada que combina las predicciones de múltiples modelos ML (LSTM, RandomForest, SVM, etc.) en una señal consolidada (ensemble). La vista debe mostrar:
|
||||
|
||||
1. **Señal Ensemble Principal:** La decisión combinada (BUY/SELL/HOLD) con confianza agregada
|
||||
2. **Confianza Combinada:** Porcentaje agregado considerando pesos de cada modelo
|
||||
3. **Contribuciones Individuales:** Lista de modelos participantes con sus predicciones y pesos
|
||||
4. **Scoring Detallado:** Score de cada modelo, peso asignado, contribución a la decisión final
|
||||
5. **Votación de Modelos:** Cuántos modelos votaron por cada acción
|
||||
6. **Consenso:** Nivel de acuerdo entre modelos
|
||||
|
||||
---
|
||||
|
||||
## Mockups/Wireframes
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ ENSEMBLE SIGNAL BTCUSDT │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ENSEMBLE DECISION │ │
|
||||
│ ├────────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ Signal: 🟢 BUY │ │
|
||||
│ │ Confidence: ████████░ 82% │ │
|
||||
│ │ Score: 8.2/10 │ │
|
||||
│ │ Consensus: STRONG (4/5 models agree) │ │
|
||||
│ │ │ │
|
||||
│ │ Entry Price Avg: $89,450.00 │ │
|
||||
│ │ TP1: $89,650 (+0.22%) | TP2: $89,850 (+0.45%) │ │
|
||||
│ │ TP3: $90,050 (+0.67%) | SL: $89,150 (-0.33%) │ │
|
||||
│ │ │ │
|
||||
│ │ Generated: 2026-01-25 10:30:15 UTC │ │
|
||||
│ │ Expires: 2026-01-25 14:30:15 UTC (4h horizon) │ │
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ MODEL CONTRIBUTIONS │ │
|
||||
│ ├────────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ [Model Distribution] │ │
|
||||
│ │ │ │
|
||||
│ │ 🟢 BUY: 4 models (80%) ████████░░ │ │
|
||||
│ │ 🔴 SELL: 1 model (20%) ██░░░░░░░░ │ │
|
||||
│ │ 🔵 HOLD: 0 models (0%) ░░░░░░░░░░ │ │
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ DETAILED MODEL VOTING │ │
|
||||
│ ├────────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ Model Name Prediction Score Weight Contribution│ │
|
||||
│ │ ─────────────────────────────────────────────────────────│ │
|
||||
│ │ ✓ LSTM 🟢 BUY 87% 25% 21.75% │ │
|
||||
│ │ ✓ RandomForest 🟢 BUY 85% 30% 25.50% │ │
|
||||
│ │ ✓ SVM 🟢 BUY 78% 20% 15.60% │ │
|
||||
│ │ ✓ Transformer 🟢 BUY 81% 15% 12.15% │ │
|
||||
│ │ ✗ GradientBoosting 🔴 SELL 72% 10% -7.20%* │ │
|
||||
│ │ │ │
|
||||
│ │ *Negative contribution indicates minority prediction │ │
|
||||
│ │ │ │
|
||||
│ │ CONSENSUS ANALYSIS: │ │
|
||||
│ │ • 4 out of 5 models predict BUY (strong agreement) │ │
|
||||
│ │ • Confidence scores range from 72% to 87% (tight) │ │
|
||||
│ │ • 1 outlier (GradientBoosting) predicts SELL │ │
|
||||
│ │ • Overall consensus strength: 80% (STRONG) │ │
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ HISTORICAL MODEL PERFORMANCE │ │
|
||||
│ ├────────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ Model Name Accuracy Win Rate Last 7d Trend │ │
|
||||
│ │ ─────────────────────────────────────────────────────────│ │
|
||||
│ │ LSTM 84.2% 71.5% 72% ↑ Good │ │
|
||||
│ │ RandomForest 82.1% 68.9% 69% ↑ Good │ │
|
||||
│ │ SVM 79.8% 65.3% 66% → Stable │ │
|
||||
│ │ Transformer 81.5% 70.2% 71% ↑ Good │ │
|
||||
│ │ GradientBoosting 76.2% 61.4% 62% ↓ Declining│ │
|
||||
│ │ │ │
|
||||
│ │ Ensemble (Combined) 85.6% 75.1% 76% ↑ Excellent│ │
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [View Technical Context] [Compare Models] [View Signal History] │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ MODEL COMPARISON CHART │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Prediction Heatmap: │
|
||||
│ │
|
||||
│ LSTM RF SVM TRF GB ENS │
|
||||
│ BTCUSDT [87%] [85%][78%][81%][72%][82%] 🟢 BUY │
|
||||
│ ETHUSDT [84%] [86%][75%][80%][70%][81%] 🟢 BUY │
|
||||
│ XRPUSDT [81%] [79%][72%][76%][68%][77%] 🟢 BUY │
|
||||
│ SOLUSDT [76%] [74%][69%][71%][65%][72%] 🟡 HOLD (Weak) │
|
||||
│ │
|
||||
│ Legend: ENS = Ensemble Vote │
|
||||
│ │
|
||||
│ Consensus Strength Graph: │
|
||||
│ │
|
||||
│ 100% │ │
|
||||
│ │ ╱─╲ │
|
||||
│ 80% │ ╱─ ╲╱╲ │
|
||||
│ │ ╱─ ╲─╲ │
|
||||
│ 60% │╱─ ╲─── │
|
||||
│ │────────────────────► time │
|
||||
│ 40% │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
**Escenario 1: Ver señal ensemble**
|
||||
```gherkin
|
||||
DADO que el usuario está autenticado en Trading Platform
|
||||
CUANDO navega a "ML Signals > Ensemble" para un símbolo (ej: BTCUSDT)
|
||||
ENTONCES ve la señal ensemble con:
|
||||
- Decisión principal (BUY/SELL/HOLD)
|
||||
- Confianza combinada (%)
|
||||
- Score de 0 a 10
|
||||
- Nivel de consenso (STRONG/MODERATE/WEAK)
|
||||
- Número de modelos votando por cada acción
|
||||
Y se muestra el timestamp de generación y expiración
|
||||
```
|
||||
|
||||
**Escenario 2: Ver contribuciones de modelos**
|
||||
```gherkin
|
||||
DADO que el usuario está viendo una señal ensemble
|
||||
CUANDO expande la sección "Model Contributions"
|
||||
ENTONCES ve una tabla con:
|
||||
- Nombre de cada modelo
|
||||
- Predicción individual (BUY/SELL/HOLD)
|
||||
- Score/confianza de cada modelo (%)
|
||||
- Peso asignado a cada modelo (%)
|
||||
- Contribución calculada al resultado final
|
||||
Y las filas están ordenadas por peso (mayor a menor)
|
||||
```
|
||||
|
||||
**Escenario 3: Ver votación de modelos**
|
||||
```gherkin
|
||||
DADO que existen 5 modelos en el ensemble
|
||||
CUANDO veo la distribución de votos
|
||||
ENTONCES se muestra:
|
||||
- Gráfico de barras: cuántos modelos votaron por cada acción
|
||||
- Porcentaje: 80% BUY, 20% SELL, 0% HOLD
|
||||
- Indicador visual de consenso (fuerte/moderado/débil)
|
||||
- Descripción: "4 out of 5 models agree on BUY"
|
||||
```
|
||||
|
||||
**Escenario 4: Ver performance histórico de modelos**
|
||||
```gherkin
|
||||
DADO que el usuario quiere evaluar fiabilidad de cada modelo
|
||||
CUANDO expande "Historical Model Performance"
|
||||
ENTONCES ve para cada modelo:
|
||||
- Accuracy (%)
|
||||
- Win Rate en los últimos 7 días (%)
|
||||
- Tendencia (↑/→/↓)
|
||||
- Comparación con ensemble
|
||||
```
|
||||
|
||||
**Escenario 5: Detectar outliers**
|
||||
```gherkin
|
||||
DADO que 1 modelo predice diferente al ensemble (ej: SELL vs BUY)
|
||||
CUANDO veo la tabla de contribuciones
|
||||
ENTONCES:
|
||||
- Se destaca visualmente con icono ✗ (en lugar de ✓)
|
||||
- Se muestra con color diferente (rojo vs verde)
|
||||
- Se incluye nota: "Minority prediction"
|
||||
- Contribución se resta del total (negativa)
|
||||
```
|
||||
|
||||
**Escenario 6: Comparar modelos lado a lado**
|
||||
```gherkin
|
||||
DADO que quiero evaluar predicciones de todos los modelos
|
||||
CUANDO hago click en "Compare Models"
|
||||
ENTONCES se abre una vista con:
|
||||
- Heatmap de predicciones por símbolo
|
||||
- Scores de cada modelo lado a lado
|
||||
- Tendencia de consenso en tiempo (gráfico)
|
||||
- Botón para exportar comparación
|
||||
```
|
||||
|
||||
## Criterios Adicionales
|
||||
|
||||
- [ ] Debe cargar en menos de 1.5 segundos
|
||||
- [ ] Soportar 5-10 modelos simultáneamente
|
||||
- [ ] Actualizar automáticamente cada 30 segundos (si hay nuevas señales)
|
||||
- [ ] Ser responsive en mobile
|
||||
- [ ] Mostrar claramente cuál es el modelo más confiable
|
||||
- [ ] Detectar y resaltar predicciones outliers
|
||||
- [ ] Permitir ponderar dinámicamente los modelos (admin)
|
||||
|
||||
---
|
||||
|
||||
## Tareas Técnicas
|
||||
|
||||
**Backend:**
|
||||
- [ ] BE-ML-016: Crear endpoint GET /api/ml/ensemble/:symbol
|
||||
- [ ] BE-ML-017: Implementar cálculo de confianza combinada (weighted average)
|
||||
- [ ] BE-ML-018: Implementar votación y consenso de modelos
|
||||
- [ ] BE-ML-019: Obtener scores individuales de cada modelo
|
||||
- [ ] BE-ML-020: Calcular performance histórico de modelos
|
||||
- [ ] BE-ML-021: Endpoint GET /api/ml/ensemble/:symbol/models (detalle)
|
||||
- [ ] BE-ML-022: Endpoint GET /api/ml/ensemble/comparison (heatmap)
|
||||
|
||||
**Frontend:**
|
||||
- [ ] FE-ML-035: Crear componente `EnsembleSignal.tsx`
|
||||
- [ ] FE-ML-036: Crear componente `ModelVoting.tsx`
|
||||
- [ ] FE-ML-037: Crear componente `ModelContributions.tsx` (tabla)
|
||||
- [ ] FE-ML-038: Crear componente `ConsensusIndicator.tsx`
|
||||
- [ ] FE-ML-039: Crear componente `ModelPerformance.tsx`
|
||||
- [ ] FE-ML-040: Crear componente `ModelComparison.tsx` (heatmap)
|
||||
- [ ] FE-ML-041: Implementar auto-refresh cada 30 segundos
|
||||
- [ ] FE-ML-042: Agregar indicadores visuales para outliers
|
||||
|
||||
**Tests:**
|
||||
- [ ] TEST-ML-016: Test de cálculo de confianza combinada
|
||||
- [ ] TEST-ML-017: Test de votación de modelos
|
||||
- [ ] TEST-ML-018: Test E2E de vista ensemble
|
||||
- [ ] TEST-ML-019: Test de detección de outliers
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
**Depende de:**
|
||||
- [ ] RF-ML-003: Múltiples modelos ML entrenados (LSTM, RF, SVM, Transformer, GB)
|
||||
- [ ] RF-ML-004: Sistema de pesos y scoring de modelos
|
||||
- [ ] US-ML-001: Ver señal básica - Estado: Completado
|
||||
|
||||
**Bloquea:**
|
||||
- [ ] US-ML-009: Configurar ensemble (admin puede cambiar pesos)
|
||||
- [ ] US-ML-010: Histórico de ensemble signals
|
||||
|
||||
---
|
||||
|
||||
## Notas Técnicas
|
||||
|
||||
**Endpoints involucrados:**
|
||||
| Método | Endpoint | Descripción |
|
||||
|--------|----------|-------------|
|
||||
| GET | /api/ml/ensemble/:symbol | Obtener señal ensemble actual |
|
||||
| GET | /api/ml/ensemble/:symbol/models | Detalle de cada modelo |
|
||||
| GET | /api/ml/ensemble/comparison | Heatmap de predicciones |
|
||||
| GET | /api/ml/ensemble/:symbol/performance | Performance histórico |
|
||||
|
||||
**Query Parameters - Ensemble Signal:**
|
||||
```
|
||||
GET /api/ml/ensemble/BTCUSDT?
|
||||
include_performance=true&
|
||||
include_comparison=false
|
||||
```
|
||||
|
||||
**Response esperado:**
|
||||
```typescript
|
||||
interface EnsembleSignalResponse {
|
||||
ensemble: {
|
||||
symbol: string;
|
||||
action: 'BUY' | 'SELL' | 'HOLD';
|
||||
confidence: number; // weighted average, 0-100
|
||||
score: number; // 0-10
|
||||
consensus_strength: 'STRONG' | 'MODERATE' | 'WEAK';
|
||||
timestamp: string;
|
||||
expires_at: string;
|
||||
|
||||
entry_price: number;
|
||||
tp1: number;
|
||||
tp2: number;
|
||||
tp3: number;
|
||||
sl: number;
|
||||
};
|
||||
|
||||
model_votes: {
|
||||
buy_count: number;
|
||||
sell_count: number;
|
||||
hold_count: number;
|
||||
total_models: number;
|
||||
buy_percentage: number;
|
||||
sell_percentage: number;
|
||||
hold_percentage: number;
|
||||
};
|
||||
|
||||
models: ModelContribution[];
|
||||
|
||||
consensus_analysis: {
|
||||
all_agree: boolean;
|
||||
minority_models: string[];
|
||||
agreement_percentage: number;
|
||||
description: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ModelContribution {
|
||||
model_id: string;
|
||||
model_name: string;
|
||||
prediction: 'BUY' | 'SELL' | 'HOLD';
|
||||
score: number; // 0-100
|
||||
confidence: number; // 0-100
|
||||
weight: number; // 0-100, suma con otros = 100
|
||||
contribution: number; // score * weight
|
||||
is_outlier: boolean;
|
||||
|
||||
performance?: ModelPerformance;
|
||||
}
|
||||
|
||||
interface ModelPerformance {
|
||||
accuracy: number; // last 100 predictions
|
||||
win_rate: number; // last 7 days
|
||||
total_signals: number;
|
||||
correct_signals: number;
|
||||
trend: 'UP' | 'STABLE' | 'DOWN';
|
||||
last_updated: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Cálculos:**
|
||||
```typescript
|
||||
// Confianza combinada (weighted average)
|
||||
ensemble_confidence = sum(model.score * model.weight) / 100
|
||||
|
||||
// Votación
|
||||
buy_percentage = (buy_count / total_models) * 100
|
||||
|
||||
// Contribución de cada modelo
|
||||
contribution = model.score * model.weight / 100
|
||||
|
||||
// Consenso
|
||||
consensus_strength = all_models_agree ? 'STRONG' :
|
||||
buy_percentage > 60 ? 'MODERATE' : 'WEAK'
|
||||
```
|
||||
|
||||
**Componentes UI:**
|
||||
- `EnsembleSignal`: Container principal
|
||||
- `EnsembleHeader`: Decisión + confianza + consenso
|
||||
- `ModelVoting`: Distribución de votos (barras)
|
||||
- `ModelContributions`: Tabla detallada
|
||||
- `ConsensusIndicator`: Visual de acuerdo
|
||||
- `ModelPerformance`: Stats históricos
|
||||
- `ModelComparison`: Heatmap
|
||||
- `OutlierBadge`: Indicador de predicción minoritaria
|
||||
|
||||
**Estado (Zustand):**
|
||||
```typescript
|
||||
interface EnsembleStore {
|
||||
ensemble: EnsembleSignalResponse | null;
|
||||
selectedSymbol: string;
|
||||
isLoading: boolean;
|
||||
lastUpdated: string;
|
||||
autoRefreshInterval: number; // 30000 ms
|
||||
|
||||
fetchEnsemble: (symbol: string) => Promise<void>;
|
||||
setAutoRefresh: (enabled: boolean) => void;
|
||||
compareModels: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Definition of Ready (DoR)
|
||||
|
||||
- [x] Historia claramente escrita (quién, qué, por qué)
|
||||
- [x] Criterios de aceptación definidos
|
||||
- [x] Story points estimados
|
||||
- [x] Dependencias identificadas
|
||||
- [x] Modelos ML disponibles (LSTM, RF, SVM, Transformer, GB)
|
||||
- [x] Diseño/mockup disponible
|
||||
|
||||
## Definition of Done (DoD)
|
||||
|
||||
- [ ] Código implementado según criterios
|
||||
- [ ] Tests unitarios escritos y pasando (>80% coverage)
|
||||
- [ ] Tests E2E pasando
|
||||
- [ ] Code review aprobado
|
||||
- [ ] Documentación actualizada
|
||||
- [ ] QA aprobado en staging
|
||||
- [ ] Auto-refresh funciona sin memory leaks
|
||||
- [ ] Performance: carga < 1.5s
|
||||
- [ ] Mobile responsive
|
||||
- [ ] Outliers detectados y resaltados correctamente
|
||||
- [ ] Desplegado en producción
|
||||
|
||||
---
|
||||
|
||||
## Historial de Cambios
|
||||
|
||||
| Fecha | Cambio | Autor |
|
||||
|-------|--------|-------|
|
||||
| 2026-01-25 | Creación | Requirements-Analyst |
|
||||
|
||||
---
|
||||
|
||||
**Creada por:** Requirements-Analyst
|
||||
**Fecha:** 2026-01-25
|
||||
**Última actualización:** 2026-01-25
|
||||
@ -0,0 +1,495 @@
|
||||
---
|
||||
id: "US-ML-009"
|
||||
title: "Ver Análisis ICT"
|
||||
type: "User Story"
|
||||
status: "Pending"
|
||||
priority: "Alta"
|
||||
epic: "OQI-006"
|
||||
project: "trading-platform"
|
||||
story_points: 5
|
||||
created_date: "2026-01-25"
|
||||
updated_date: "2026-01-25"
|
||||
---
|
||||
|
||||
# US-ML-009: Ver Análisis ICT
|
||||
|
||||
## Metadata
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | US-ML-009 |
|
||||
| **Épica** | OQI-006 - Señales ML y Predicciones |
|
||||
| **Módulo** | ml-signals |
|
||||
| **Prioridad** | P0 (Alta) |
|
||||
| **Story Points** | 5 |
|
||||
| **Sprint** | Sprint 8 |
|
||||
| **Estado** | Pendiente |
|
||||
| **Asignado a** | Por asignar |
|
||||
|
||||
---
|
||||
|
||||
## Historia de Usuario
|
||||
|
||||
**Como** trader/inversor,
|
||||
**quiero** ver el análisis ICT (Inner Circle Trader) que incluye zonas de liquidez, fair value gaps, y order blocks,
|
||||
**para** identificar puntos de entrada/salida de alto potencial basados en la metodología ICT.
|
||||
|
||||
## Descripción Detallada
|
||||
|
||||
El usuario debe poder visualizar en el chart de precios un análisis completo de la metodología ICT que incluya:
|
||||
|
||||
1. **Zonas de Liquidez (Liquidity Zones)**: Áreas donde se acumulan órdenes de compra/venta
|
||||
2. **Fair Value Gaps (FVGs)**: Brechas de precio donde no hay transacciones (gaps de aire)
|
||||
3. **Order Blocks (OB)**: Bloques de órdenes que actúan como soporte/resistencia
|
||||
4. **Puntos de Interés (POI)**: Zonas clave de reversión y manipulación de precios
|
||||
|
||||
Cada elemento debe ser interactivo mostrando detalles como tamaño de la zona, potencia, fechas de formación, y probabilidad de inversión de precio.
|
||||
|
||||
## Mockups/Wireframes
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ ICT ANALYSIS - BTCUSDT 4h │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Controls: │
|
||||
│ [Liquidity Zones ☑] [FVGs ☑] [Order Blocks ☑] [POI ☑] │
|
||||
│ [Bias: Bullish ▼] [Threshold: Medium ▼] [Legend] │
|
||||
│ │
|
||||
│ ══════════════════════════════════════════════════════════════ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ BTCUSDT / 4H Chart │ │
|
||||
│ │ │ │
|
||||
│ │ ╱╲ │ │
|
||||
│ │ ╱┌──┐╲ 🔵 Order Block │ │
|
||||
│ │ ╱╲ ╱│ 🔵 │╲ (Bullish, 8.2/10) │ │
|
||||
│ │ ╱┌──┐╲ ╱ │ │ ╲ Entry: $89,200 │ │
|
||||
│ │ ╱ │ 🟢│ ╲ ╱ │ │ ╲ Strength: 95% │ │
|
||||
│ │ ╱┌───┼────┼───╲╱ │ │ ╲ │ │
|
||||
│ │ ╱ │ 🟡 │ 🔴 │ ║ │ │ ╲ ☑ Liquidity Zone │ │
|
||||
│ │╱───┼────┼────┼──║──────┼────┼──────╲ Above: $90,100 │ │
|
||||
│ │─────┼────┼────┼──║──────┼────┼─────── Strength: High │ │
|
||||
│ │ │ │ │ ║ │ │ Probability: 75% │ │
|
||||
│ │ │ │ │ ║ │ 🔘 │ │ │
|
||||
│ │ │ │ │ ║ │ 🟣 │ 🟡 Fair Value Gap │
|
||||
│ │ └────┴────┴──║──────┴────┴── $89,850 - $89,650 │
|
||||
│ │ ║ Gap Size: 0.2% │
|
||||
│ │ ║ Type: Bullish Breaker │
|
||||
│ │ │
|
||||
│ │ [Zoom] [Reset] [Compare] [Backtest This] [Alerts] │
|
||||
│ │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ICT ELEMENTS FOUND (16) │
|
||||
│ ═════════════════════════════════════════════════════════════ │
|
||||
│ │
|
||||
│ ┌─ LIQUIDITY ZONES (4) ─────────────────────────────────────┐ │
|
||||
│ │ ☑ Above Current Price: │ │
|
||||
│ │ • $90,100 - $90,500 (Strength: 94%) - Last 3H ago │ │
|
||||
│ │ • $91,200 - $91,800 (Strength: 87%) - Last 8H ago │ │
|
||||
│ │ ☑ Below Current Price: │ │
|
||||
│ │ • $88,500 - $88,200 (Strength: 91%) - Last 2H ago │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ FAIR VALUE GAPS (3) ──────────────────────────────────────┐ │
|
||||
│ │ • $89,850 - $89,650 (Bullish Breaker, Gap Size: 0.2%) │ │
|
||||
│ │ ├─ Formed: 45 min ago │ │
|
||||
│ │ ├─ Status: Unmitigated (Not touched by price) │ │
|
||||
│ │ └─ Probability: High that will be filled in 4-8H │ │
|
||||
│ │ • $87,900 - $87,500 (Bearish Breaker, Gap Size: 0.4%) │ │
|
||||
│ │ ├─ Formed: 6H ago │ │
|
||||
│ │ ├─ Status: Partially Mitigated │ │
|
||||
│ │ └─ Probability: Medium that will be retested │ │
|
||||
│ │ • $90,200 - $89,950 (Bullish Breaker, Gap Size: 0.25%) │ │
|
||||
│ │ ├─ Formed: 2H ago │ │
|
||||
│ │ ├─ Status: Unmitigated │ │
|
||||
│ │ └─ Probability: Very High (needs mitigation) │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ ORDER BLOCKS (5) ────────────────────────────────────────┐ │
|
||||
│ │ ☑ Bullish Order Block: │ │
|
||||
│ │ • Entry: $89,200 - $89,350 (Strength: 95%, Score: 8.2) │ │
|
||||
│ │ • Formed during strong uptrend 3H ago │ │
|
||||
│ │ • Touch points: 2 (May retry) │ │
|
||||
│ │ • Next resistance: $90,100 (Liquidity Zone) │ │
|
||||
│ │ ☑ Bearish Order Block: │ │
|
||||
│ │ • Entry: $90,500 - $90,650 (Strength: 88%, Score: 7.9) │ │
|
||||
│ │ • Formed during downtrend 6H ago │ │
|
||||
│ │ • Touch points: 1 (Fresh block) │ │
|
||||
│ │ • Next support: $89,500 (FVG area) │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ POINTS OF INTEREST (4) ──────────────────────────────────┐ │
|
||||
│ │ • $90,100 (Swing High + Liquidity Zone) │ │
|
||||
│ │ ├─ Type: Supply/Resistance │ │
|
||||
│ │ ├─ Confluence: 3 levels │ │
|
||||
│ │ └─ Probability of reversal: 82% │ │
|
||||
│ │ • $88,500 (Swing Low + Liquidity Zone) │ │
|
||||
│ │ ├─ Type: Demand/Support │ │
|
||||
│ │ ├─ Confluence: 2 levels │ │
|
||||
│ │ └─ Probability of bounce: 76% │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
**Escenario 1: Mostrar análisis ICT completo**
|
||||
```gherkin
|
||||
DADO que el usuario está en la pantalla de chart
|
||||
CUANDO abre la sección "ICT Analysis"
|
||||
ENTONCES se visualizan en el chart:
|
||||
- Zonas de liquidez (coloreadas y etiquetadas)
|
||||
- Fair Value Gaps (dibujados como rectángulos vacíos)
|
||||
- Order Blocks (rectángulos rellenos con poder)
|
||||
- Puntos de Interés (círculos/estrelas destacadas)
|
||||
Y cada elemento tiene color diferenciado (bullish/bearish)
|
||||
Y se muestra un panel lateral con listado de todos los elementos
|
||||
```
|
||||
|
||||
**Escenario 2: Filtrar elementos ICT**
|
||||
```gherkin
|
||||
DADO que el usuario está viendo el análisis ICT
|
||||
CUANDO desactiva un tipo de elemento (ej: FVGs)
|
||||
ENTONCES desaparecen del chart
|
||||
Y se actualiza el contador en el panel lateral
|
||||
Y el resto de elementos se mantienen visibles
|
||||
```
|
||||
|
||||
**Escenario 3: Ver detalles de zona de liquidez**
|
||||
```gherkin
|
||||
DADO que el usuario está en ICT Analysis
|
||||
CUANDO hace hover/click en una zona de liquidez
|
||||
ENTONCES aparece un tooltip/modal con:
|
||||
- Rango de precios (high-low)
|
||||
- Fortaleza/poder de la zona (%)
|
||||
- Fecha de formación
|
||||
- Número de toques/veces que fue tocada
|
||||
- Probabilidad de que el precio vuelva a esa zona
|
||||
- Botón para crear orden en esa zona
|
||||
```
|
||||
|
||||
**Escenario 4: Ver detalles de Fair Value Gap**
|
||||
```gherkin
|
||||
DADO que el usuario está en ICT Analysis
|
||||
CUANDO hace click en un FVG
|
||||
ENTONCES aparece un modal con:
|
||||
- Rango del gap (precio alto - precio bajo)
|
||||
- Tipo de gap (Bullish Breaker / Bearish Breaker / Continuation)
|
||||
- Tamaño del gap (en pips/%)
|
||||
- Estado (Unmitigated / Partially mitigated / Fully mitigated)
|
||||
- Fecha de formación
|
||||
- Probabilidad de que se cierre el gap (%)
|
||||
- Momento estimado de cierre
|
||||
- [Ver en chart] [Crear alerta] [Crear orden]
|
||||
```
|
||||
|
||||
**Escenario 5: Ver detalles de Order Block**
|
||||
```gherkin
|
||||
DADO que el usuario está en ICT Analysis
|
||||
CUANDO hace click en un Order Block
|
||||
ENTONCES aparece un modal con:
|
||||
- Rango de entrada del bloque
|
||||
- Tipo (Bullish / Bearish)
|
||||
- Score de fortaleza (1-10)
|
||||
- Potencia del bloque (%)
|
||||
- Número de veces que fue tocado
|
||||
- Formación: qué vela/candle lo creó
|
||||
- Próxima resistencia/soporte
|
||||
- [Crear orden en este bloque] [Crear alerta]
|
||||
```
|
||||
|
||||
**Escenario 6: Cambiar timeframe**
|
||||
```gherkin
|
||||
DADO que el usuario está en ICT Analysis
|
||||
CUANDO cambia el timeframe (ej: 1H → 4H → 1D)
|
||||
ENTONCES se recalcula y redibuja todo el análisis ICT
|
||||
Y los elementos cambian según el timeframe elegido
|
||||
Y el API actualiza con los datos del nuevo timeframe
|
||||
```
|
||||
|
||||
**Escenario 7: Crear alerta en zona ICT**
|
||||
```gherkin
|
||||
DADO que el usuario está viendo un elemento ICT
|
||||
CUANDO hace click en "Crear alerta" desde el modal
|
||||
ENTONCES se abre un formulario para crear una alerta:
|
||||
- Precio de trigger (pre-rellenado)
|
||||
- Tipo de alerta (email/push/SMS)
|
||||
- Mensaje personalizado
|
||||
CUANDO confirma
|
||||
ENTONCES se crea la alerta
|
||||
Y se muestra confirmación "Alerta creada para $90,100"
|
||||
```
|
||||
|
||||
**Escenario 8: Comparar análisis entre timeframes**
|
||||
```gherkin
|
||||
DADO que el usuario está en ICT Analysis
|
||||
CUANDO hace click en "Compare"
|
||||
ENTONCES se abre una vista con dos charts lado a lado:
|
||||
- Izquierda: timeframe actual (ej: 4H)
|
||||
- Derecha: timeframe diferente (ej: 1D)
|
||||
Y ambos muestran su análisis ICT correspondiente
|
||||
Y se resaltan confluyencias entre timeframes
|
||||
```
|
||||
|
||||
**Escenario 9: Backtesting de zona ICT**
|
||||
```gherkin
|
||||
DADO que el usuario está viendo un Order Block o FVG
|
||||
CUANDO hace click en "Backtest This"
|
||||
ENTONCES se abre un análisis histórico mostrando:
|
||||
- Cuántas veces el precio tocó esta zona en el pasado
|
||||
- Cuántas veces resultó en reversión
|
||||
- Ganancia promedio si se hubiera operado en esta zona
|
||||
- Tasa de éxito (%)
|
||||
- Gráfico histórico de touch points
|
||||
```
|
||||
|
||||
## Criterios Adicionales
|
||||
|
||||
- [ ] El análisis debe cargar en menos de 1.5 segundos
|
||||
- [ ] Debe soportar todos los timeframes (1m, 5m, 15m, 1h, 4h, 1d, 1w)
|
||||
- [ ] Debe recalcularse automáticamente cada nueva vela
|
||||
- [ ] Los colores deben ser customizables por tema (light/dark)
|
||||
- [ ] Bullish elements en verde/azul, Bearish en rojo/naranja
|
||||
- [ ] Responsive en tablets y mobile (scrollable chart)
|
||||
- [ ] Performance: máximo 50 elementos ICT dibujados simultáneamente
|
||||
- [ ] Debe funcionar sin valores reales de market data si es necesario (mock data)
|
||||
|
||||
---
|
||||
|
||||
## Tareas Técnicas
|
||||
|
||||
**Backend (FastAPI - ml-engine):**
|
||||
- [ ] BE-ML-016: Crear endpoint GET /api/ml/ict/:symbol
|
||||
- [ ] BE-ML-017: Implementar análisis de Liquidity Zones
|
||||
- [ ] BE-ML-018: Implementar detección de Fair Value Gaps
|
||||
- [ ] BE-ML-019: Implementar detección de Order Blocks
|
||||
- [ ] BE-ML-020: Implementar cálculo de Points of Interest
|
||||
- [ ] BE-ML-021: Implementar score/strength para cada elemento
|
||||
- [ ] BE-ML-022: Endpoint GET /api/ml/ict/:symbol/compare (compare timeframes)
|
||||
- [ ] BE-ML-023: Implementar caching de análisis ICT
|
||||
|
||||
**Python ML Service:**
|
||||
- [ ] ML-ICT-001: Desarrollar módulo ICT detector
|
||||
- [ ] ML-ICT-002: Algoritmo de identificación de Liquidity Zones
|
||||
- [ ] ML-ICT-003: Algoritmo de detección de FVGs
|
||||
- [ ] ML-ICT-004: Algoritmo de detección de Order Blocks
|
||||
- [ ] ML-ICT-005: Algoritmo de cálculo de POI (Points of Interest)
|
||||
- [ ] ML-ICT-006: Validación y backtesting de elementos ICT
|
||||
- [ ] ML-ICT-007: Tests unitarios de detección
|
||||
|
||||
**Frontend (React):**
|
||||
- [ ] FE-ML-035: Crear componente `ICTAnalysis.tsx`
|
||||
- [ ] FE-ML-036: Crear componente `ICTChart.tsx` (integración lightweight-charts)
|
||||
- [ ] FE-ML-037: Crear componente `ICTControls.tsx` (filtros y toggles)
|
||||
- [ ] FE-ML-038: Crear componente `ICTSidebar.tsx` (listado de elementos)
|
||||
- [ ] FE-ML-039: Crear componente `LiquidityZoneModal.tsx`
|
||||
- [ ] FE-ML-040: Crear componente `FVGModal.tsx`
|
||||
- [ ] FE-ML-041: Crear componente `OrderBlockModal.tsx`
|
||||
- [ ] FE-ML-042: Crear componente `POIModal.tsx`
|
||||
- [ ] FE-ML-043: Crear componente `ComparisonChart.tsx`
|
||||
- [ ] FE-ML-044: Crear componente `BacktestModal.tsx`
|
||||
- [ ] FE-ML-045: Implementar dibujado en lightweight-charts
|
||||
- [ ] FE-ML-046: Implementar state management (Zustand) para ICT data
|
||||
- [ ] FE-ML-047: Implementar tooltips interactivos
|
||||
- [ ] FE-ML-048: Implementar colores customizables por tema
|
||||
|
||||
**Tests:**
|
||||
- [ ] TEST-ML-016: Test detección de Liquidity Zones
|
||||
- [ ] TEST-ML-017: Test detección de FVGs
|
||||
- [ ] TEST-ML-018: Test detección de Order Blocks
|
||||
- [ ] TEST-ML-019: Test E2E de análisis completo
|
||||
- [ ] TEST-ML-020: Test de performance (<1.5s load)
|
||||
|
||||
**Documentación:**
|
||||
- [ ] DOC-ICT-001: Guía de análisis ICT en la plataforma
|
||||
- [ ] DOC-ICT-002: Explicación de cada elemento ICT
|
||||
- [ ] DOC-ICT-003: Cómo interpretar las métricas
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
**Depende de:**
|
||||
- [ ] RF-ML-001: Integración de datos de mercado (OHLCV)
|
||||
- [ ] US-ML-001: Ver chart de trading
|
||||
- [ ] US-ML-002: Ver señal en chart
|
||||
- [ ] OQI-003: Trading Charts (infraestructura base)
|
||||
|
||||
**Bloquea:**
|
||||
- [ ] US-ML-010: Alertas ICT
|
||||
- [ ] US-ML-011: Backtesting de estrategia ICT
|
||||
|
||||
---
|
||||
|
||||
## Notas Técnicas
|
||||
|
||||
**Endpoints involucrados:**
|
||||
| Método | Endpoint | Descripción |
|
||||
|--------|----------|-------------|
|
||||
| GET | /api/ml/ict/:symbol | Obtener análisis ICT para símbolo |
|
||||
| GET | /api/ml/ict/:symbol/timeframe/:tf | Análisis ICT por timeframe |
|
||||
| GET | /api/ml/ict/:symbol/compare | Comparar análisis entre TFs |
|
||||
| POST | /api/ml/ict/:symbol/backtest | Backtesting de zona ICT |
|
||||
| GET | /api/alerts/:symbol/ict | Obtener alertas ICT activas |
|
||||
|
||||
**Query Parameters - ICT Analysis:**
|
||||
```
|
||||
GET /api/ml/ict/BTCUSDT?
|
||||
timeframe=4h&
|
||||
include=liquidity_zones,fvgs,order_blocks,poi&
|
||||
threshold=medium&
|
||||
bias=bullish
|
||||
```
|
||||
|
||||
**Response esperado:**
|
||||
```typescript
|
||||
interface ICTAnalysisResponse {
|
||||
symbol: string;
|
||||
timeframe: string;
|
||||
timestamp: string;
|
||||
analysis: {
|
||||
liquidity_zones: LiquidityZone[];
|
||||
fair_value_gaps: FairValueGap[];
|
||||
order_blocks: OrderBlock[];
|
||||
points_of_interest: PointOfInterest[];
|
||||
};
|
||||
summary: {
|
||||
total_elements: number;
|
||||
bullish_elements: number;
|
||||
bearish_elements: number;
|
||||
strongest_level: number;
|
||||
nearest_liquidity_up: number;
|
||||
nearest_liquidity_down: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface LiquidityZone {
|
||||
id: string;
|
||||
type: 'BUY' | 'SELL';
|
||||
price_high: number;
|
||||
price_low: number;
|
||||
strength: number; // 0-100
|
||||
formed_at: string;
|
||||
touch_count: number;
|
||||
probability_return: number; // 0-100
|
||||
}
|
||||
|
||||
interface FairValueGap {
|
||||
id: string;
|
||||
type: 'BULLISH_BREAKER' | 'BEARISH_BREAKER' | 'CONTINUATION';
|
||||
gap_high: number;
|
||||
gap_low: number;
|
||||
gap_size_pips: number;
|
||||
gap_size_percent: number;
|
||||
status: 'UNMITIGATED' | 'PARTIALLY_MITIGATED' | 'FULLY_MITIGATED';
|
||||
formed_at: string;
|
||||
probability_fill: number; // 0-100
|
||||
estimated_fill_time: string; // ISO duration
|
||||
}
|
||||
|
||||
interface OrderBlock {
|
||||
id: string;
|
||||
type: 'BULLISH' | 'BEARISH';
|
||||
entry_high: number;
|
||||
entry_low: number;
|
||||
strength_score: number; // 1-10
|
||||
power_percent: number; // 0-100
|
||||
touch_count: number;
|
||||
formed_at: string;
|
||||
candle_formation: string;
|
||||
next_target: number;
|
||||
}
|
||||
|
||||
interface PointOfInterest {
|
||||
id: string;
|
||||
price: number;
|
||||
type: 'SWING_HIGH' | 'SWING_LOW' | 'CONFLUENCE';
|
||||
confluence_count: number;
|
||||
reversal_probability: number; // 0-100
|
||||
bounce_probability: number; // 0-100
|
||||
elements: string[]; // IDs de elementos que confluyen
|
||||
}
|
||||
```
|
||||
|
||||
**Componentes UI principales:**
|
||||
- `ICTAnalysis`: Container del análisis
|
||||
- `ICTChart`: Canvas del chart con dibujado de elementos
|
||||
- `ICTControls`: Controles de filtrado y display
|
||||
- `ICTSidebar`: Listado de elementos detectados
|
||||
- `ICTModals`: Familia de modales (Liquidity, FVG, OB, POI)
|
||||
- `ComparisonChart`: Vista comparativa de timeframes
|
||||
- `BacktestView`: Análisis histórico de una zona
|
||||
|
||||
**State Management (Zustand):**
|
||||
```typescript
|
||||
interface ICTStore {
|
||||
ictData: ICTAnalysisResponse | null;
|
||||
selectedElement: ICTElement | null;
|
||||
timeframe: string;
|
||||
visibleElements: {
|
||||
liquidity_zones: boolean;
|
||||
fvgs: boolean;
|
||||
order_blocks: boolean;
|
||||
poi: boolean;
|
||||
};
|
||||
bias: 'BULLISH' | 'BEARISH' | 'NEUTRAL';
|
||||
threshold: 'LOW' | 'MEDIUM' | 'HIGH';
|
||||
|
||||
fetchICTAnalysis: (symbol: string, tf: string) => Promise<void>;
|
||||
selectElement: (element: ICTElement) => void;
|
||||
toggleElementType: (type: keyof visibleElements) => void;
|
||||
setBias: (bias: string) => void;
|
||||
setThreshold: (threshold: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
**Integración con Chart:**
|
||||
- Usar series personalizada de lightweight-charts
|
||||
- Plugins para dibujado de zonas y elementos
|
||||
- Overlay layers para diferentes tipos de elementos
|
||||
|
||||
---
|
||||
|
||||
## Definition of Ready (DoR)
|
||||
|
||||
- [x] Historia claramente escrita (quién, qué, por qué)
|
||||
- [x] Criterios de aceptación detallados (9 escenarios)
|
||||
- [x] Story points estimados (5)
|
||||
- [x] Dependencias identificadas
|
||||
- [x] Sin bloqueadores
|
||||
- [x] Diseño/mockup disponible
|
||||
- [x] APIs especificadas
|
||||
|
||||
## Definition of Done (DoD)
|
||||
|
||||
- [ ] Código Python implementado (ICT detectors)
|
||||
- [ ] Código TypeScript implementado (endpoints + frontend)
|
||||
- [ ] Tests unitarios escritos y pasando (Python y TS)
|
||||
- [ ] Tests E2E pasando
|
||||
- [ ] Code review aprobado
|
||||
- [ ] Documentación actualizada
|
||||
- [ ] Backtesting validado (accuracy > 80%)
|
||||
- [ ] Rendimiento verificado (<1.5s load)
|
||||
- [ ] Responsive en mobile/tablet
|
||||
- [ ] QA aprobado en staging
|
||||
- [ ] Desplegado en producción
|
||||
|
||||
---
|
||||
|
||||
## Historial de Cambios
|
||||
|
||||
| Fecha | Cambio | Autor |
|
||||
|-------|--------|-------|
|
||||
| 2026-01-25 | Creación | Claude Code |
|
||||
|
||||
---
|
||||
|
||||
**Creada por:** Claude Code
|
||||
**Fecha:** 2026-01-25
|
||||
**Última actualización:** 2026-01-25
|
||||
@ -0,0 +1,448 @@
|
||||
---
|
||||
id: "US-ML-010"
|
||||
title: "Scan Multi-Símbolo"
|
||||
type: "User Story"
|
||||
status: "Pending"
|
||||
priority: "Media"
|
||||
epic: "OQI-006"
|
||||
project: "trading-platform"
|
||||
story_points: 5
|
||||
created_date: "2026-01-25"
|
||||
updated_date: "2026-01-25"
|
||||
---
|
||||
|
||||
# US-ML-010: Scan Multi-Símbolo
|
||||
|
||||
## Metadata
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | US-ML-010 |
|
||||
| **Épica** | OQI-006 - Señales ML y Predicciones |
|
||||
| **Módulo** | ml-signals |
|
||||
| **Prioridad** | P2 (Media) |
|
||||
| **Story Points** | 5 |
|
||||
| **Sprint** | Por asignar |
|
||||
| **Estado** | Pendiente |
|
||||
| **Asignado a** | Por asignar |
|
||||
|
||||
---
|
||||
|
||||
## Historia de Usuario
|
||||
|
||||
**Como** trader/inversor,
|
||||
**quiero** escanear múltiples símbolos simultáneamente en busca de señales de trading,
|
||||
**para** encontrar las mejores oportunidades disponibles en el mercado de forma eficiente.
|
||||
|
||||
## Descripción Detallada
|
||||
|
||||
El usuario debe poder seleccionar una lista de símbolos (desde una lista predefinida, watchlist personal, o subir lista personalizada), ejecutar un escaneo que genere señales ML para todos ellos en paralelo, y visualizar los resultados filtrados y ordenados por confianza. El escaneo debe completarse en menos de 10 segundos para máximo 50 símbolos.
|
||||
|
||||
## Mockups/Wireframes
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ MULTI-SYMBOL SCAN │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Symbol Selection: │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ▼ Select from: [Popular ▼] [My Watchlist ▼] [Custom ▼] │ │
|
||||
│ │ │ │
|
||||
│ │ ☑ BTCUSDT ☑ ETHUSDT ☑ BNBUSDT │ │
|
||||
│ │ ☑ ADAUSDT ☑ XRPUSDT ☑ DOGEUSDT │ │
|
||||
│ │ ☑ MATICUSDT ☑ SOLUSDT ☑ LINKUSDT │ │
|
||||
│ │ │ │
|
||||
│ │ [Clear All] [Select All] [Select: 9 symbols] │ │
|
||||
│ │ │ │
|
||||
│ │ Or upload CSV: [Choose File] upload-symbols.csv │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Filter & Sort: │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Signal Type: [All ▼] Action: [BUY ▼] Horizon: [All ▼] │ │
|
||||
│ │ Min Confidence: [50% ▼] Sort by: [Confidence ▼] │ │
|
||||
│ │ │ │
|
||||
│ │ [🔄 Scan Now] [⏸️ Cancel] Scanning: 7/9 │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ══════════════════════════════════════════════════════════════ │
|
||||
│ │
|
||||
│ SCAN RESULTS - 9 Symbols, 12 Signals Found │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Symbol Action Horizon Conf. Score Entry Status │ │
|
||||
│ ├────────────────────────────────────────────────────────────┤ │
|
||||
│ │ BTCUSDT 🟢 BUY Scalping 85% 9.2/10 $89,400 ✅ Ready│ │
|
||||
│ │ ETHUSDT 🟢 BUY Intraday 78% 8.8/10 $3,240 ✅ Ready│ │
|
||||
│ │ BNBUSDT 🟢 BUY Swing 72% 8.1/10 $620 ✅ Ready│ │
|
||||
│ │ ADAUSDT 🔴 SELL Intraday 68% 7.6/10 $1.05 ✅ Ready│ │
|
||||
│ │ LINKUSDT 🟢 BUY Swing 65% 7.3/10 $28.50 ✅ Ready│ │
|
||||
│ │ SOLUSDT 🔵 HOLD Position 58% 6.9/10 $190.50 ⚠️ Weak │ │
|
||||
│ │ XRPUSDT 🔴 SELL Scalping 55% 6.2/10 $2.45 ⚠️ Weak │ │
|
||||
│ │ MATICUSDT - - - - - - ❌ NoSig│ │
|
||||
│ │ DOGEUSDT - - - - - - ❌ NoSig│ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [View Details] [Add to Watchlist] [Export Results] │
|
||||
│ │
|
||||
│ SUMMARY │
|
||||
│ ══════════════════════════════════════════════════════════════ │
|
||||
│ Total Symbols Scanned: 9 │
|
||||
│ Signals Found: 7 (77.8%) │
|
||||
│ Strong Signals (>70%): 4 │
|
||||
│ Weak Signals (50-70%): 3 │
|
||||
│ Avg Confidence: 68.3% │
|
||||
│ │
|
||||
│ [Create Portfolio from BUY Signals] [Refresh Scan] │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ SCAN PROGRESS [✕ Cancel] │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Scanning 9 symbols in parallel... │
|
||||
│ │
|
||||
│ ✅ BTCUSDT (2.1s) │
|
||||
│ ✅ ETHUSDT (1.8s) │
|
||||
│ ✅ BNBUSDT (1.5s) │
|
||||
│ ⏳ ADAUSDT (Generating signal...) │
|
||||
│ ⏳ XRPUSDT (Generating signal...) │
|
||||
│ ⏳ LINKUSDT (Analyzing indicators...) │
|
||||
│ ⏳ SOLUSDT (Analyzing indicators...) │
|
||||
│ ⏳ MATICUSDT (Analyzing indicators...) │
|
||||
│ ⏳ DOGEUSDT (Analyzing indicators...) │
|
||||
│ │
|
||||
│ Completed: 3/9 • Elapsed: 5.2s • Est. time: ~7.8s │
|
||||
│ ████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 33% │
|
||||
│ │
|
||||
│ [View Real-time Feed] [Pause] [Cancel] │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
**Escenario 1: Seleccionar símbolos desde lista predefinida**
|
||||
```gherkin
|
||||
DADO que el usuario está en la pantalla Multi-Symbol Scan
|
||||
CUANDO hace click en "Popular"
|
||||
ENTONCES se muestran los 50 símbolos más populares/líquidos
|
||||
Y puede seleccionar hasta 50 símbolos con checkboxes
|
||||
Y se muestra el contador "Select: X symbols"
|
||||
```
|
||||
|
||||
**Escenario 2: Usar watchlist personal**
|
||||
```gherkin
|
||||
DADO que el usuario tiene una watchlist guardada
|
||||
CUANDO selecciona "My Watchlist"
|
||||
ENTONCES se cargan automáticamente los símbolos de la watchlist
|
||||
Y puede desseleccionar símbolos si lo desea
|
||||
Y se mantiene la selección al cambiar de filtro
|
||||
```
|
||||
|
||||
**Escenario 3: Subir lista personalizada en CSV**
|
||||
```gherkin
|
||||
DADO que el usuario está en Symbol Selection
|
||||
CUANDO hace click en "Choose File" y selecciona un CSV
|
||||
ENTONCES el sistema parsea el archivo (formato: 1 símbolo por línea)
|
||||
Y agrega los símbolos a la selección
|
||||
Y muestra cantidad de símbolos cargados (validando que sean correctos)
|
||||
Y permite deseleccionar los que no desea
|
||||
```
|
||||
|
||||
**Escenario 4: Ejecutar escaneo multi-símbolo**
|
||||
```gherkin
|
||||
DADO que el usuario ha seleccionado 9 símbolos
|
||||
Y ha configurado filtros (tipo de señal, confianza mínima)
|
||||
CUANDO hace click en "Scan Now"
|
||||
ENTONCES inicia escaneo paralelo en todos los símbolos
|
||||
Y muestra progreso en tiempo real (símbolos completados)
|
||||
Y el escaneo completa en menos de 10 segundos para 50 símbolos
|
||||
Y muestra "Scanning: 3/9" mientras se ejecuta
|
||||
```
|
||||
|
||||
**Escenario 5: Ver resultados en tabla**
|
||||
```gherkin
|
||||
DADO que el escaneo se ha completado
|
||||
CUANDO se muestran los resultados
|
||||
ENTONCES se visualiza tabla con columnas:
|
||||
- Símbolo
|
||||
- Acción (BUY/SELL/HOLD)
|
||||
- Horizonte
|
||||
- Confianza %
|
||||
- Score (0-10)
|
||||
- Precio de entrada
|
||||
- Estado (✅ Ready / ⚠️ Weak / ❌ No Signal)
|
||||
Y cada fila es clickeable para ver detalles completos
|
||||
```
|
||||
|
||||
**Escenario 6: Filtrar resultados por confianza**
|
||||
```gherkin
|
||||
DADO que se muestran los resultados del escaneo
|
||||
CUANDO selecciona "Min Confidence: 70%"
|
||||
ENTONCES se muestran solo señales con confianza >= 70%
|
||||
Y se actualiza el contador "7 of 12 signals"
|
||||
Y el resumen se recalcula solo para señales filtradas
|
||||
```
|
||||
|
||||
**Escenario 7: Filtrar por tipo de señal**
|
||||
```gherkin
|
||||
DADO que se muestran los resultados
|
||||
CUANDO selecciona "Action: BUY"
|
||||
ENTONCES muestra solo señales de compra
|
||||
Y se pueden combinar filtros (ej: Action=BUY AND Horizon=Scalping)
|
||||
```
|
||||
|
||||
**Escenario 8: Ordenar por confianza**
|
||||
```gherkin
|
||||
DADO que se muestran resultados
|
||||
CUANDO hace click en "Sort by: Confidence" (descendente)
|
||||
ENTONCES la tabla se ordena con señales más confiables primero
|
||||
Y se puede invertir el orden (ascendente/descendente)
|
||||
Y se pueden ordenar también por Score, Símbolo, etc.
|
||||
```
|
||||
|
||||
**Escenario 9: Exportar resultados**
|
||||
```gherkin
|
||||
DADO que se muestran los resultados del escaneo
|
||||
CUANDO hace click en "Export Results"
|
||||
ENTONCES descarga archivo CSV con:
|
||||
- Symbol, Action, Horizon, Confidence, Score, Entry Price
|
||||
- Nombre: "scan-results-2026-01-25-09-30.csv"
|
||||
```
|
||||
|
||||
**Escenario 10: Crear portfolio desde resultados**
|
||||
```gherkin
|
||||
DADO que se muestran resultados del escaneo
|
||||
CUANDO hace click en "Create Portfolio from BUY Signals"
|
||||
ENTONCES se abre modal para crear nuevo portfolio
|
||||
Y se preseleccionan automáticamente todos los BUY signals
|
||||
Y el usuario puede ajustar pesos y confirmar creación
|
||||
```
|
||||
|
||||
## Criterios Adicionales
|
||||
|
||||
- [ ] El escaneo debe soportar máximo 50 símbolos simultáneamente
|
||||
- [ ] El tiempo máximo debe ser menor a 10 segundos
|
||||
- [ ] Los resultados deben mostrarse ordenados por confianza descendente
|
||||
- [ ] El CSV debe ser válido y con header
|
||||
- [ ] Debe mostrar progreso en tiempo real durante el escaneo
|
||||
- [ ] Debe manejar símbolos inválidos (ignorarlos o marcar con error)
|
||||
- [ ] La tabla debe ser responsive en móvil
|
||||
- [ ] Los filtros deben ser combinables
|
||||
|
||||
---
|
||||
|
||||
## Tareas Técnicas
|
||||
|
||||
**Backend:**
|
||||
- [ ] BE-ML-020: Crear endpoint POST /api/ml/scan
|
||||
- [ ] BE-ML-021: Implementar worker para escaneo paralelo (queue-based)
|
||||
- [ ] BE-ML-022: Validar símbolos de entrada
|
||||
- [ ] BE-ML-023: Parsear CSV de símbolos
|
||||
- [ ] BE-ML-024: Implementar filtros (action, horizon, confidence_min)
|
||||
- [ ] BE-ML-025: Generar respuesta con resultados ordenados
|
||||
- [ ] BE-ML-026: Implementar export a CSV
|
||||
|
||||
**Frontend:**
|
||||
- [ ] FE-ML-040: Crear componente `MultiSymbolScan.tsx`
|
||||
- [ ] FE-ML-041: Crear componente `SymbolSelector.tsx`
|
||||
- [ ] FE-ML-042: Crear componente `ScanFilters.tsx`
|
||||
- [ ] FE-ML-043: Crear componente `ScanResults.tsx`
|
||||
- [ ] FE-ML-044: Crear componente `ScanProgress.tsx`
|
||||
- [ ] FE-ML-045: Implementar upload de CSV
|
||||
- [ ] FE-ML-046: Implementar tabla con sorting y filtros
|
||||
- [ ] FE-ML-047: Implementar export a CSV
|
||||
- [ ] FE-ML-048: Integración con TanStack Query (react-query)
|
||||
- [ ] FE-ML-049: Real-time progress updates (WebSocket/SSE)
|
||||
|
||||
**ML Engine:**
|
||||
- [ ] ML-020: Optimizar generación de señales para múltiples símbolos
|
||||
- [ ] ML-021: Implementar ejecución paralela de análisis
|
||||
- [ ] ML-022: Cachear indicadores técnicos por símbolo
|
||||
|
||||
**Tests:**
|
||||
- [ ] TEST-ML-020: Test de validación de símbolos
|
||||
- [ ] TEST-ML-021: Test de parseo CSV
|
||||
- [ ] TEST-ML-022: Test de escaneo paralelo (timeout, errores)
|
||||
- [ ] TEST-ML-023: Test de filtrado y ordenamiento
|
||||
- [ ] TEST-ML-024: Test E2E completo del multi-scan
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
**Depende de:**
|
||||
- [ ] US-ML-002: Ver señal - Estado: Pendiente
|
||||
- [ ] RF-ML-001: Generación de señales ML
|
||||
- [ ] Infrastructure: Worker queue (Bull/RabbitMQ)
|
||||
|
||||
**Bloquea:**
|
||||
- [ ] US-ML-011: Alertas de señales (requiere resultados del scan)
|
||||
- [ ] US-ML-012: Portfolio builder (requiere scan multi-símbolo)
|
||||
|
||||
---
|
||||
|
||||
## Notas Técnicas
|
||||
|
||||
**Endpoints involucrados:**
|
||||
| Método | Endpoint | Descripción |
|
||||
|--------|----------|-------------|
|
||||
| POST | /api/ml/scan | Ejecutar escaneo multi-símbolo |
|
||||
| GET | /api/ml/scan/:scanId/progress | Obtener progreso en tiempo real |
|
||||
| GET | /api/ml/scan/:scanId/results | Obtener resultados del escaneo |
|
||||
| GET | /api/ml/scan/:scanId/export.csv | Exportar resultados a CSV |
|
||||
| GET | /api/symbols/popular | Lista de símbolos populares |
|
||||
| GET | /api/watchlists/:id/symbols | Símbolos de watchlist |
|
||||
|
||||
**Request esperado:**
|
||||
```typescript
|
||||
interface MultiSymbolScanRequest {
|
||||
symbols: string[]; // Array de símbolos (ej: ['BTCUSDT', 'ETHUSDT'])
|
||||
filters: {
|
||||
signal_type?: 'BUY' | 'SELL' | 'HOLD' | 'ALL';
|
||||
horizon?: string;
|
||||
confidence_min?: number; // 0-100
|
||||
action?: 'BUY' | 'SELL';
|
||||
};
|
||||
sort_by?: 'confidence' | 'score' | 'symbol';
|
||||
sort_order?: 'asc' | 'desc';
|
||||
}
|
||||
```
|
||||
|
||||
**Response esperado:**
|
||||
```typescript
|
||||
interface MultiSymbolScanResponse {
|
||||
scan_id: string;
|
||||
status: 'completed' | 'in_progress' | 'failed';
|
||||
timestamp: string;
|
||||
symbols_scanned: number;
|
||||
signals_found: number;
|
||||
results: SignalResult[];
|
||||
summary: {
|
||||
total_symbols: number;
|
||||
signals_found: number;
|
||||
strong_signals: number; // confidence >= 70%
|
||||
weak_signals: number; // 50% <= confidence < 70%
|
||||
avg_confidence: number;
|
||||
};
|
||||
execution_time_ms: number;
|
||||
}
|
||||
|
||||
interface SignalResult {
|
||||
symbol: string;
|
||||
action: 'BUY' | 'SELL' | 'HOLD' | null;
|
||||
horizon?: string;
|
||||
confidence?: number;
|
||||
score?: number;
|
||||
entry_price?: number;
|
||||
status: 'ready' | 'weak' | 'no_signal' | 'error';
|
||||
error_message?: string;
|
||||
}
|
||||
```
|
||||
|
||||
**CSV Upload Parser:**
|
||||
```typescript
|
||||
// Soporta formatos:
|
||||
// 1. Un símbolo por línea (simple)
|
||||
BTCUSDT
|
||||
ETHUSDT
|
||||
BNBUSDT
|
||||
|
||||
// 2. CSV con header
|
||||
Symbol,Exchange,Type
|
||||
BTCUSDT,BINANCE,SPOT
|
||||
ETHUSDT,BINANCE,SPOT
|
||||
```
|
||||
|
||||
**Componentes UI:**
|
||||
- `MultiSymbolScan`: Container principal
|
||||
- `SymbolSelector`: Selector de símbolos con pestañas
|
||||
- `SymbolListPicker`: Selector desde lista predefinida
|
||||
- `WatchlistPicker`: Selector desde watchlist personal
|
||||
- `CSVUploader`: Componente de upload de CSV
|
||||
- `ScanFilters`: Filtros de escaneo
|
||||
- `ScanResults`: Tabla de resultados
|
||||
- `ScanProgress`: Modal de progreso en tiempo real
|
||||
- `ResultsActions`: Acciones sobre resultados (export, create portfolio)
|
||||
|
||||
**Estado (Zustand):**
|
||||
```typescript
|
||||
interface MultiSymbolScanStore {
|
||||
selectedSymbols: string[];
|
||||
scanResults: SignalResult[];
|
||||
scanProgress: {
|
||||
total: number;
|
||||
completed: number;
|
||||
currentSymbol: string;
|
||||
};
|
||||
filters: ScanFilters;
|
||||
isScanning: boolean;
|
||||
sortBy: 'confidence' | 'score' | 'symbol';
|
||||
sortOrder: 'asc' | 'desc';
|
||||
|
||||
setSelectedSymbols: (symbols: string[]) => void;
|
||||
startScan: () => Promise<void>;
|
||||
updateProgress: (progress: ScanProgress) => void;
|
||||
updateFilters: (filters: Partial<ScanFilters>) => void;
|
||||
setSortBy: (field: string, order: 'asc' | 'desc') => void;
|
||||
exportToCSV: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
**Real-time Progress:**
|
||||
- Usar WebSocket o SSE para enviar actualizaciones de progreso
|
||||
- Endpoint: `/api/ml/scan/:scanId/progress` con streaming
|
||||
- O usar polling cada 500ms si WebSocket no disponible
|
||||
|
||||
**Performance Targets:**
|
||||
- 50 símbolos scanned: < 10 segundos
|
||||
- 20 símbolos scanned: < 5 segundos
|
||||
- 10 símbolos scanned: < 3 segundos
|
||||
|
||||
---
|
||||
|
||||
## Definition of Ready (DoR)
|
||||
|
||||
- [x] Historia claramente escrita (quién, qué, por qué)
|
||||
- [x] Criterios de aceptación definidos
|
||||
- [x] Story points estimados
|
||||
- [x] Dependencias identificadas
|
||||
- [x] Diseño/mockup disponible
|
||||
- [x] Targets de performance definidos
|
||||
- [x] Formatos de datos especificados
|
||||
|
||||
## Definition of Done (DoD)
|
||||
|
||||
- [ ] Código implementado según criterios
|
||||
- [ ] Tests unitarios escritos y pasando
|
||||
- [ ] Tests E2E pasando
|
||||
- [ ] Code review aprobado
|
||||
- [ ] Documentación actualizada
|
||||
- [ ] QA aprobado en staging
|
||||
- [ ] Escaneo paralelo funciona correctamente
|
||||
- [ ] Real-time progress updates funcionan
|
||||
- [ ] CSV upload y export validan correctamente
|
||||
- [ ] Filtros y ordenamiento funcionan
|
||||
- [ ] Performance meets targets (< 10s para 50 símbolos)
|
||||
- [ ] Manejo de errores robusto (símbolos inválidos, timeouts)
|
||||
- [ ] Responsive en móvil
|
||||
- [ ] Desplegado en producción
|
||||
|
||||
---
|
||||
|
||||
## Historial de Cambios
|
||||
|
||||
| Fecha | Cambio | Autor |
|
||||
|-------|--------|-------|
|
||||
| 2026-01-25 | Creación | Requirements-Analyst |
|
||||
|
||||
---
|
||||
|
||||
**Creada por:** Requirements-Analyst
|
||||
**Fecha:** 2026-01-25
|
||||
**Última actualización:** 2026-01-25
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,357 @@
|
||||
---
|
||||
id: "US-LLM-011"
|
||||
title: "Ejecutar Trade desde Chat"
|
||||
type: "User Story"
|
||||
status: "Pending"
|
||||
priority: "Alta"
|
||||
epic: "OQI-007"
|
||||
project: "trading-platform"
|
||||
story_points: 5
|
||||
created_date: "2026-01-25"
|
||||
updated_date: "2026-01-25"
|
||||
---
|
||||
|
||||
# US-LLM-011: Ejecutar Trade desde Chat
|
||||
|
||||
**Épica:** OQI-007 - LLM Strategy Agent
|
||||
**Sprint:** TBD
|
||||
**Story Points:** 5
|
||||
**Prioridad:** P0 - Alta
|
||||
|
||||
---
|
||||
|
||||
## Historia de Usuario
|
||||
|
||||
**Como** usuario Pro o Premium con capacidad de trading real
|
||||
**Quiero** ejecutar trades directamente desde el chat con el asistente AI mediante comandos naturales
|
||||
**Para** operar en los mercados rápidamente sin cambiar de interfaz
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
### AC-1: Interpretar comandos de trading
|
||||
```gherkin
|
||||
Given soy usuario Pro/Premium
|
||||
And tengo acceso a trading real
|
||||
When digo frases como "Vende 50 acciones de AAPL" o "Compra Bitcoin a $43,000"
|
||||
Then el agente interpreta correctamente:
|
||||
- Acción (compra/venta)
|
||||
- Símbolo del activo
|
||||
- Cantidad
|
||||
- Tipo de orden (mercado/límite)
|
||||
- Precio (si es límite)
|
||||
And valida que la orden sea válida
|
||||
```
|
||||
|
||||
### AC-2: Mostrar preview de la orden
|
||||
```gherkin
|
||||
Given el agente interpreta un comando de trading
|
||||
When prepara la orden
|
||||
Then muestra un resumen detallado:
|
||||
- Símbolo y nombre del activo
|
||||
- Acción (BUY/SELL)
|
||||
- Cantidad de unidades
|
||||
- Tipo de orden (market/limit)
|
||||
- Precio unitario estimado
|
||||
- Costo/ingresos totales
|
||||
- Comisiones estimadas
|
||||
- Impacto en el portafolio
|
||||
And NO ejecuta la orden automáticamente
|
||||
```
|
||||
|
||||
### AC-3: Requerir confirmación explícita
|
||||
```gherkin
|
||||
Given el agente muestra el preview de la orden
|
||||
When el usuario revisa los detalles
|
||||
Then el agente pide confirmación explícita:
|
||||
- "¿Confirmas esta orden?" o similar
|
||||
- Botones/acciones claras (Confirmar/Cancelar)
|
||||
And SOLO ejecuta si el usuario confirma
|
||||
And permite cancelar en cualquier momento
|
||||
```
|
||||
|
||||
### AC-4: Validar límites de riesgo
|
||||
```gherkin
|
||||
Given quiero ejecutar una orden
|
||||
When el agente prepara la orden
|
||||
Then valida:
|
||||
- Saldo disponible suficiente
|
||||
- Límite de exposición por activo
|
||||
- Límite de pérdida diaria
|
||||
- Límite de apalancamiento
|
||||
And si hay violación:
|
||||
- Informa qué límite se excede
|
||||
- Muestra valores actuales vs permitidos
|
||||
- NO permite ejecutar
|
||||
```
|
||||
|
||||
### AC-5: Ejecutar y retornar feedback
|
||||
```gherkin
|
||||
Given el usuario confirma la orden
|
||||
When se ejecuta correctamente
|
||||
Then muestra confirmación con:
|
||||
- ID de la orden
|
||||
- Status (filled/pending)
|
||||
- Símbolo y precio ejecutado
|
||||
- Cantidad y costo final
|
||||
- Timestamp exacto
|
||||
- Posición actualizada en el activo
|
||||
And registra en el historial del chat
|
||||
```
|
||||
|
||||
### AC-6: Manejo de errores
|
||||
```gherkin
|
||||
Given intento ejecutar una orden
|
||||
When ocurre error (conexión, mercado cerrado, etc.)
|
||||
Then el agente:
|
||||
- Informa el tipo de error
|
||||
- Explica por qué no se ejecutó
|
||||
- Sugiere alternativas (ej: orden pendiente)
|
||||
- NO procesa parcialmente la orden
|
||||
```
|
||||
|
||||
### AC-7: Restricción por plan
|
||||
```gherkin
|
||||
Given soy usuario Free o con trading deshabilitado
|
||||
When intento ejecutar una orden
|
||||
Then el agente:
|
||||
- Rechaza la orden
|
||||
- Explica que requiere plan Pro/Premium
|
||||
- Muestra opción de upgrade
|
||||
- NO ejecuta ninguna orden
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flujo de Confirmación
|
||||
|
||||
```markdown
|
||||
## Confirmar Orden 📊
|
||||
|
||||
**Acción:** VENTA
|
||||
**Símbolo:** AAPL - Apple Inc.
|
||||
**Cantidad:** 50 acciones
|
||||
**Tipo:** Market Order
|
||||
**Precio actual:** $185.32
|
||||
**Costo total:** ~$9,266.00
|
||||
**Comisión:** ~$9.27
|
||||
**Neto:** ~$9,256.73
|
||||
|
||||
**Tu portafolio:**
|
||||
- Posición actual: 100 acciones @ $184.50
|
||||
- Posición después: 50 acciones
|
||||
|
||||
---
|
||||
|
||||
⚠️ Esta es una orden REAL de trading
|
||||
|
||||
¿Confirmas esta orden?
|
||||
|
||||
[✅ Confirmar] [❌ Cancelar]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Respuesta Post-Ejecución
|
||||
|
||||
```markdown
|
||||
## Orden Ejecutada ✅
|
||||
|
||||
**ID:** ORD-20260125-001234
|
||||
**Status:** Filled (Completada)
|
||||
**Símbolo:** AAPL
|
||||
**Acción:** VENTA
|
||||
**Cantidad:** 50 acciones
|
||||
**Precio:** $185.31
|
||||
**Total:** $9,265.50
|
||||
**Comisión:** $9.27
|
||||
**Neto:** $9,256.23
|
||||
**Timestamp:** 15:45:32 ET
|
||||
|
||||
**Posición Actualizada:**
|
||||
- AAPL: 50 acciones @ $185.31 (promedio)
|
||||
- Valor: $9,265.50
|
||||
- Variación: +2.5% hoy
|
||||
|
||||
Registro guardado en tu historial.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Respuesta con Error
|
||||
|
||||
```markdown
|
||||
## Error al Ejecutar ❌
|
||||
|
||||
**Razón:** Mercado cerrado (Cierre: 16:00 ET)
|
||||
|
||||
La orden no pudo ejecutarse porque el mercado de NYSE está cerrado.
|
||||
|
||||
**Alternativas:**
|
||||
- Crear orden pendiente para la apertura mañana
|
||||
- Intentar en mercados abiertos (premarket a las 04:00 ET)
|
||||
- Esperar a mañana a las 09:30 ET
|
||||
|
||||
¿Qué prefieres?
|
||||
|
||||
[Orden Pendiente] [Otra acción] [Cancelar]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoint
|
||||
|
||||
### POST `/api/llm/execute-trade`
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"sessionId": "chat-session-12345",
|
||||
"interpretation": {
|
||||
"action": "BUY|SELL",
|
||||
"symbol": "AAPL",
|
||||
"quantity": 50,
|
||||
"orderType": "market|limit",
|
||||
"limitPrice": null,
|
||||
"timeInForce": "DAY|GTC"
|
||||
},
|
||||
"userId": "user-uuid",
|
||||
"confirmedAt": "2026-01-25T15:45:32Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (Success):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"order": {
|
||||
"orderId": "ORD-20260125-001234",
|
||||
"status": "filled",
|
||||
"symbol": "AAPL",
|
||||
"action": "SELL",
|
||||
"quantity": 50,
|
||||
"executedPrice": 185.31,
|
||||
"totalValue": 9265.50,
|
||||
"commission": 9.27,
|
||||
"netValue": 9256.23,
|
||||
"executedAt": "2026-01-25T15:45:32Z"
|
||||
},
|
||||
"message": "Orden ejecutada exitosamente"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (Error):**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "MARKET_CLOSED",
|
||||
"message": "El mercado está cerrado",
|
||||
"details": "NYSE cierra a las 16:00 ET"
|
||||
},
|
||||
"alternatives": [
|
||||
{
|
||||
"type": "pending_order",
|
||||
"description": "Crear orden pendiente para apertura"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notas Técnicas
|
||||
|
||||
- **Tool:** `execute_trade` (en el LLM Agent)
|
||||
- **Servicio:** Trading API Backend (puerto 3080)
|
||||
- **Confirmación:** OBLIGATORIA antes de ejecutar
|
||||
- **Validación:** Límites de riesgo, saldo, mercado abierto
|
||||
- **Logging:** Todas las órdenes registradas con timestamp
|
||||
- **Rate limit:** 20 órdenes/minuto por usuario
|
||||
- **Timeout:** 30 segundos para confirmación
|
||||
- **Rollback:** Si falla ejecución, reversión de cambios
|
||||
|
||||
---
|
||||
|
||||
## Flujo Técnico
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A["Usuario: 'Vende 50 AAPL'"] -->|Chat Input| B["LLM Agent"]
|
||||
B -->|NLU| C["Interpretar Comando"]
|
||||
C -->|Validar Sintaxis| D{¿Válido?}
|
||||
D -->|No| E["Error: Comando inválido"]
|
||||
D -->|Sí| F["Preparar Preview"]
|
||||
F -->|GET /api/market/price| G["Obtener precio actual"]
|
||||
G -->|GET /api/portfolio/position| H["Obtener posición actual"]
|
||||
H -->|Calcular| I["Preparar detalles orden"]
|
||||
I -->|Mostrar| J["Preview en chat"]
|
||||
J -->|Usuario revisa| K{¿Confirma?}
|
||||
K -->|Cancela| L["Cancelado"]
|
||||
K -->|Confirma| M["POST /api/llm/execute-trade"]
|
||||
M -->|Validar riesgos| N{¿Límites OK?}
|
||||
N -->|No| O["Error: Límite excedido"]
|
||||
N -->|Sí| P["Ejecutar orden"]
|
||||
P -->|Trading System| Q["Orden procesada"]
|
||||
Q -->|Response| R["Mostrar confirmación"]
|
||||
R -->|Actualizar portafolio| S["Feedback final"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación Técnica
|
||||
|
||||
- [ ] Interpretar al menos 10 variantes de comandos
|
||||
- [ ] Validar todos los campos obligatorios
|
||||
- [ ] Conectar con Trading API backend
|
||||
- [ ] Mostrar preview antes de ejecutar
|
||||
- [ ] Requerir confirmación explícita
|
||||
- [ ] Validar límites de riesgo
|
||||
- [ ] Ejecutar orden correctamente
|
||||
- [ ] Mostrar confirmación con detalles
|
||||
- [ ] Registrar en historial
|
||||
- [ ] Manejo robusto de errores
|
||||
- [ ] Tests unitarios (80%+ coverage)
|
||||
- [ ] Tests E2E del flujo completo
|
||||
- [ ] QA aprobado
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
- RF-LLM-005: Herramienta Trading Execution
|
||||
- RF-LLM-006: Interpretación NLU avanzada
|
||||
- ET-LLM-005: Arquitectura Tools
|
||||
- OQI-001: Autenticación y autorización
|
||||
- OQI-003: Paper Trading System
|
||||
- REC-BACKEND-001: Trading API
|
||||
|
||||
---
|
||||
|
||||
## Definición de Done
|
||||
|
||||
- [ ] Endpoint `/api/llm/execute-trade` implementado
|
||||
- [ ] LLM Agent puede interpretar comandos
|
||||
- [ ] Preview de orden funcionando
|
||||
- [ ] Confirmación obligatoria
|
||||
- [ ] Validación de límites de riesgo
|
||||
- [ ] Ejecución de orden
|
||||
- [ ] Feedback post-ejecución
|
||||
- [ ] Manejo de errores
|
||||
- [ ] Restricción por plan
|
||||
- [ ] Tests unitarios
|
||||
- [ ] Tests E2E
|
||||
- [ ] QA aprobado
|
||||
- [ ] Documentación actualizada
|
||||
|
||||
---
|
||||
|
||||
*Historia de usuario - Sistema NEXUS*
|
||||
*Trading Platform*
|
||||
@ -0,0 +1,656 @@
|
||||
# ET-PFM-008: Especificación Frontend - Módulo Portfolio Manager
|
||||
|
||||
**Módulo:** OQI-008 Portfolio Manager
|
||||
**Documento:** ET-PFM-008
|
||||
**Estado:** A IMPLEMENTAR
|
||||
**Versión:** 1.0.0
|
||||
**Fecha de Creación:** 2026-01-25
|
||||
**Última Actualización:** 2026-01-25
|
||||
|
||||
---
|
||||
|
||||
## 📋 Resumen Ejecutivo
|
||||
|
||||
Este documento especifica la arquitectura, estructura de componentes y flujos de interfaz de usuario para el módulo Portfolio Manager en la plataforma de trading. **Este es un diseño a implementar que no cuenta actualmente con código base.**
|
||||
|
||||
El módulo proporciona funcionalidades de visualización, análisis y gestión de portafolios de inversión, incluyendo posiciones, métricas de riesgo, pruebas de estrés y generación de reportes.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Objetivos del Frontend
|
||||
|
||||
1. Proporcionar visualización clara y en tiempo real del estado del portafolio
|
||||
2. Facilitar análisis de riesgo mediante métricas consolidadas
|
||||
3. Permitir simulaciones de rebalanceo de posiciones
|
||||
4. Generar reportes analíticos exportables
|
||||
5. Integrar datos de múltiples fuentes de mercado
|
||||
|
||||
---
|
||||
|
||||
## 📄 Páginas Propuestas
|
||||
|
||||
### 1. PortfolioPage
|
||||
|
||||
**Ruta:** `/portfolio`
|
||||
**Descripción:** Página principal de gestión del portafolio
|
||||
|
||||
**Responsabilidades:**
|
||||
- Mostrar resumen ejecutivo del portafolio
|
||||
- Listar posiciones activas
|
||||
- Mostrar indicadores clave de desempeño (KPIs)
|
||||
- Permitir filtrado y búsqueda de posiciones
|
||||
- Facilitar acceso a rebalanceo
|
||||
|
||||
**Estructura:**
|
||||
```
|
||||
PortfolioPage
|
||||
├── PortfolioSummaryCard
|
||||
├── PerformanceChart
|
||||
├── PositionsTable
|
||||
│ ├── Filtros
|
||||
│ └── Paginación
|
||||
└── Acciones (Rebalancear, Exportar)
|
||||
```
|
||||
|
||||
**Datos Requeridos:**
|
||||
- Valor total del portafolio
|
||||
- Rentabilidad YTD
|
||||
- Rentabilidad acumulada
|
||||
- Composición por activo
|
||||
- Posiciones individuales con métricas
|
||||
|
||||
---
|
||||
|
||||
### 2. StressTestPage
|
||||
|
||||
**Ruta:** `/portfolio/stress-tests`
|
||||
**Descripción:** Página para ejecución y análisis de pruebas de estrés
|
||||
|
||||
**Responsabilidades:**
|
||||
- Crear nuevas pruebas de estrés
|
||||
- Ejecutar escenarios predefinidos
|
||||
- Visualizar resultados de stress tests
|
||||
- Comparar múltiples escenarios
|
||||
- Exportar análisis de sensibilidad
|
||||
|
||||
**Estructura:**
|
||||
```
|
||||
StressTestPage
|
||||
├── ScenarioSelector
|
||||
├── SimulationControls
|
||||
├── RiskMetricsPanel
|
||||
├── ResultsVisualization
|
||||
│ ├── ImpactChart
|
||||
│ └── SensitivityTable
|
||||
└── ExportOptions
|
||||
```
|
||||
|
||||
**Datos Requeridos:**
|
||||
- Escenarios disponibles
|
||||
- Parámetros de simulación
|
||||
- Resultados de ejecución
|
||||
- Metricas de sensibilidad
|
||||
|
||||
---
|
||||
|
||||
### 3. ReportsPage
|
||||
|
||||
**Ruta:** `/portfolio/reports`
|
||||
**Descripción:** Página de generación y consulta de reportes analíticos
|
||||
|
||||
**Responsabilidades:**
|
||||
- Listar reportes generados
|
||||
- Crear nuevos reportes personalizados
|
||||
- Visualizar reportes en tiempo real
|
||||
- Descargar reportes en múltiples formatos
|
||||
- Programar generación automática de reportes
|
||||
|
||||
**Estructura:**
|
||||
```
|
||||
ReportsPage
|
||||
├── ReportsList
|
||||
│ └── ReportFilters
|
||||
├── ReportGenerator
|
||||
│ ├── ParameterSelector
|
||||
│ └── FormatSelector
|
||||
├── ReportViewer
|
||||
│ ├── PaginationControls
|
||||
│ └── ExportOptions
|
||||
└── ScheduleManager
|
||||
```
|
||||
|
||||
**Datos Requeridos:**
|
||||
- Lista de reportes disponibles
|
||||
- Historico de reportes generados
|
||||
- Parámetros configurables
|
||||
- Formato de salida (PDF, Excel, CSV)
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Componentes Propuestos
|
||||
|
||||
### 1. PortfolioSummaryCard
|
||||
|
||||
**Tipo:** Componente Presentacional
|
||||
**Ubicación:** `src/components/portfolio/PortfolioSummaryCard.vue`
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface PortfolioSummaryProps {
|
||||
totalValue: number;
|
||||
ytdReturn: number;
|
||||
cumulativeReturn: number;
|
||||
currency: string;
|
||||
lastUpdated: Date;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Funcionalidades:**
|
||||
- Mostrar valor total del portafolio
|
||||
- Indicadores de rentabilidad (YTD, acumulada)
|
||||
- Tendencia visual (arriba/abajo)
|
||||
- Moneda de referencia
|
||||
- Timestamp de actualización
|
||||
- Estado de carga
|
||||
|
||||
**Emits:**
|
||||
- `refresh`: Solicitar actualización de datos
|
||||
|
||||
---
|
||||
|
||||
### 2. PositionsTable
|
||||
|
||||
**Tipo:** Componente Presentacional
|
||||
**Ubicación:** `src/components/portfolio/PositionsTable.vue`
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface PositionsTableProps {
|
||||
positions: Position[];
|
||||
loading?: boolean;
|
||||
sortBy?: string;
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
pageSize?: number;
|
||||
currentPage?: number;
|
||||
}
|
||||
|
||||
interface Position {
|
||||
id: string;
|
||||
symbol: string;
|
||||
name: string;
|
||||
quantity: number;
|
||||
entryPrice: number;
|
||||
currentPrice: number;
|
||||
currentValue: number;
|
||||
percentageChange: number;
|
||||
percentageOfPortfolio: number;
|
||||
sector?: string;
|
||||
lastUpdated: Date;
|
||||
}
|
||||
```
|
||||
|
||||
**Funcionalidades:**
|
||||
- Visualización de tabla de posiciones
|
||||
- Ordenamiento por columnas
|
||||
- Paginación configurable
|
||||
- Filtrado por sector/tipo
|
||||
- Búsqueda por símbolo o nombre
|
||||
- Indicadores visuales de desempeño
|
||||
- Acciones por fila (detalles, vender, rebalancear)
|
||||
|
||||
**Emits:**
|
||||
- `sort`: Cambio de ordenamiento
|
||||
- `page-change`: Cambio de página
|
||||
- `position-selected`: Selección de posición
|
||||
- `position-action`: Acción sobre posición
|
||||
|
||||
---
|
||||
|
||||
### 3. PerformanceChart
|
||||
|
||||
**Tipo:** Componente Presentacional
|
||||
**Ubicación:** `src/components/portfolio/PerformanceChart.vue`
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface PerformanceChartProps {
|
||||
data: PerformanceData[];
|
||||
timeframe?: 'D' | 'W' | 'M' | 'Y' | 'ALL';
|
||||
currency?: string;
|
||||
showComparison?: boolean;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
interface PerformanceData {
|
||||
date: Date;
|
||||
value: number;
|
||||
return: number;
|
||||
benchmark?: number;
|
||||
}
|
||||
```
|
||||
|
||||
**Funcionalidades:**
|
||||
- Gráfico de línea del desempeño del portafolio
|
||||
- Múltiples plazos (Día, Semana, Mes, Año, Todo)
|
||||
- Comparación con benchmark
|
||||
- Visualización de retorno absoluto y porcentual
|
||||
- Interactividad con tooltips
|
||||
- Exportación como imagen
|
||||
|
||||
**Emits:**
|
||||
- `timeframe-change`: Cambio de período
|
||||
- `range-select`: Selección de rango personalizado
|
||||
|
||||
---
|
||||
|
||||
### 4. RiskMetricsPanel
|
||||
|
||||
**Tipo:** Componente Presentacional
|
||||
**Ubicación:** `src/components/portfolio/RiskMetricsPanel.vue`
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface RiskMetricsPanelProps {
|
||||
metrics: RiskMetrics;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
interface RiskMetrics {
|
||||
volatility: number; // Volatilidad anualizada
|
||||
sharpeRatio: number; // Ratio de Sharpe
|
||||
sortinoRatio: number; // Ratio de Sortino
|
||||
valueAtRisk: number; // VaR 95%
|
||||
expectedShortfall: number; // CVaR (Expected Shortfall)
|
||||
beta: number; // Beta respecto a benchmark
|
||||
correlation: number; // Correlación con benchmark
|
||||
maxDrawdown: number; // Máxima caída histórica
|
||||
diversificationRatio: number; // Ratio de diversificación
|
||||
}
|
||||
```
|
||||
|
||||
**Funcionalidades:**
|
||||
- Visualización de métricas de riesgo clave
|
||||
- Indicadores de riesgo (bajo/medio/alto)
|
||||
- Comparación con benchmarks
|
||||
- Explicación de métricas (tooltips)
|
||||
- Gráficos de sensibilidad
|
||||
- Histórico de evolución de métricas
|
||||
|
||||
**Emits:**
|
||||
- `metric-selected`: Selección de métrica para análisis profundo
|
||||
- `learn-more`: Solicitud de explicación de métrica
|
||||
|
||||
---
|
||||
|
||||
### 5. RebalanceModal
|
||||
|
||||
**Tipo:** Componente Modal
|
||||
**Ubicación:** `src/components/portfolio/RebalanceModal.vue`
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface RebalanceModalProps {
|
||||
visible: boolean;
|
||||
portfolio: Portfolio;
|
||||
targetAllocation: AllocationTarget[];
|
||||
currentAllocation: AllocationCurrent[];
|
||||
constraints?: RebalanceConstraints;
|
||||
}
|
||||
|
||||
interface AllocationTarget {
|
||||
assetType: string;
|
||||
targetPercentage: number;
|
||||
minPercentage?: number;
|
||||
maxPercentage?: number;
|
||||
}
|
||||
|
||||
interface RebalanceConstraints {
|
||||
maxTransactionCost?: number;
|
||||
minTradeSize?: number;
|
||||
excludeAssets?: string[];
|
||||
allowNewPositions?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Funcionalidades:**
|
||||
- Mostrar asignación actual vs. objetivo
|
||||
- Calcular transacciones necesarias
|
||||
- Permitir selección de restricciones
|
||||
- Vista previa de cambios
|
||||
- Estimación de costos de transacción
|
||||
- Confirmación antes de ejecutar
|
||||
- Historial de rebalanceos
|
||||
|
||||
**Emits:**
|
||||
- `close`: Cierre del modal
|
||||
- `confirm`: Confirmación de rebalanceo
|
||||
- `cancel`: Cancelación de operación
|
||||
- `preview`: Solicitud de vista previa
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ Estructura de Store
|
||||
|
||||
### portfolioStore
|
||||
|
||||
**Ubicación:** `src/stores/portfolioStore.ts`
|
||||
|
||||
**Estado:**
|
||||
```typescript
|
||||
interface PortfolioState {
|
||||
portfolio: Portfolio | null;
|
||||
positions: Position[];
|
||||
selectedPosition: Position | null;
|
||||
performanceData: PerformanceData[];
|
||||
riskMetrics: RiskMetrics | null;
|
||||
stressTestResults: StressTestResult[] | null;
|
||||
reports: Report[];
|
||||
loading: {
|
||||
portfolio: boolean;
|
||||
positions: boolean;
|
||||
riskMetrics: boolean;
|
||||
stressTest: boolean;
|
||||
reports: boolean;
|
||||
};
|
||||
filters: {
|
||||
sector?: string;
|
||||
minValue?: number;
|
||||
maxValue?: number;
|
||||
searchTerm?: string;
|
||||
};
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface Portfolio {
|
||||
id: string;
|
||||
name: string;
|
||||
totalValue: number;
|
||||
ytdReturn: number;
|
||||
cumulativeReturn: number;
|
||||
currency: string;
|
||||
positions: Position[];
|
||||
lastUpdated: Date;
|
||||
}
|
||||
```
|
||||
|
||||
**Acciones Principales:**
|
||||
|
||||
```typescript
|
||||
// Portafolio
|
||||
fetchPortfolio(portfolioId: string): Promise<Portfolio>
|
||||
updatePortfolio(portfolioId: string, data: Partial<Portfolio>): Promise<Portfolio>
|
||||
selectPosition(position: Position): void
|
||||
clearSelection(): void
|
||||
|
||||
// Posiciones
|
||||
fetchPositions(portfolioId: string): Promise<Position[]>
|
||||
updatePosition(positionId: string, data: Partial<Position>): Promise<Position>
|
||||
deletePosition(positionId: string): Promise<void>
|
||||
addPosition(position: Position): Promise<Position>
|
||||
|
||||
// Desempeño
|
||||
fetchPerformanceData(portfolioId: string, timeframe: string): Promise<PerformanceData[]>
|
||||
|
||||
// Métricas de Riesgo
|
||||
fetchRiskMetrics(portfolioId: string): Promise<RiskMetrics>
|
||||
|
||||
// Pruebas de Estrés
|
||||
executeStressTest(portfolioId: string, scenario: StressScenario): Promise<StressTestResult>
|
||||
fetchStressTestResults(portfolioId: string): Promise<StressTestResult[]>
|
||||
|
||||
// Rebalanceo
|
||||
calculateRebalance(portfolioId: string, targets: AllocationTarget[]): Promise<RebalancePlan>
|
||||
executeRebalance(portfolioId: string, plan: RebalancePlan): Promise<RebalanceResult>
|
||||
|
||||
// Reportes
|
||||
fetchReports(portfolioId: string): Promise<Report[]>
|
||||
generateReport(portfolioId: string, config: ReportConfig): Promise<Report>
|
||||
exportReport(reportId: string, format: 'pdf' | 'excel' | 'csv'): Promise<Blob>
|
||||
|
||||
// Filtros
|
||||
setFilter(filterName: string, value: any): void
|
||||
clearFilters(): void
|
||||
```
|
||||
|
||||
**Getters:**
|
||||
```typescript
|
||||
getPortfolioValue(): number
|
||||
getTotalReturn(): number
|
||||
getPositionCount(): number
|
||||
getFilteredPositions(): Position[]
|
||||
getTopPerformer(): Position | null
|
||||
getTopLoser(): Position | null
|
||||
getRiskLevel(): 'low' | 'medium' | 'high'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Flujos de Datos
|
||||
|
||||
### Flujo 1: Carga Inicial de Portafolio
|
||||
|
||||
```
|
||||
PortfolioPage Montada
|
||||
↓
|
||||
[Dispatch] fetchPortfolio()
|
||||
↓
|
||||
API Backend (GET /api/portfolio/{id})
|
||||
↓
|
||||
[Commit] setPortfolio()
|
||||
↓
|
||||
PortfolioSummaryCard actualizado
|
||||
PositionsTable actualizado
|
||||
```
|
||||
|
||||
### Flujo 2: Búsqueda y Filtrado
|
||||
|
||||
```
|
||||
Usuario escribe en SearchInput
|
||||
↓
|
||||
[Dispatch] setFilter('searchTerm', value)
|
||||
↓
|
||||
Computed getFilteredPositions()
|
||||
↓
|
||||
PositionsTable re-renderiza
|
||||
```
|
||||
|
||||
### Flujo 3: Ejecución de Stress Test
|
||||
|
||||
```
|
||||
Usuario selecciona escenario
|
||||
↓
|
||||
[Dispatch] executeStressTest(scenario)
|
||||
↓
|
||||
API Backend (POST /api/portfolio/stress-test)
|
||||
↓
|
||||
[Commit] setStressTestResults()
|
||||
↓
|
||||
RiskMetricsPanel actualizado
|
||||
ResultsVisualization mostrada
|
||||
```
|
||||
|
||||
### Flujo 4: Rebalanceo
|
||||
|
||||
```
|
||||
Usuario abre RebalanceModal
|
||||
↓
|
||||
[Dispatch] calculateRebalance(targets)
|
||||
↓
|
||||
API Backend (POST /api/portfolio/calculate-rebalance)
|
||||
↓
|
||||
Modal muestra vista previa
|
||||
↓
|
||||
Usuario confirma
|
||||
↓
|
||||
[Dispatch] executeRebalance(plan)
|
||||
↓
|
||||
API Backend (POST /api/portfolio/execute-rebalance)
|
||||
↓
|
||||
[Dispatch] fetchPortfolio() para refrescar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Estilos y Temas
|
||||
|
||||
### Principios de Diseño
|
||||
|
||||
- **Diseño Responsivo:** Compatible con desktop, tablet y mobile
|
||||
- **Accesibilidad:** WCAG 2.1 AA como mínimo
|
||||
- **Consistencia:** Uso de design system corporativo
|
||||
- **Rendimiento:** Carga y animaciones fluidas
|
||||
|
||||
### Componentes de UI Requeridos
|
||||
|
||||
- Cards para resumen de métricas
|
||||
- Tablas con ordenamiento y paginación
|
||||
- Gráficos interactivos (Chart.js o Echarts)
|
||||
- Modals para acciones confirmables
|
||||
- Alerts y notificaciones Toast
|
||||
- Loaders y spinners
|
||||
- Iconografía consistente
|
||||
|
||||
---
|
||||
|
||||
## 📊 Integraciones Externas
|
||||
|
||||
### APIs Backend Requeridas
|
||||
|
||||
| Endpoint | Método | Descripción |
|
||||
|----------|--------|-------------|
|
||||
| `/api/portfolio/{id}` | GET | Obtener datos del portafolio |
|
||||
| `/api/portfolio/{id}` | PUT | Actualizar portafolio |
|
||||
| `/api/portfolio/{id}/positions` | GET | Listar posiciones |
|
||||
| `/api/portfolio/{id}/positions` | POST | Crear posición |
|
||||
| `/api/portfolio/position/{id}` | PUT | Actualizar posición |
|
||||
| `/api/portfolio/position/{id}` | DELETE | Eliminar posición |
|
||||
| `/api/portfolio/{id}/performance` | GET | Obtener datos de desempeño |
|
||||
| `/api/portfolio/{id}/risk-metrics` | GET | Calcular métricas de riesgo |
|
||||
| `/api/portfolio/stress-test` | POST | Ejecutar prueba de estrés |
|
||||
| `/api/portfolio/{id}/rebalance/calculate` | POST | Calcular plan de rebalanceo |
|
||||
| `/api/portfolio/{id}/rebalance/execute` | POST | Ejecutar rebalanceo |
|
||||
| `/api/portfolio/{id}/reports` | GET | Listar reportes |
|
||||
| `/api/portfolio/report/generate` | POST | Generar reporte |
|
||||
| `/api/portfolio/report/{id}/export` | GET | Exportar reporte |
|
||||
|
||||
### Servicios Externos
|
||||
|
||||
- **Market Data Provider:** Para precios en tiempo real
|
||||
- **Benchmark Data:** Índices de referencia (S&P 500, etc.)
|
||||
- **Reporting Engine:** Generación de reportes complejos
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuración y Constantes
|
||||
|
||||
### Variables de Entorno
|
||||
|
||||
```env
|
||||
VITE_PORTFOLIO_API_BASE_URL=http://localhost:3000/api
|
||||
VITE_CHART_LIBRARY=echarts|chart.js
|
||||
VITE_REPORT_FORMAT_DEFAULT=pdf
|
||||
VITE_AUTO_REFRESH_INTERVAL=30000
|
||||
VITE_MAX_POSITIONS_DISPLAY=50
|
||||
```
|
||||
|
||||
### Constantes de Aplicación
|
||||
|
||||
```typescript
|
||||
const CURRENCY_SYMBOLS: Record<string, string> = {
|
||||
USD: '$',
|
||||
EUR: '€',
|
||||
COP: '$',
|
||||
};
|
||||
|
||||
const TIMEFRAMES = ['D', 'W', 'M', 'Y', 'ALL'] as const;
|
||||
|
||||
const RISK_LEVELS = {
|
||||
low: { min: 0, max: 0.08 },
|
||||
medium: { min: 0.08, max: 0.15 },
|
||||
high: { min: 0.15, max: Infinity },
|
||||
};
|
||||
|
||||
const STRESS_SCENARIOS = [
|
||||
{ id: 'market-crash', label: 'Caída de Mercado (-10%)', intensity: -0.1 },
|
||||
{ id: 'rate-shock', label: 'Choque de Tasas (+2%)', intensity: 0.02 },
|
||||
{ id: 'geopolitical', label: 'Evento Geopolítico', intensity: -0.05 },
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 Responsive Design
|
||||
|
||||
### Breakpoints
|
||||
|
||||
- **Mobile:** < 640px
|
||||
- **Tablet:** 640px - 1024px
|
||||
- **Desktop:** > 1024px
|
||||
|
||||
### Ajustes por Pantalla
|
||||
|
||||
| Elemento | Mobile | Tablet | Desktop |
|
||||
|----------|--------|--------|---------|
|
||||
| PortfolioSummaryCard | Apilado | 2 columnas | 4 columnas |
|
||||
| PositionsTable | Horizontal scroll | Scroll limitado | Completo |
|
||||
| PerformanceChart | Altura reducida | Altura media | Altura completa |
|
||||
| RiskMetricsPanel | 1 columna | 2 columnas | 3 columnas |
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Seguridad
|
||||
|
||||
- **Autenticación:** Bearer token en headers
|
||||
- **Validación Frontend:** Validación de input antes de envío
|
||||
- **HTTPS Obligatorio:** En producción
|
||||
- **CORS:** Configuración restrictiva
|
||||
- **Rate Limiting:** Control de llamadas a API
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notas de Implementación
|
||||
|
||||
### Estado Actual
|
||||
|
||||
**Este documento describe un diseño que aún no ha sido implementado.** No existe código base en el repositorio para estos componentes.
|
||||
|
||||
### Próximos Pasos
|
||||
|
||||
1. Crear estructura de directorios de componentes
|
||||
2. Implementar store de Pinia con acciones básicas
|
||||
3. Desarrollar componentes en orden de dependencia
|
||||
4. Integrar con APIs backend
|
||||
5. Implementar pruebas unitarias
|
||||
6. Realizar pruebas de integración
|
||||
7. Optimizar rendimiento
|
||||
|
||||
### Consideraciones Técnicas
|
||||
|
||||
- **Framework:** Vue 3 (Composition API recomendada)
|
||||
- **State Management:** Pinia
|
||||
- **Gráficos:** Echarts o Chart.js según disponibilidad
|
||||
- **Validación:** Vee-validate
|
||||
- **Estilos:** TailwindCSS o SCSS
|
||||
- **Pruebas:** Vitest + Vue Test Utils
|
||||
|
||||
---
|
||||
|
||||
## 📚 Referencias Relacionadas
|
||||
|
||||
- OQI-008 Portfolio Manager - Especificación General
|
||||
- ET-PFM-001-backend.md - Especificación Backend
|
||||
- ET-PFM-002-database.md - Especificación Base de Datos
|
||||
- Arquitectura Trading Platform
|
||||
|
||||
---
|
||||
|
||||
## ✅ Control de Cambios
|
||||
|
||||
| Versión | Fecha | Autor | Cambios |
|
||||
|---------|-------|-------|---------|
|
||||
| 1.0.0 | 2026-01-25 | Sistema | Especificación inicial |
|
||||
|
||||
---
|
||||
|
||||
**Estado:** A IMPLEMENTAR | **Prioridad:** Alta | **Dependencias:** Backend Portfolio Manager (OQI-008)
|
||||
@ -0,0 +1,154 @@
|
||||
---
|
||||
id: "US-PFM-013"
|
||||
title: "Alerta de Rebalanceo"
|
||||
type: "User Story"
|
||||
status: "Pending"
|
||||
priority: "Media"
|
||||
epic: "OQI-008"
|
||||
project: "trading-platform"
|
||||
story_points: 5
|
||||
created_date: "2026-01-25"
|
||||
updated_date: "2026-01-25"
|
||||
---
|
||||
|
||||
# US-PFM-013: Alerta de Rebalanceo
|
||||
|
||||
**Épica:** OQI-008 - Portfolio Manager
|
||||
**Story Points:** 5 | **Prioridad:** P2
|
||||
|
||||
---
|
||||
|
||||
## Historia de Usuario
|
||||
|
||||
**Como** inversor
|
||||
**Quiero** recibir alertas cuando mi portafolio se desvíe de la asignación objetivo
|
||||
**Para** mantener mi estrategia de inversión y rebalancear cuando sea necesario
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
```gherkin
|
||||
Scenario: Configurar umbral de desviación
|
||||
Given soy usuario Pro/Premium
|
||||
And estoy en la sección de Alertas
|
||||
When configuro un umbral de desviación (ej: 5%)
|
||||
Then el sistema guarda la configuración
|
||||
And veo el umbral configurado en mi dashboard
|
||||
|
||||
Scenario: Recibir alerta cuando se excede el umbral
|
||||
Given tengo un umbral configurado de 5%
|
||||
And mi asignación objetivo es 60% acciones / 40% bonos
|
||||
When mi portafolio alcanza 65% acciones / 35% bonos
|
||||
Then recibo notificación push
|
||||
And recibo notificación email
|
||||
And veo badge de alerta en el dashboard
|
||||
|
||||
Scenario: Ver desviación actual del portafolio
|
||||
Given estoy en la sección de Alertas
|
||||
When consulto mi portafolio
|
||||
Then veo tabla con cada clase de activo
|
||||
And cada fila muestra: asignación objetivo, asignación actual, desviación %
|
||||
And las desviaciones > umbral se resaltan en rojo
|
||||
|
||||
Scenario: Recibir sugerencias de rebalanceo
|
||||
Given mi portafolio está fuera de balance
|
||||
When visualizo la desviación
|
||||
Then veo sugerencias automáticas de rebalanceo
|
||||
And cada sugerencia muestra: qué vender, qué comprar, montos
|
||||
And puedo ejecutar rebalanceo desde la sugerencia (1-click)
|
||||
|
||||
Scenario: Configurar canales de notificación
|
||||
Given estoy en configuración de alertas
|
||||
When selecciono canales (push, email, SMS)
|
||||
Then el sistema respeta mis preferencias
|
||||
And solo recibo en canales habilitados
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
- RF-PFM-007.1, RF-PFM-007.2, RF-PFM-007.3
|
||||
- ET-PFM-007
|
||||
- US-PFM-009 (Asignación objetivo)
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### POST /api/portfolio/alerts
|
||||
**Configurar umbral de alerta**
|
||||
```json
|
||||
{
|
||||
"userId": "uuid",
|
||||
"threshold": 5.0,
|
||||
"channels": ["push", "email"],
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
**Response:** 201 Created
|
||||
|
||||
### GET /api/portfolio/deviation
|
||||
**Obtener desviación actual del portafolio**
|
||||
```json
|
||||
{
|
||||
"userId": "uuid",
|
||||
"timestamp": "2026-01-25T10:30:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"assetClass": "stocks",
|
||||
"targetAllocation": 60.0,
|
||||
"currentAllocation": 65.2,
|
||||
"deviation": 5.2,
|
||||
"status": "alert"
|
||||
},
|
||||
{
|
||||
"assetClass": "bonds",
|
||||
"targetAllocation": 40.0,
|
||||
"currentAllocation": 34.8,
|
||||
"deviation": -5.2,
|
||||
"status": "alert"
|
||||
}
|
||||
],
|
||||
"isBalanced": false,
|
||||
"suggestions": [
|
||||
{
|
||||
"action": "sell",
|
||||
"assetClass": "stocks",
|
||||
"amount": 500.00
|
||||
},
|
||||
{
|
||||
"action": "buy",
|
||||
"assetClass": "bonds",
|
||||
"amount": 500.00
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Elementos de UI/UX
|
||||
|
||||
1. **Card de Alertas en Dashboard**
|
||||
- Mostrar estado: ✓ Balanceado / ⚠ Fuera de balance
|
||||
- Badge rojo si hay desviación > umbral
|
||||
- Botón "Configurar"
|
||||
|
||||
2. **Modal de Configuración**
|
||||
- Input slider: umbral 1-10%
|
||||
- Checkboxes: canales de notificación
|
||||
- Botón Guardar
|
||||
|
||||
3. **Página de Desviación**
|
||||
- Tabla con desgloses por activo
|
||||
- Gráfico de dona: objetivo vs actual
|
||||
- Sugerencias de rebalanceo con botón "Ejecutar"
|
||||
|
||||
4. **Notificaciones**
|
||||
- Push: "Tu portafolio se desvió 5.2% de tu objetivo. Rebalancear"
|
||||
- Email: Detalles completos + link a dashboard
|
||||
|
||||
---
|
||||
|
||||
*Historia de usuario - Sistema NEXUS*
|
||||
@ -0,0 +1,177 @@
|
||||
---
|
||||
id: "US-PFM-014"
|
||||
title: "Generar Reporte PDF"
|
||||
type: "User Story"
|
||||
status: "Ready"
|
||||
priority: "Alta"
|
||||
epic: "OQI-008"
|
||||
project: "trading-platform"
|
||||
story_points: 5
|
||||
created_date: "2026-01-25"
|
||||
updated_date: "2026-01-25"
|
||||
---
|
||||
|
||||
# US-PFM-014: Generar Reporte PDF
|
||||
|
||||
**Épica:** OQI-008 - Portfolio Manager
|
||||
**Story Points:** 5 | **Prioridad:** P0
|
||||
|
||||
---
|
||||
|
||||
## Historia de Usuario
|
||||
|
||||
**Como** inversor
|
||||
**Quiero** generar reportes PDF de mi portafolio
|
||||
**Para** análisis offline o compartir con asesores financieros
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
```gherkin
|
||||
Scenario: Seleccionar período de reporte
|
||||
Given estoy en la sección de reportes del portafolio
|
||||
And tengo datos de inversión disponibles
|
||||
When selecciono un rango de fechas
|
||||
Then puedo elegir período predeterminado (mensual, trimestral, anual)
|
||||
And puedo ingresar fechas personalizadas
|
||||
And el período es validado contra datos disponibles
|
||||
|
||||
Scenario: Generar reporte con gráficos
|
||||
Given seleccioné un período válido
|
||||
When solicito generar reporte PDF
|
||||
Then el documento incluye gráficos de:
|
||||
- Evolución del valor del portafolio (línea temporal)
|
||||
- Distribución por activos (pie chart)
|
||||
- Comparativo portafolio vs benchmark
|
||||
- Retorno acumulado vs rentabilidad mensual (barras)
|
||||
|
||||
Scenario: Incluir métricas de rendimiento
|
||||
Given el reporte está siendo generado
|
||||
When se incluyen métricas de rendimiento
|
||||
Then veo en el documento:
|
||||
- Rentabilidad total (%)
|
||||
- Rentabilidad anualizada
|
||||
- Volatilidad
|
||||
- Ratio de Sharpe
|
||||
- Máxima pérdida acumulada (drawdown)
|
||||
- Ratio ganancia/pérdida
|
||||
|
||||
Scenario: Detalle de posiciones
|
||||
Given el reporte incluye posiciones
|
||||
When se genera la sección de posiciones
|
||||
Then veo tabla con:
|
||||
- Código/Ticker del activo
|
||||
- Cantidad de unidades
|
||||
- Precio de compra/Precio actual
|
||||
- Valor actual en portafolio
|
||||
- Rendimiento (% y valor absoluto)
|
||||
- Fecha de compra
|
||||
|
||||
Scenario: Descargar reporte
|
||||
Given el reporte PDF está listo
|
||||
When hago clic en "Descargar"
|
||||
Then el archivo se descarga como: Portfolio_[fecha_inicio]_[fecha_fin]_[timestamp].pdf
|
||||
And el formato PDF es optimizado (tamaño <5MB)
|
||||
|
||||
Scenario: Enviar reporte por email
|
||||
Given el reporte PDF está listo
|
||||
When hago clic en "Enviar por Email"
|
||||
And ingreso dirección(es) de correo
|
||||
Then el PDF se envía a los destinatarios
|
||||
And recibo confirmación de envío
|
||||
And el destinatario recibe el correo con adjunto
|
||||
|
||||
Scenario: Reporte multi-cuenta
|
||||
Given tengo múltiples cuentas/portafolios
|
||||
When selecciono la opción "Consolidado"
|
||||
Then el reporte muestra:
|
||||
- Resumen general
|
||||
- Desglose por cada cuenta
|
||||
- Distribución consolidada
|
||||
- Rentabilidades combinadas
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoint
|
||||
|
||||
### POST /api/portfolio/reports/pdf
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"portfolioId": "uuid",
|
||||
"startDate": "2026-01-01",
|
||||
"endDate": "2026-01-25",
|
||||
"includeGraphs": true,
|
||||
"includeMetrics": true,
|
||||
"includePositions": true,
|
||||
"benchmark": "SPX",
|
||||
"downloadFormat": "pdf",
|
||||
"sendEmail": {
|
||||
"enabled": false,
|
||||
"recipients": []
|
||||
},
|
||||
"consolidated": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{
|
||||
"id": "report_uuid",
|
||||
"status": "generated",
|
||||
"downloadUrl": "/api/reports/download/report_uuid",
|
||||
"emailStatus": "pending|sent|failed",
|
||||
"generatedAt": "2026-01-25T10:30:00Z",
|
||||
"fileName": "Portfolio_2026-01-01_2026-01-25_20260125103000.pdf"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (202 - Async):**
|
||||
```json
|
||||
{
|
||||
"jobId": "job_uuid",
|
||||
"status": "processing",
|
||||
"estimatedTime": "30s",
|
||||
"checkUrl": "/api/portfolio/reports/pdf/status/job_uuid"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
- RF-PFM-007: Cálculo de métricas de rendimiento
|
||||
- RF-PFM-008: Integración con servicio de reportes
|
||||
- RF-PFM-009: Generación de gráficos embebidos en PDF
|
||||
- ET-PFM-007: Entidad ReportPDF
|
||||
- US-PFM-012: Reporte Fiscal (complementario)
|
||||
|
||||
---
|
||||
|
||||
## Notas Técnicas
|
||||
|
||||
- Usar librería `pdfkit` o `puppeteer` para generación
|
||||
- Gráficos embebidos con `chart.js` o `lightweight-charts`
|
||||
- Queue asíncrona para reportes grandes (>500MB portafolio)
|
||||
- Implementar retry automático en fallos de email
|
||||
- Almacenar reportes 30 días máximo
|
||||
- Validar tamaño de portafolio antes de generar
|
||||
|
||||
---
|
||||
|
||||
## Aceptación
|
||||
|
||||
- [ ] Endpoint POST creado y documentado
|
||||
- [ ] Generación de PDF con gráficos funcional
|
||||
- [ ] Descarga de archivos validada
|
||||
- [ ] Envío por email implementado
|
||||
- [ ] Tests unitarios y de integración
|
||||
- [ ] Documentación en Swagger
|
||||
- [ ] Manual de usuario actualizado
|
||||
|
||||
---
|
||||
|
||||
*Historia de usuario - Sistema NEXUS*
|
||||
@ -0,0 +1,633 @@
|
||||
# ET-MKT-003: Especificación Frontend - Módulo Marketplace
|
||||
|
||||
**Documento:** ET-MKT-003-frontend.md
|
||||
**Proyecto:** Trading Platform - OQI-009-Marketplace
|
||||
**Versión:** 1.0.0
|
||||
**Estado:** A IMPLEMENTAR
|
||||
**Última Actualización:** 2026-01-25
|
||||
|
||||
---
|
||||
|
||||
## 1. Descripción General
|
||||
|
||||
Este documento define la especificación de la capa frontend para el módulo Marketplace (OQI-009) del sistema Trading Platform. El módulo permite a usuarios explorar, comparar y adquirir productos financieros digitales (Signal Packs, sesiones de asesoramiento y complementos de visualización).
|
||||
|
||||
### Nota Importante
|
||||
**Esta especificación describe un diseño a implementar. No existe código frontend actual para este módulo.** Las páginas y componentes listados representan la propuesta de arquitectura que será desarrollada en futuras iteraciones.
|
||||
|
||||
### Productos Soportados
|
||||
- **Signal Packs**: Paquetes de señales de trading pre-configuradas
|
||||
- **Advisory Sessions**: Sesiones de asesoramiento con expertos
|
||||
- **Visualization Addons**: Complementos avanzados de visualización de mercados
|
||||
|
||||
---
|
||||
|
||||
## 2. Arquitectura Frontend
|
||||
|
||||
### 2.1 Stack Tecnológico
|
||||
```
|
||||
Framework: React 18.x
|
||||
Lenguaje: TypeScript 5.x
|
||||
Estilos: Tailwind CSS + CSS Modules
|
||||
Gestión Estado: Redux Toolkit
|
||||
HTTP Client: Axios
|
||||
Componentes UI: Headless UI / Radix UI
|
||||
Testing: Jest + React Testing Library
|
||||
```
|
||||
|
||||
### 2.2 Patrones Arquitectónicos
|
||||
- **Componentes Funcionales:** Todos los componentes deben ser funcionales con hooks
|
||||
- **Estructura por Features:** Organización en carpetas por feature (pages, components)
|
||||
- **Custom Hooks:** Lógica reutilizable extraída en hooks personalizados
|
||||
- **Context API:** Para estados compartidos entre componentes distantes
|
||||
- **Lazy Loading:** Carga diferida de componentes con React.lazy()
|
||||
|
||||
---
|
||||
|
||||
## 3. Estructura de Directorios
|
||||
|
||||
```
|
||||
src/
|
||||
├── features/
|
||||
│ └── marketplace/
|
||||
│ ├── pages/
|
||||
│ │ ├── MarketplacePage/
|
||||
│ │ ├── ProductDetailPage/
|
||||
│ │ ├── SignalPacksPage/
|
||||
│ │ └── AdvisoryPage/
|
||||
│ ├── components/
|
||||
│ │ ├── ProductCard/
|
||||
│ │ ├── SignalPackCard/
|
||||
│ │ ├── AdvisorCard/
|
||||
│ │ ├── BookingModal/
|
||||
│ │ ├── FilterBar/
|
||||
│ │ ├── ProductGallery/
|
||||
│ │ └── ReviewSection/
|
||||
│ ├── hooks/
|
||||
│ │ ├── useMarketplaceFilters.ts
|
||||
│ │ ├── useProductCart.ts
|
||||
│ │ └── useAdvisorBooking.ts
|
||||
│ ├── services/
|
||||
│ │ ├── marketplaceApi.ts
|
||||
│ │ ├── productApi.ts
|
||||
│ │ └── advisoryApi.ts
|
||||
│ ├── types/
|
||||
│ │ └── marketplace.types.ts
|
||||
│ ├── store/
|
||||
│ │ └── marketplaceSlice.ts
|
||||
│ └── styles/
|
||||
│ └── marketplace.module.css
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Páginas Propuestas
|
||||
|
||||
### 4.1 MarketplacePage
|
||||
**Ruta:** `/marketplace`
|
||||
**Responsabilidad:** Landing principal del marketplace con vista general de todos los productos
|
||||
|
||||
#### Características:
|
||||
- Hero section con introducción al marketplace
|
||||
- Buscador global de productos
|
||||
- Filtros por categoría (Signal Packs, Advisory, Addons)
|
||||
- Grid de productos destacados
|
||||
- Testimonios de usuarios
|
||||
- Llamadas a la acción (CTA)
|
||||
|
||||
#### Responsive Design:
|
||||
- Desktop: Grid 4 columnas
|
||||
- Tablet: Grid 2 columnas
|
||||
- Mobile: Grid 1 columna
|
||||
|
||||
---
|
||||
|
||||
### 4.2 ProductDetailPage
|
||||
**Ruta:** `/marketplace/product/:productId`
|
||||
**Responsabilidad:** Vista detallada de un producto individual
|
||||
|
||||
#### Contenido Esperado:
|
||||
- Galería de imágenes/screenshots
|
||||
- Nombre, descripción y precio
|
||||
- Especificaciones técnicas
|
||||
- Comparativa con productos similares
|
||||
- Sección de reviews y ratings
|
||||
- Botón de compra/carrito
|
||||
- Información del vendedor
|
||||
- FAQ relacionadas
|
||||
|
||||
#### Datos a Cargar:
|
||||
- Detalles del producto (API GET `/api/products/:id`)
|
||||
- Reviews (API GET `/api/products/:id/reviews`)
|
||||
- Productos relacionados (API GET `/api/products/:id/related`)
|
||||
|
||||
---
|
||||
|
||||
### 4.3 SignalPacksPage
|
||||
**Ruta:** `/marketplace/signal-packs`
|
||||
**Responsabilidad:** Página especializada para Signal Packs con funcionalidades específicas
|
||||
|
||||
#### Características:
|
||||
- Filtros avanzados (tipo de señal, precisión, timeframe)
|
||||
- Comparador de paquetes
|
||||
- Gráfico de performance histórico
|
||||
- Tabla de estadísticas en tiempo real
|
||||
- Demos interactivos
|
||||
- Botón "Probar Gratis" y "Comprar Ahora"
|
||||
|
||||
#### Componentes Específicos:
|
||||
- `SignalPackCard` (extendido)
|
||||
- `PerformanceChart`
|
||||
- `ComparisonTable`
|
||||
- `LiveStatisticsWidget`
|
||||
|
||||
---
|
||||
|
||||
### 4.4 AdvisoryPage
|
||||
**Ruta:** `/marketplace/advisory`
|
||||
**Responsabilidad:** Página para sesiones de asesoramiento con expertos
|
||||
|
||||
#### Características:
|
||||
- Directorio de asesores con filtros
|
||||
- Calendarios de disponibilidad
|
||||
- Precios por tipo de sesión
|
||||
- Credenciales y especialidades de asesores
|
||||
- Sistema de booking integrado
|
||||
- Historial de sesiones del usuario
|
||||
- Funcionalidad de reseñas post-sesión
|
||||
|
||||
#### Integración Modal:
|
||||
- Modal de booking (`BookingModal`) se dispara desde tarjetas de asesores
|
||||
- Confirmación y pago dentro del modal
|
||||
|
||||
---
|
||||
|
||||
## 5. Componentes Propuestos
|
||||
|
||||
### 5.1 ProductCard
|
||||
**Ubicación:** `components/ProductCard/`
|
||||
|
||||
```typescript
|
||||
interface ProductCardProps {
|
||||
product: Product;
|
||||
onViewDetails: (productId: string) => void;
|
||||
onAddToCart?: (product: Product) => void;
|
||||
variant?: 'default' | 'compact' | 'featured';
|
||||
}
|
||||
```
|
||||
|
||||
**Elementos:**
|
||||
- Imagen de portada
|
||||
- Nombre del producto
|
||||
- Rating (estrellas + número de reviews)
|
||||
- Precio y descuento (si aplica)
|
||||
- Descripción breve (2-3 líneas)
|
||||
- Etiquetas (categoría, trending)
|
||||
- Botones de acción (Ver detalles, Agregar al carrito)
|
||||
|
||||
**Estados:**
|
||||
- Default: Producto disponible
|
||||
- Loading: Cargando datos
|
||||
- Disabled: Producto no disponible
|
||||
- Favorited: Producto agregado a favoritos
|
||||
|
||||
---
|
||||
|
||||
### 5.2 SignalPackCard
|
||||
**Ubicación:** `components/SignalPackCard/`
|
||||
**Extends:** ProductCard (con extensiones específicas)
|
||||
|
||||
```typescript
|
||||
interface SignalPackCardProps extends ProductCardProps {
|
||||
winRate: number;
|
||||
totalSignals: number;
|
||||
avgReturnPerSignal: number;
|
||||
lastSignalDate: Date;
|
||||
difficultyLevel: 'beginner' | 'intermediate' | 'advanced';
|
||||
}
|
||||
```
|
||||
|
||||
**Elementos Adicionales:**
|
||||
- Badge de dificultad
|
||||
- Win rate en porcentaje
|
||||
- Total de señales generadas
|
||||
- Retorno promedio por señal
|
||||
- Fecha de última señal
|
||||
- Gráfico mini de performance (sparkline)
|
||||
|
||||
---
|
||||
|
||||
### 5.3 AdvisorCard
|
||||
**Ubicación:** `components/AdvisorCard/`
|
||||
|
||||
```typescript
|
||||
interface AdvisorCardProps {
|
||||
advisor: Advisor;
|
||||
onBookSession: (advisorId: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
**Elementos:**
|
||||
- Foto de perfil
|
||||
- Nombre y especialidad
|
||||
- Rating y número de sesiones completadas
|
||||
- Descripción breve
|
||||
- Certificaciones y credenciales (badges)
|
||||
- Precio por hora/sesión
|
||||
- Disponibilidad (horarios)
|
||||
- Botón "Reservar Ahora"
|
||||
- Link a perfil completo
|
||||
|
||||
---
|
||||
|
||||
### 5.4 BookingModal
|
||||
**Ubicación:** `components/BookingModal/`
|
||||
|
||||
```typescript
|
||||
interface BookingModalProps {
|
||||
advisor: Advisor;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: (booking: BookingRequest) => Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
**Flujo:**
|
||||
1. **Selección de Sesión**: Tipo y duración
|
||||
2. **Calendario**: Seleccionar fecha/hora disponible
|
||||
3. **Detalles Personales**: Confirmar información del usuario
|
||||
4. **Resumen de Pago**: Mostrar total y método de pago
|
||||
5. **Confirmación**: Enviar reserva y mostrar confirmación
|
||||
|
||||
**Validaciones:**
|
||||
- Fecha/hora no pueden ser pasadas
|
||||
- Usuario debe estar autenticado
|
||||
- Validar disponibilidad en tiempo real
|
||||
- Procesar pago antes de confirmar
|
||||
|
||||
---
|
||||
|
||||
### 5.5 Componentes Auxiliares
|
||||
|
||||
#### FilterBar
|
||||
**Ubicación:** `components/FilterBar/`
|
||||
**Responsabilidad:** Filtros dinámicos reutilizables
|
||||
|
||||
```typescript
|
||||
interface FilterBarProps {
|
||||
filters: FilterDefinition[];
|
||||
onApply: (appliedFilters: Record<string, any>) => void;
|
||||
onReset: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### ProductGallery
|
||||
**Ubicación:** `components/ProductGallery/`
|
||||
**Responsabilidad:** Galería de imágenes con zoom y thumbnails
|
||||
|
||||
#### ReviewSection
|
||||
**Ubicación:** `components/ReviewSection/`
|
||||
**Responsabilidad:** Mostrar y permitir crear nuevas reseñas
|
||||
|
||||
---
|
||||
|
||||
## 6. Flujos de Usuario Principales
|
||||
|
||||
### 6.1 Flujo: Explorar y Comprar Signal Pack
|
||||
```
|
||||
MarketplacePage
|
||||
→ (filtrar/buscar)
|
||||
→ SignalPacksPage
|
||||
→ ProductDetailPage (Signal Pack)
|
||||
→ Carrito
|
||||
→ Checkout
|
||||
```
|
||||
|
||||
### 6.2 Flujo: Reservar Sesión de Asesoramiento
|
||||
```
|
||||
MarketplacePage
|
||||
→ AdvisoryPage
|
||||
→ (filtrar asesores)
|
||||
→ AdvisorCard
|
||||
→ BookingModal (se abre)
|
||||
→ Confirmación y Pago
|
||||
→ Página de confirmación
|
||||
```
|
||||
|
||||
### 6.3 Flujo: Ver Detalles de Complemento
|
||||
```
|
||||
MarketplacePage
|
||||
→ ProductDetailPage (Addon)
|
||||
→ Comparar con similares
|
||||
→ Agregar al carrito
|
||||
→ Checkout
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Integración con API
|
||||
|
||||
### 7.1 Endpoints Consumidos
|
||||
```
|
||||
GET /api/marketplace/products # Listar productos con filtros
|
||||
GET /api/marketplace/products/:id # Detalle de producto
|
||||
GET /api/marketplace/products/:id/reviews
|
||||
POST /api/marketplace/cart # Agregar al carrito
|
||||
GET /api/marketplace/advisors # Listar asesores
|
||||
POST /api/marketplace/bookings # Crear booking
|
||||
GET /api/user/orders # Historial de compras
|
||||
```
|
||||
|
||||
### 7.2 Servicios API (Custom Hooks)
|
||||
```typescript
|
||||
// marketplaceApi.ts
|
||||
export const marketplaceApi = {
|
||||
getProducts: (filters?: ProductFilters) => Promise<Product[]>,
|
||||
getProductDetail: (id: string) => Promise<Product>,
|
||||
getAdvisors: (filters?: AdvisorFilters) => Promise<Advisor[]>,
|
||||
createBooking: (booking: BookingRequest) => Promise<BookingResponse>,
|
||||
getReviews: (productId: string) => Promise<Review[]>,
|
||||
submitReview: (review: ReviewSubmission) => Promise<Review>,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Estado Global (Redux)
|
||||
|
||||
### 8.1 Slices del Store
|
||||
```typescript
|
||||
// marketplaceSlice.ts
|
||||
{
|
||||
marketplace: {
|
||||
products: Product[],
|
||||
selectedProduct: Product | null,
|
||||
advisors: Advisor[],
|
||||
cart: CartItem[],
|
||||
filters: {
|
||||
category: string[],
|
||||
priceRange: [number, number],
|
||||
rating: number,
|
||||
// ... más filtros
|
||||
},
|
||||
loading: boolean,
|
||||
error: string | null,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 Actions Principales
|
||||
- `setProducts(products)`
|
||||
- `setSelectedProduct(product)`
|
||||
- `addToCart(product)`
|
||||
- `removeFromCart(productId)`
|
||||
- `updateFilters(filters)`
|
||||
- `setLoading(boolean)`
|
||||
|
||||
---
|
||||
|
||||
## 9. Validaciones Frontend
|
||||
|
||||
### 9.1 Búsqueda y Filtros
|
||||
- Campo de búsqueda: mínimo 3 caracteres
|
||||
- Filtros deben reflejar datos del backend
|
||||
- Debounce en búsqueda (300ms)
|
||||
|
||||
### 9.2 Booking de Sesiones
|
||||
- Fecha no puede ser en el pasado
|
||||
- Hora debe coincidir con disponibilidad del asesor
|
||||
- Usuario debe estar autenticado
|
||||
- Validar saldo/método de pago
|
||||
|
||||
### 9.3 Carrito
|
||||
- Validar cantidad disponible
|
||||
- Evitar agregar mismo producto dos veces (aumentar cantidad)
|
||||
- Mostrar total actualizado
|
||||
|
||||
### 9.4 Formularios
|
||||
- Validación en tiempo real
|
||||
- Mensajes de error claros
|
||||
- Estados loading en botones de envío
|
||||
|
||||
---
|
||||
|
||||
## 10. Performance y Optimización
|
||||
|
||||
### 10.1 Técnicas
|
||||
- **Code Splitting:** Lazy loading de páginas con React.lazy()
|
||||
- **Memoization:** React.memo() para componentes puros
|
||||
- **Image Optimization:** Optimizar imágenes, WebP, lazy loading
|
||||
- **Virtualization:** Virtualizaciónlist para listas largas (si aplica)
|
||||
- **Bundle Analysis:** Usar webpack-bundle-analyzer
|
||||
|
||||
### 10.2 Métricas Objetivo
|
||||
- LCP (Largest Contentful Paint): < 2.5s
|
||||
- FID (First Input Delay): < 100ms
|
||||
- CLS (Cumulative Layout Shift): < 0.1
|
||||
|
||||
---
|
||||
|
||||
## 11. Accesibilidad (A11y)
|
||||
|
||||
### 11.1 Estándares
|
||||
- WCAG 2.1 Nivel AA
|
||||
- Navegación por teclado
|
||||
- Contraste mínimo 4.5:1
|
||||
- Labels explícitos en formularios
|
||||
- ARIA labels donde sea necesario
|
||||
|
||||
### 11.2 Implementación
|
||||
```typescript
|
||||
// Ejemplo: Botón accesible
|
||||
<button
|
||||
aria-label="Agregar producto al carrito"
|
||||
className="btn-primary"
|
||||
onClick={handleAddToCart}
|
||||
>
|
||||
Agregar al Carrito
|
||||
</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Testing
|
||||
|
||||
### 12.1 Cobertura Objetivo
|
||||
- Unit Tests: 80% de componentes
|
||||
- Integration Tests: Flujos principales
|
||||
- E2E Tests: Casos de compra y booking
|
||||
|
||||
### 12.2 Ejemplos de Pruebas
|
||||
```typescript
|
||||
// ProductCard.test.tsx
|
||||
describe('ProductCard', () => {
|
||||
it('should render product information', () => {
|
||||
const product = mockProduct();
|
||||
render(<ProductCard product={product} onViewDetails={jest.fn()} />);
|
||||
expect(screen.getByText(product.name)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onViewDetails when clicked', () => {
|
||||
const onViewDetails = jest.fn();
|
||||
const product = mockProduct();
|
||||
render(<ProductCard product={product} onViewDetails={onViewDetails} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /ver detalles/i }));
|
||||
expect(onViewDetails).toHaveBeenCalledWith(product.id);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Tipo de Datos Principales
|
||||
|
||||
### 13.1 Interfaces TypeScript
|
||||
|
||||
```typescript
|
||||
// marketplace.types.ts
|
||||
|
||||
interface Product {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
price: number;
|
||||
discount?: number;
|
||||
category: 'signal_pack' | 'advisory' | 'addon';
|
||||
rating: number;
|
||||
reviewCount: number;
|
||||
image: string;
|
||||
images?: string[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface SignalPack extends Product {
|
||||
winRate: number;
|
||||
totalSignals: number;
|
||||
avgReturnPerSignal: number;
|
||||
difficultyLevel: 'beginner' | 'intermediate' | 'advanced';
|
||||
lastSignalDate: Date;
|
||||
}
|
||||
|
||||
interface Advisor {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
photo: string;
|
||||
specialty: string;
|
||||
bio: string;
|
||||
rating: number;
|
||||
completedSessions: number;
|
||||
pricePerHour: number;
|
||||
certifications: Certification[];
|
||||
availability: TimeSlot[];
|
||||
}
|
||||
|
||||
interface BookingRequest {
|
||||
advisorId: string;
|
||||
userId: string;
|
||||
sessionType: 'consultation' | 'strategy_review';
|
||||
duration: 30 | 60 | 90; // minutos
|
||||
scheduledDate: Date;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
interface Review {
|
||||
id: string;
|
||||
productId: string;
|
||||
userId: string;
|
||||
rating: number;
|
||||
title: string;
|
||||
comment: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Responsive Design Breakpoints
|
||||
|
||||
```css
|
||||
/* Tailwind / CSS */
|
||||
Mobile: < 640px (sm)
|
||||
Tablet: 640px+ (md)
|
||||
Desktop: 1024px+ (lg)
|
||||
Wide: 1280px+ (xl)
|
||||
UltraWide: 1536px+ (2xl)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15. Roadmap de Implementación
|
||||
|
||||
### Fase 1: Estructura Base (Sprint 1-2)
|
||||
- [ ] Setup proyecto React + TypeScript
|
||||
- [ ] Crear estructura de directorios
|
||||
- [ ] Implementar routing y layout base
|
||||
- [ ] Configurar Redux store
|
||||
|
||||
### Fase 2: Componentes (Sprint 3-4)
|
||||
- [ ] ProductCard, SignalPackCard, AdvisorCard
|
||||
- [ ] FilterBar, ProductGallery
|
||||
- [ ] BookingModal
|
||||
|
||||
### Fase 3: Páginas (Sprint 5-6)
|
||||
- [ ] MarketplacePage
|
||||
- [ ] ProductDetailPage
|
||||
- [ ] SignalPacksPage
|
||||
- [ ] AdvisoryPage
|
||||
|
||||
### Fase 4: Integraciones y Polish (Sprint 7-8)
|
||||
- [ ] Consumir APIs
|
||||
- [ ] Testing
|
||||
- [ ] Optimización
|
||||
- [ ] Despliegue
|
||||
|
||||
---
|
||||
|
||||
## 16. Referencias y Documentos Relacionados
|
||||
|
||||
- **ET-MKT-001-database.md**: Esquema de base de datos
|
||||
- **ET-MKT-002-api.md**: Especificación de API REST
|
||||
- **Guía de Componentes**: (a crear en futuro)
|
||||
- **Guía de Testing**: (a crear en futuro)
|
||||
|
||||
---
|
||||
|
||||
## 17. Notas de Desarrollo
|
||||
|
||||
### Prácticas Recomendadas
|
||||
1. Mantener componentes pequeños y enfocados
|
||||
2. Usar TypeScript para toda la capa frontend
|
||||
3. Escribir tests mientras se desarrolla
|
||||
4. Revisar código antes de merge
|
||||
5. Documentar componentes complejos
|
||||
|
||||
### Dependencias NPM Esperadas
|
||||
```json
|
||||
{
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@reduxjs/toolkit": "^1.9.x",
|
||||
"react-redux": "^8.1.x",
|
||||
"axios": "^1.4.x",
|
||||
"tailwindcss": "^3.3.x",
|
||||
"typescript": "^5.1.x"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 18. Cambios Pendientes
|
||||
|
||||
Este documento será actualizado cuando:
|
||||
- Se inicie implementación de componentes
|
||||
- Se definan cambios en UX/UI
|
||||
- Se agreguen nuevas funcionalidades
|
||||
- Se identifiquen patrones de optimización
|
||||
|
||||
---
|
||||
|
||||
**Documento Generado:** 2026-01-25
|
||||
**Próxima Revisión:** Tras completar Fase 1 de implementación
|
||||
**Estado:** BORRADOR - A IMPLEMENTAR
|
||||
@ -0,0 +1,48 @@
|
||||
# TASK-2026-01-25-FRONTEND-ANALYSIS
|
||||
|
||||
**Estado:** COMPLETADA
|
||||
**Fecha:** 2026-01-25
|
||||
**Tipo:** ANALYSIS
|
||||
|
||||
## Resumen
|
||||
|
||||
Analisis y documentacion detallada del frontend de trading-platform.
|
||||
|
||||
## Entregables Completados
|
||||
|
||||
### FASE 1: Inventarios (3/3)
|
||||
- MASTER_INVENTORY.yml actualizado
|
||||
- FRONTEND_INVENTORY.yml completado (48 componentes)
|
||||
- BACKEND_INVENTORY.yml completado (trading + ML modules)
|
||||
|
||||
### FASE 2: Especificaciones Frontend (5/5)
|
||||
- ET-AUTH-006-frontend.md (OQI-001)
|
||||
- ET-ML-008-frontend.md (OQI-006)
|
||||
- ET-LLM-007-frontend.md (OQI-007)
|
||||
- ET-PFM-008-frontend.md (OQI-008)
|
||||
- ET-MKT-003-frontend.md (OQI-009)
|
||||
|
||||
### FASE 3: Historias de Usuario (8 nuevas)
|
||||
- US-AUTH-013, US-AUTH-014
|
||||
- US-ML-008, US-ML-009, US-ML-010
|
||||
- US-LLM-011
|
||||
- US-PFM-013, US-PFM-014
|
||||
|
||||
### FASE 4: Coherencia API-Frontend
|
||||
- API-FRONTEND-COVERAGE-MATRIX.md (85% cobertura)
|
||||
- FILE-GENERATION-SPEC.md
|
||||
|
||||
### FASE 5: Dependencias
|
||||
- DEPENDENCY-GRAPH.yml actualizado con epicas
|
||||
|
||||
## Metricas
|
||||
|
||||
| Metrica | Valor |
|
||||
|---------|-------|
|
||||
| Modulos con doc frontend | 10/10 (100%) |
|
||||
| Cobertura API-Frontend | 85% |
|
||||
| Historias de usuario nuevas | 8 |
|
||||
| Archivos creados/modificados | 20+ |
|
||||
|
||||
---
|
||||
*Completado: 2026-01-25 | Sistema: SIMCO v4.0.0*
|
||||
@ -6,8 +6,8 @@ created: "2026-01-24"
|
||||
updated: "2026-01-24"
|
||||
|
||||
resumen:
|
||||
total_tareas: 0
|
||||
completadas: 0
|
||||
total_tareas: 1
|
||||
completadas: 1
|
||||
en_progreso: 0
|
||||
pendientes: 0
|
||||
|
||||
@ -15,9 +15,21 @@ formato_id:
|
||||
patron: "TASK-{YYYY-MM-DD}-{NNN}"
|
||||
ejemplo: "TASK-2026-01-24-001"
|
||||
|
||||
por_fecha: {}
|
||||
por_fecha:
|
||||
2026-01-25:
|
||||
- id: TASK-2026-01-25-FRONTEND-ANALYSIS
|
||||
titulo: "Analisis y Documentacion Frontend"
|
||||
estado: COMPLETADA
|
||||
tipo: ANALYSIS
|
||||
|
||||
tareas_activas: []
|
||||
|
||||
tareas_completadas:
|
||||
- id: TASK-2026-01-25-FRONTEND-ANALYSIS
|
||||
fecha_inicio: "2026-01-25"
|
||||
fecha_fin: "2026-01-25"
|
||||
entregables: 20+
|
||||
|
||||
instrucciones:
|
||||
crear_tarea: |
|
||||
1. Crear carpeta YYYY-MM-DD/ si no existe
|
||||
|
||||
Loading…
Reference in New Issue
Block a user