diff --git a/docs/02-definicion-modulos/OQI-001-fundamentos-auth/especificaciones/ET-AUTH-006-frontend.md b/docs/02-definicion-modulos/OQI-001-fundamentos-auth/especificaciones/ET-AUTH-006-frontend.md new file mode 100644 index 0000000..62d53f0 --- /dev/null +++ b/docs/02-definicion-modulos/OQI-001-fundamentos-auth/especificaciones/ET-AUTH-006-frontend.md @@ -0,0 +1,1130 @@ +--- +id: "ET-AUTH-006" +title: "Frontend Specification for Auth Module" +type: "Specification" +status: "Done" +rf_parent: "RF-AUTH-003" +epic: "OQI-001" +version: "1.0" +created_date: "2026-01-25" +updated_date: "2026-01-25" +--- + +# ET-AUTH-006: Especificación Técnica - Frontend Auth + +**Version:** 1.0.0 +**Fecha:** 2026-01-25 +**Estado:** ✅ Implementado +**Épica:** [OQI-001](../_MAP.md) + +--- + +## Resumen + +Esta especificación detalla la implementación frontend del módulo de autenticación de Trading Platform, incluyendo páginas, componentes reutilizables, servicios de API y flujos de usuario. + +--- + +## Stack Tecnológico + +| Componente | Tecnología | Versión | +|------------|-----------|---------| +| Framework | React | 18.2.0 | +| Build Tool | Vite | 6.2.0 | +| Router | React Router | 6.x | +| State Management | Zustand | 4.4.7 | +| Data Fetching | TanStack Query | 5.14.0 | +| Styling | CSS Modules + Tailwind | 3.x | +| Forms | React Hook Form | 7.x | +| Validación | Zod | 3.x | +| HTTP Client | Axios | 1.x | +| Auth Tokens | JWT | - | + +--- + +## Arquitectura Frontend + +``` +apps/frontend/ +├── src/ +│ ├── pages/ +│ │ ├── auth/ +│ │ │ ├── Login.tsx +│ │ │ ├── Register.tsx +│ │ │ ├── ForgotPassword.tsx +│ │ │ ├── ResetPassword.tsx +│ │ │ ├── VerifyEmail.tsx +│ │ │ └── AuthCallback.tsx +│ ├── components/ +│ │ ├── auth/ +│ │ │ ├── SocialLoginButtons.tsx +│ │ │ ├── PhoneLoginForm.tsx +│ │ │ ├── TwoFactorForm.tsx +│ │ │ └── PasswordStrengthMeter.tsx +│ ├── services/ +│ │ ├── api/ +│ │ │ └── authService.ts +│ ├── stores/ +│ │ └── authStore.ts +│ ├── hooks/ +│ │ ├── useAuth.ts +│ │ ├── useAuthForm.ts +│ │ └── useSessionManager.ts +│ ├── types/ +│ │ └── auth.ts +│ └── utils/ +│ ├── tokenManager.ts +│ └── validators.ts +``` + +--- + +## Páginas de Autenticación + +### 1. Login Page + +**Ruta:** `/auth/login` + +**Responsabilidades:** +- Autenticación con email/contraseña +- Redirección a OAuth (Google, GitHub, etc.) +- Recuperación de contraseña +- Manejo de 2FA si está activado +- Recordar usuario + +**Flujo:** + +``` +1. Usuario ingresa email y contraseña +2. Validar formato de datos +3. Llamar a /api/v1/auth/login +4. Si 2FA está activado: + → Mostrar formulario de verificación 2FA + → Usuario ingresa código + → Llamar a /api/v1/auth/2fa/verify +5. Guardar tokens en localStorage/sessionStorage +6. Actualizar auth store (Zustand) +7. Redirigir a /dashboard +``` + +**Props/State:** + +```typescript +interface LoginPageProps { + redirectTo?: string; + onSuccess?: () => void; +} + +interface LoginFormData { + email: string; + password: string; + rememberMe: boolean; +} +``` + +**Manejo de Errores:** + +| Código | Error | Acción | +|--------|-------|--------| +| INVALID_CREDENTIALS | Credenciales inválidas | Mostrar mensaje de error | +| EMAIL_NOT_VERIFIED | Email no verificado | Botón para reenviar verificación | +| ACCOUNT_LOCKED | Cuenta bloqueada | Mostrar tiempo de espera | +| ACCOUNT_SUSPENDED | Cuenta suspendida | Mostrar mensaje de contacto | +| RATE_LIMIT | Demasiados intentos | Desactivar botón por tiempo | + +--- + +### 2. Register Page + +**Ruta:** `/auth/register` + +**Responsabilidades:** +- Registro con email/contraseña +- Validación de contraseña fuerte +- Aceptación de términos y condiciones +- Registro social (opcional) +- Envío de email de verificación + +**Flujo:** + +``` +1. Usuario completa formulario: + - Email + - Contraseña + - Confirmar contraseña + - Nombre completo + - Aceptar términos +2. Validar localmente (Zod) +3. Mostrar indicador de fortaleza de contraseña +4. Llamar a /api/v1/auth/register +5. Mostrar confirmación de email enviado +6. Redirigir a /auth/verify-email después de 3 segundos +``` + +**Props/State:** + +```typescript +interface RegisterFormData { + email: string; + password: string; + confirmPassword: string; + firstName: string; + lastName: string; + acceptTerms: boolean; + acceptNewsletter?: boolean; +} + +interface PasswordRequirements { + minLength: boolean; + hasUppercase: boolean; + hasLowercase: boolean; + hasNumber: boolean; + hasSpecialChar: boolean; +} +``` + +**Validaciones:** + +- Email válido y único +- Contraseña: mínimo 12 caracteres, 1 mayúscula, 1 minúscula, 1 número, 1 carácter especial +- Contraseñas coinciden +- Edad mínima 18 años (si aplica) +- Términos aceptados + +--- + +### 3. Forgot Password Page + +**Ruta:** `/auth/forgot-password` + +**Responsabilidades:** +- Solicitar recuperación de contraseña +- Validar email +- Enviar link de recuperación +- Mostrar confirmación + +**Flujo:** + +``` +1. Usuario ingresa email +2. Validar formato +3. Llamar a /api/v1/auth/forgot-password +4. Mostrar mensaje de confirmación + "Si la cuenta existe, recibirás instrucciones por email" +5. Mostrar campo para ingresar código alternativo +6. Opción de volver a login +``` + +**Seguridad:** +- No revelar si email existe +- Rate limiting en lado cliente +- Email enviado con validación de seguridad + +--- + +### 4. Reset Password Page + +**Ruta:** `/auth/reset-password?token=xxx` + +**Responsabilidades:** +- Validar token de recuperación +- Permitir cambio de contraseña +- Verificar nueva contraseña + +**Flujo:** + +``` +1. Extraer token del URL +2. Validar que token es válido en cliente +3. Usuario ingresa nueva contraseña +4. Mostrar indicador de fortaleza +5. Validar confirmación +6. Llamar a /api/v1/auth/reset-password +7. Mostrar confirmación +8. Redirigir a /auth/login después de 3 segundos +``` + +**Manejo de Errores:** + +| Error | Acción | +|-------|--------| +| Token inválido | Mostrar formulario para solicitar nuevo | +| Token expirado | Botón para reenviar | + +--- + +### 5. Verify Email Page + +**Ruta:** `/auth/verify-email` + +**Responsabilidades:** +- Mostrar instrucciones de verificación +- Permitir ingreso manual de código +- Resenviar email de verificación +- Validar código de verificación + +**Flujo:** + +``` +1. Mostrar mensaje: "Revisa tu email para verificar tu cuenta" +2. Campo para ingresar código de 6 dígitos +3. Link para reenviar email (con rate limiting) +4. Contador de tiempo restante para expiración +5. Llamar a /api/v1/auth/verify-email con token +6. Mostrar confirmación +7. Botón "Continuar a login" +``` + +**Validaciones:** +- Código de 6 dígitos +- No expirado +- Formato correcto + +--- + +### 6. Auth Callback Page + +**Ruta:** `/auth/callback/:provider?code=xxx&state=xxx` + +**Responsabilidades:** +- Procesar callback de OAuth +- Intercambiar código por tokens +- Manejar errores de OAuth +- Redirigir según resultado + +**Flujo:** + +``` +1. Página muestra loading spinner +2. Extraer code y state del URL +3. Validar state para prevenir CSRF +4. Llamar a /api/v1/auth/oauth/{provider} +5. Si éxito: + → Guardar tokens + → Redirigir a redirectTo o /dashboard +6. Si error: + → Mostrar mensaje de error + → Botón para volver a intentar + → Botón para ir a login +``` + +**Providers Soportados:** +- Google +- GitHub +- Apple (iOS) +- Microsoft + +--- + +## Componentes Reutilizables + +### SocialLoginButtons + +**Ubicación:** `src/components/auth/SocialLoginButtons.tsx` + +**Props:** + +```typescript +interface SocialLoginButtonsProps { + onSuccess?: (user: AuthUser) => void; + onError?: (error: Error) => void; + className?: string; + showLabels?: boolean; + providers?: ('google' | 'github' | 'apple' | 'microsoft')[]; +} +``` + +**Funcionalidades:** + +``` +1. Botones para cada proveedor OAuth +2. Iconos proporcionados por providers +3. Hover y estados activos +4. Loading state durante autenticación +5. Manejo de errores con mensajes claros +6. Responsivo en mobile +``` + +**Rendering:** + +```jsx + +``` + +**Estilos:** +- Google: Azul (#4285F4) +- GitHub: Negro (#333) +- Apple: Negro (#000) +- Microsoft: Azul (#00A4EF) +- Ancho: 100% en mobile, auto en desktop +- Altura: 48px +- Spacing entre botones: 12px + +--- + +### PhoneLoginForm + +**Ubicación:** `src/components/auth/PhoneLoginForm.tsx` + +**Props:** + +```typescript +interface PhoneLoginFormProps { + onSuccess?: (user: AuthUser, tokens: TokenResponse) => void; + onError?: (error: Error) => void; + defaultCountry?: string; + disabled?: boolean; +} + +interface PhoneLoginFormState { + step: 'phone' | 'otp'; + phone: string; + code: string; + channel: 'sms' | 'whatsapp'; + expiresAt: Date; + loading: boolean; + error?: string; +} +``` + +**Flujo de Pasos:** + +**Paso 1: Ingreso de Teléfono** + +``` +1. Selector de país con bandera +2. Campo de teléfono internacional +3. Selector de canal (SMS/WhatsApp) +4. Validar formato con libphonenumber-js +5. Botón "Enviar código" +``` + +**Paso 2: Verificación OTP** + +``` +1. Mostrar último 4 dígitos del teléfono +2. Campo de 6 dígitos (auto-focus después de cada) +3. Countdown timer (5 minutos) +4. Link "Reenviar código" (después de 30s) +5. Link "Cambiar número" +6. Botón "Verificar código" +``` + +**Validaciones:** + +```typescript +const phoneRegex = /^\+[1-9]\d{1,14}$/; +const otpRegex = /^\d{6}$/; +``` + +--- + +### TwoFactorForm + +**Ubicación:** `src/components/auth/TwoFactorForm.tsx` + +**Props:** + +```typescript +interface TwoFactorFormProps { + tempToken: string; + methods: ('totp' | 'backup_code' | 'sms')[]; + onSuccess: (tokens: TokenResponse) => void; + onError: (error: Error) => void; +} + +interface TwoFactorFormState { + method: 'totp' | 'backup_code' | 'sms'; + code: string; + remainingAttempts: number; + loading: boolean; +} +``` + +**Métodos Soportados:** + +1. **TOTP (Authenticator)** + - Campo de 6 dígitos + - Auto-focus y validación en tiempo real + - Contador de tiempo (30s por código) + +2. **Backup Codes** + - Campo de 8 caracteres (sin guiones) + - Validación de formato + - Mensaje de "código usado" + +3. **SMS** + - Reenviador de OTP a teléfono + - Campo de 6 dígitos + - Countdown timer + +--- + +### PasswordStrengthMeter + +**Ubicación:** `src/components/auth/PasswordStrengthMeter.tsx` + +**Props:** + +```typescript +interface PasswordStrengthMeterProps { + password: string; + showRequirements?: boolean; + minLength?: number; + className?: string; +} + +interface PasswordRequirements { + minLength: boolean; // >= 12 caracteres + hasUppercase: boolean; // >= 1 mayúscula + hasLowercase: boolean; // >= 1 minúscula + hasNumber: boolean; // >= 1 número + hasSpecialChar: boolean; // >= 1 carácter especial +} +``` + +**Rendering:** + +``` +Barra de fortaleza: +┌──────────────────────┐ +│ █████░░░░░░░░░░░░░░│ Muy débil +└──────────────────────┘ + +Requisitos: +✓ Mínimo 12 caracteres +✗ Una mayúscula +✓ Una minúscula +✓ Un número +✗ Carácter especial +``` + +**Colores:** +- Rojo: Muy débil +- Naranja: Débil +- Amarillo: Medio +- Verde claro: Fuerte +- Verde oscuro: Muy fuerte + +--- + +## Servicios de API + +### authService.ts + +**Ubicación:** `src/services/api/authService.ts` + +**Métodos:** + +```typescript +class AuthService { + // Login + login(email: string, password: string): Promise + + // Registro + register(data: RegisterRequest): Promise + + // Logout + logout(): Promise + + // Renovar tokens + refreshTokens(refreshToken: string): Promise + + // Obtener usuario actual + getCurrentUser(): Promise + + // OAuth + getOAuthUrl(provider: string, redirectTo?: string): Promise<{ authUrl: string; state: string }> + oauth(provider: string, code: string, state: string): Promise + unlinkOAuth(provider: string): Promise + + // Teléfono + sendPhoneOTP(phone: string, channel: 'sms' | 'whatsapp'): Promise<{ expiresAt: Date }> + verifyPhoneOTP(phone: string, code: string): Promise + + // 2FA + setupTwoFactor(): Promise<{ secret: string; qrCode: string; otpauthUrl: string }> + enableTwoFactor(code: string): Promise<{ backupCodes: string[] }> + verifyTwoFactor(tempToken: string, code: string): Promise + disableTwoFactor(code: string): Promise + + // Contraseña + forgotPassword(email: string): Promise + resetPassword(token: string, password: string): Promise + + // Email + verifyEmail(token: string): Promise + resendVerificationEmail(email: string): Promise + + // Sesiones + getSessions(): Promise + revokeSession(sessionId: string): Promise + revokeAllOtherSessions(): Promise +} + +export const authService = new AuthService(); +``` + +**Configuración Base:** + +```typescript +const apiClient = axios.create({ + baseURL: process.env.VITE_API_URL || 'http://localhost:3080/api/v1', + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// Interceptor para agregar token +apiClient.interceptors.request.use((config) => { + const token = tokenManager.getAccessToken(); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +// Interceptor para renovar token expirado +apiClient.interceptors.response.use( + (response) => response, + async (error) => { + if (error.response?.status === 401) { + const refreshToken = tokenManager.getRefreshToken(); + if (refreshToken) { + // Intentar renovar + const response = await refreshTokens(refreshToken); + // Reintentar solicitud original + } else { + // Redirigir a login + } + } + return Promise.reject(error); + } +); +``` + +--- + +## Estado Global (Zustand) + +### authStore.ts + +**Ubicación:** `src/stores/authStore.ts` + +```typescript +interface AuthState { + // Estado + user: AuthUser | null; + isAuthenticated: boolean; + isLoading: boolean; + error: Error | null; + tokens: TokenResponse | null; + + // Métodos + setUser: (user: AuthUser | null) => void; + setTokens: (tokens: TokenResponse | null) => void; + login: (email: string, password: string) => Promise; + register: (data: RegisterRequest) => Promise; + logout: () => Promise; + refreshUser: () => Promise; + clearError: () => void; + + // 2FA + setRequires2FA: (requires: boolean) => void; + setTempToken: (token: string) => void; +} + +export const useAuthStore = create((set, get) => ({ + user: null, + isAuthenticated: false, + isLoading: false, + error: null, + tokens: null, + + setUser: (user) => set({ user, isAuthenticated: !!user }), + + setTokens: (tokens) => { + set({ tokens }); + if (tokens) { + tokenManager.setTokens(tokens); + } else { + tokenManager.clear(); + } + }, + + login: async (email, password) => { + // Implementación + }, + + logout: async () => { + set({ user: null, tokens: null, isAuthenticated: false }); + tokenManager.clear(); + }, + + // ... otros métodos +})); +``` + +--- + +## Hooks Personalizados + +### useAuth() + +```typescript +function useAuth() { + const store = useAuthStore(); + + return { + user: store.user, + isAuthenticated: store.isAuthenticated, + isLoading: store.isLoading, + login: store.login, + logout: store.logout, + register: store.register, + }; +} + +// Uso: +function MyComponent() { + const { user, isAuthenticated, logout } = useAuth(); + + if (!isAuthenticated) return null; + + return
Bienvenido {user?.firstName}
; +} +``` + +### useAuthForm() + +```typescript +function useAuthForm(formType: 'login' | 'register' | 'resetPassword') { + const form = useForm({ + resolver: zodResolver(getSchema(formType)), + mode: 'onBlur', + }); + + const onSubmit = async (data) => { + // Validar y enviar + }; + + return { + form, + isSubmitting: form.formState.isSubmitting, + errors: form.formState.errors, + onSubmit: form.handleSubmit(onSubmit), + }; +} +``` + +### useSessionManager() + +```typescript +function useSessionManager() { + const [sessions, setSessions] = useState([]); + const [loading, setLoading] = useState(false); + + const loadSessions = async () => { + setLoading(true); + const data = await authService.getSessions(); + setSessions(data); + setLoading(false); + }; + + const revokeSession = async (id: string) => { + await authService.revokeSession(id); + await loadSessions(); + }; + + useEffect(() => { + loadSessions(); + }, []); + + return { sessions, loading, revokeSession }; +} +``` + +--- + +## Flujos de Usuario + +### Flujo: Registrarse con Email + +``` +1. Usuario accede a /auth/register +2. Completa formulario: + - Email + - Contraseña (con validación en tiempo real) + - Nombre + - Acepta términos +3. Validación local (Zod) +4. POST /api/v1/auth/register +5. Respuesta: Usuario con status "pending_verification" +6. Redirigir a /auth/verify-email?email=xxx +7. Usuario ingresa código del email +8. POST /api/v1/auth/verify-email +9. Email verificado +10. Redirigir a /auth/login con mensaje "Cuenta verificada" +``` + +### Flujo: Iniciar Sesión con Email/Contraseña + +``` +1. Usuario accede a /auth/login +2. Ingresa email y contraseña +3. POST /api/v1/auth/login +4. Respuesta de servidor: + a) Sin 2FA: tokens (accessToken, refreshToken) + b) Con 2FA: tempToken y métodos disponibles +5. Si sin 2FA: + - Guardar tokens en store + - Redirigir a /dashboard +6. Si con 2FA: + - Mostrar formulario de verificación + - Usuario ingresa código + - POST /api/v1/auth/2fa/verify + - Guardar tokens + - Redirigir a /dashboard +``` + +### Flujo: Iniciar Sesión con OAuth (Google) + +``` +1. Usuario haz clic en "Iniciar sesión con Google" +2. GET /api/v1/auth/oauth/google/url?redirectTo=/dashboard +3. Respuesta: authUrl + state +4. Redirigir a Google +5. Usuario autentica y otorga permisos +6. Google redirige a /auth/callback/google?code=xxx&state=xxx +7. POST /api/v1/auth/oauth/google { code, state } +8. Respuesta: tokens +9. Guardar en store +10. Redirigir a /dashboard +``` + +### Flujo: Cambiar Contraseña Olvidada + +``` +1. Usuario accede a /auth/forgot-password +2. Ingresa email +3. POST /api/v1/auth/forgot-password +4. Mostrar mensaje: "Si la cuenta existe..." +5. Usuario recibe email con link +6. Link dirigido a /auth/reset-password?token=xxx +7. Usuario ingresa nueva contraseña +8. POST /api/v1/auth/reset-password { token, password } +9. Mostrar confirmación +10. Redirigir a /auth/login +``` + +### Flujo: Activar 2FA (TOTP) + +``` +1. Usuario va a Settings > Seguridad +2. Haz clic en "Activar autenticación de dos factores" +3. POST /api/v1/auth/2fa/setup +4. Respuesta: QR code + secret +5. Usuario escanea con Google Authenticator/Authy +6. Usuario ingresa código de 6 dígitos +7. POST /api/v1/auth/2fa/enable { code } +8. Respuesta: Backup codes +9. Usuario descargar/copia backup codes +10. Mostrar confirmación: "2FA activado" +``` + +### Flujo: Iniciar Sesión con Teléfono + +``` +1. Usuario accede a /auth/phone-login +2. Selecciona país y ingresa teléfono +3. Elige canal: SMS o WhatsApp +4. POST /api/v1/auth/phone/send { phone, channel } +5. Mostrar "Código enviado a +52 555 1234..." +6. Usuario ingresa código de 6 dígitos +7. POST /api/v1/auth/phone/verify { phone, code } +8. Si usuario nuevo: Crear account automáticamente +9. Respuesta: tokens +10. Redirigir a /dashboard +``` + +--- + +## Seguridad + +### Protección CSRF + +```typescript +// Usar state token en OAuth +const state = crypto.randomUUID(); +sessionStorage.setItem('oauth_state', state); + +// Validar en callback +const urlState = new URLSearchParams(location.search).get('state'); +if (urlState !== sessionStorage.getItem('oauth_state')) { + throw new Error('Invalid state token'); +} +``` + +### Manejo de Tokens + +```typescript +class TokenManager { + setTokens(tokens: TokenResponse) { + sessionStorage.setItem('accessToken', tokens.accessToken); + localStorage.setItem('refreshToken', tokens.refreshToken); + } + + getAccessToken(): string | null { + return sessionStorage.getItem('accessToken'); + } + + getRefreshToken(): string | null { + return localStorage.getItem('refreshToken'); + } + + clear() { + sessionStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + } + + isTokenExpired(token: string): boolean { + const decoded = jwtDecode(token); + return decoded.exp * 1000 < Date.now(); + } +} +``` + +### Protección XSS + +- Usar React (que escapa automáticamente) +- Sanitizar HTML si es necesario: `DOMPurify` +- Content Security Policy headers + +### Validación de Inputs + +```typescript +const schemas = { + login: z.object({ + email: z.string().email('Email inválido'), + password: z.string().min(1, 'Requerido'), + }), + register: z.object({ + email: z.string().email('Email inválido'), + password: z.string() + .min(12, 'Mínimo 12 caracteres') + .regex(/[A-Z]/, 'Debe contener mayúscula') + .regex(/[a-z]/, 'Debe contener minúscula') + .regex(/[0-9]/, 'Debe contener número') + .regex(/[^a-zA-Z0-9]/, 'Debe contener carácter especial'), + confirmPassword: z.string(), + }).refine((data) => data.password === data.confirmPassword, { + message: 'Las contraseñas no coinciden', + path: ['confirmPassword'], + }), +}; +``` + +--- + +## Rutas Protegidas + +```typescript +function ProtectedRoute({ children }: { children: ReactNode }) { + const { isAuthenticated, isLoading } = useAuth(); + + if (isLoading) return ; + if (!isAuthenticated) return ; + + return children; +} + +// Uso: + + } /> + + + + } /> + +``` + +--- + +## Testing + +### Unit Tests + +```typescript +describe('authService.login', () => { + it('debe retornar tokens al login exitoso', async () => { + const result = await authService.login('user@example.com', 'password'); + expect(result.data.tokens).toBeDefined(); + expect(result.data.user).toBeDefined(); + }); + + it('debe lanzar error con credenciales inválidas', async () => { + await expect( + authService.login('user@example.com', 'wrong') + ).rejects.toThrow('INVALID_CREDENTIALS'); + }); +}); +``` + +### Integration Tests + +```typescript +describe('Login Flow', () => { + it('debe completar flujo de login y redirigir', async () => { + render(); + + await userEvent.type(screen.getByLabelText('Email'), 'user@example.com'); + await userEvent.type(screen.getByLabelText('Password'), 'SecurePass123!'); + await userEvent.click(screen.getByText('Iniciar sesión')); + + await waitFor(() => { + expect(window.location.pathname).toBe('/dashboard'); + }); + }); +}); +``` + +--- + +## Tipos TypeScript + +**Ubicación:** `src/types/auth.ts` + +```typescript +export interface AuthUser { + id: string; + email: string; + firstName: string; + lastName: string; + phone?: string; + role: 'investor' | 'admin' | 'support'; + status: 'active' | 'pending_verification' | 'suspended'; + emailVerified: boolean; + phoneVerified: boolean; + twoFactorEnabled: boolean; + createdAt: Date; + updatedAt: Date; + profile?: { + displayName: string; + avatarUrl?: string; + preferredLanguage: string; + timezone: string; + }; + oauthProviders: string[]; +} + +export interface TokenResponse { + accessToken: string; + refreshToken: string; + expiresIn: number; + tokenType: 'Bearer'; +} + +export interface LoginResponse { + success: boolean; + data: { + user: AuthUser; + tokens?: TokenResponse; + requires2FA?: boolean; + tempToken?: string; + methods?: string[]; + }; +} + +export interface Session { + id: string; + device: { + type: 'desktop' | 'mobile' | 'tablet'; + browser: string; + browserVersion?: string; + os: string; + osVersion: string; + }; + location: { + city: string; + country: string; + countryCode: string; + }; + ipAddress: string; + lastActivity: Date; + createdAt: Date; + isCurrent: boolean; +} +``` + +--- + +## Checklist de Implementación + +- [ ] Páginas de autenticación creadas +- [ ] Componentes reutilizables implementados +- [ ] Servicios de API configurados +- [ ] Estado Zustand inicializado +- [ ] Hooks personalizados desarrollados +- [ ] Rutas protegidas implementadas +- [ ] Validaciones con Zod aplicadas +- [ ] Manejo de errores completado +- [ ] Tests unitarios escritos +- [ ] Tests de integración completados +- [ ] Security review realizado +- [ ] Documentación de componentes lista +- [ ] CHANGELOG actualizado + +--- + +## Performance + +### Code Splitting + +```typescript +const LoginPage = lazy(() => import('./pages/auth/LoginPage')); +const RegisterPage = lazy(() => import('./pages/auth/RegisterPage')); + +}> + + } /> + + +``` + +### Memoización + +```typescript +const SocialLoginButtons = memo(function SocialLoginButtons(props) { + // Componente +}); +``` + +### Request Caching + +```typescript +const { data: user } = useQuery({ + queryKey: ['auth', 'me'], + queryFn: () => authService.getCurrentUser(), + staleTime: 5 * 60 * 1000, // 5 minutos +}); +``` + +--- + +## Referencias + +- [ET-AUTH-001: OAuth Specification](./ET-AUTH-001-oauth.md) +- [ET-AUTH-002: JWT Specification](./ET-AUTH-002-jwt.md) +- [ET-AUTH-004: API Endpoints](./ET-AUTH-004-api.md) +- [ET-AUTH-005: Security](./ET-AUTH-005-security.md) +- [React Documentation](https://react.dev) +- [React Router Documentation](https://reactrouter.com) +- [Zustand Documentation](https://github.com/pmndrs/zustand) +- [React Hook Form Documentation](https://react-hook-form.com) +- [Zod Validation Library](https://zod.dev) diff --git a/docs/02-definicion-modulos/OQI-001-fundamentos-auth/historias-usuario/US-AUTH-013-logout-global.md b/docs/02-definicion-modulos/OQI-001-fundamentos-auth/historias-usuario/US-AUTH-013-logout-global.md new file mode 100644 index 0000000..cd18bbf --- /dev/null +++ b/docs/02-definicion-modulos/OQI-001-fundamentos-auth/historias-usuario/US-AUTH-013-logout-global.md @@ -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 │ +│ 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 + + + + + + +
+
+

Sesión cerrada en todos tus dispositivos

+
+
+

Hola {{ user_name }},

+

Se cerró sesión en TODOS tus dispositivos.

+
+ Detalles:
+ Fecha: {{ date }} a las {{ time }} {{ timezone }}
+ Ubicación: {{ location }}
+
+

¿Fuiste tú? Si reconoces esta actividad, puedes ignorar este email.

+

¿No fuiste tú? Considera cambiar tu contraseña inmediatamente:

+ Cambiar contraseña +
+
+ + +``` + +--- + +## 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) diff --git a/docs/02-definicion-modulos/OQI-001-fundamentos-auth/historias-usuario/US-AUTH-014-gestion-dispositivos.md b/docs/02-definicion-modulos/OQI-001-fundamentos-auth/historias-usuario/US-AUTH-014-gestion-dispositivos.md new file mode 100644 index 0000000..3266164 --- /dev/null +++ b/docs/02-definicion-modulos/OQI-001-fundamentos-auth/historias-usuario/US-AUTH-014-gestion-dispositivos.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 │ +│ 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) diff --git a/docs/02-definicion-modulos/OQI-006-ml-signals/especificaciones/ET-ML-008-frontend.md b/docs/02-definicion-modulos/OQI-006-ml-signals/especificaciones/ET-ML-008-frontend.md new file mode 100644 index 0000000..e715bc2 --- /dev/null +++ b/docs/02-definicion-modulos/OQI-006-ml-signals/especificaciones/ET-ML-008-frontend.md @@ -0,0 +1,1037 @@ +--- +id: "ET-ML-008" +title: "Especificación Frontend del Módulo ML Signals" +type: "Technical Specification" +status: "Implementado" +priority: "Alta" +epic: "OQI-006" +project: "trading-platform" +version: "1.0.0" +created_date: "2025-12-15" +updated_date: "2026-01-25" +--- + +# ET-ML-008: Especificación Frontend del Módulo ML Signals + +## Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | ET-ML-008 | +| **Épica** | OQI-006 - Señales ML | +| **Tipo** | Especificación Técnica | +| **Versión** | 1.0.0 | +| **Estado** | Implementado | +| **Última actualización** | 2026-01-25 | +| **Responsable** | Trading Platform Frontend Team | + +--- + +## Propósito + +Definir la arquitectura, componentes y servicios del frontend para el módulo de Señales ML (OQI-006). Este documento especifica la interfaz de usuario, estructura de componentes React, integración con el backend ML (puerto 3083) y los flujos de usuario para visualización y análisis de señales de trading. + +--- + +## Visión General + +### Objetivo Principal + +Proporcionar una interfaz intuitiva y responsive que permita a los usuarios: +- Visualizar señales ML en tiempo real +- Analizar predicciones de rangos de precios +- Monitorear indicadores técnicos avanzados +- Ejecutar operaciones basadas en señales + +### Arquitectura de Frontend + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ML DASHBOARD (REACT) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ MLDashboard Container │ │ +│ │ - State Management (Zustand) │ │ +│ │ - WebSocket Subscriptions (Real-time) │ │ +│ │ - Query Management (TanStack Query) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │ +│ │ Signals │ │ ICT Analysis │ │ Ensemble Signals │ │ +│ │ Tab │ │ Tab │ │ Tab │ │ +│ └──────────┘ └──────────────┘ └──────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ ML Service Integration │ │ +│ │ - Base URL: http://localhost:3083 │ │ +│ │ - WebSocket: ws://localhost:3083/ws │ │ +│ │ - Rate Limiting per User │ │ +│ │ - Error Handling & Retry Logic │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Componentes Frontend + +### 1. MLDashboard (Container Principal) + +**Ruta:** `apps/frontend/src/pages/MLDashboard.tsx` + +**Responsabilidades:** +- Gestionar estado global de señales ML +- Coordinar múltiples tabs (Signals, ICT Analysis, Ensemble) +- Manejar conexiones WebSocket +- Controlar ciclo de vida de datos + +**Props:** +```typescript +interface MLDashboardProps { + userId: string; + selectedSymbol?: string; + autoRefresh?: boolean; +} +``` + +**Estado Inicial:** +```typescript +interface MLDashboardState { + activeTab: 'signals' | 'ict-analysis' | 'ensemble'; + selectedSymbol: string; // BTCUSDT, ETHUSDT, etc. + timeframe: '1h' | '4h' | '1d'; + isConnected: boolean; + signals: Signal[]; + predictions: Prediction[]; + indicators: Indicator[]; + lastUpdate: Date; + loading: boolean; + error?: string; +} +``` + +**Features:** +- Real-time WebSocket connection +- Automatic reconnection with exponential backoff +- TanStack Query for data caching +- Zustand for state management +- Error boundaries + +--- + +### 2. AMDPhaseIndicator (Componente de Fase AMD) + +**Ruta:** `apps/frontend/src/components/ml/AMDPhaseIndicator.tsx` + +**Propósito:** Visualizar la fase actual AMD (Accumulation, Manipulation, Distribution) + +**Props:** +```typescript +interface AMDPhaseIndicatorProps { + phase: 'accumulation' | 'manipulation' | 'distribution' | 'unknown'; + confidence: number; // 0-100 + priceLevel: number; + volume: number; + duration: number; // minutos + history?: PhaseTransition[]; +} +``` + +**Estilos y Visualización:** +- **Accumulation:** Verde (#10b981) +- **Manipulation:** Amarillo (#f59e0b) +- **Distribution:** Rojo (#ef4444) +- **Unknown:** Gris (#6b7280) + +**Elementos:** +```jsx +
+ + + + + +
+``` + +--- + +### 3. PredictionCard (Tarjeta de Predicción) + +**Ruta:** `apps/frontend/src/components/ml/PredictionCard.tsx` + +**Propósito:** Mostrar predicciones de rangos de precios y objetivos + +**Props:** +```typescript +interface PredictionCardProps { + prediction: { + symbol: string; + timeframe: string; + predictedLow: number; + predictedHigh: number; + currentPrice: number; + confidence: number; + targetPrice: number; + stopLoss: number; + takeProfit: number[]; + direction: 'BULLISH' | 'BEARISH' | 'NEUTRAL'; + timestamp: Date; + }; + onTrade?: (action: TradeAction) => void; +} +``` + +**Componentes Internos:** +``` +PredictionCard +├── Header (Symbol, Timeframe) +├── RangeDisplay (Low-High) +├── ConfidenceGauge +├── PriceLevels (SL, TP1, TP2, TP3) +├── DirectionBadge +└── ActionButtons (Buy/Sell/Close) +``` + +**Estilos:** +- Card con sombra y border-radius +- Gradientes según dirección (BULLISH: verde, BEARISH: rojo) +- Animaciones en cambios de precio +- Responsive: 1 col mobile, 2 cols tablet, 3 cols desktop + +--- + +### 4. SignalsTimeline (Línea de Tiempo de Señales) + +**Ruta:** `apps/frontend/src/components/ml/SignalsTimeline.tsx` + +**Propósito:** Visualizar historial de señales en formato timeline + +**Props:** +```typescript +interface SignalsTimelineProps { + signals: Signal[]; + symbol: string; + timeRange: { + from: Date; + to: Date; + }; + onSignalClick?: (signal: Signal) => void; +} + +interface Signal { + id: string; + timestamp: Date; + type: 'BUY' | 'SELL' | 'CLOSE_LONG' | 'CLOSE_SHORT'; + price: number; + strength: number; // 0-100 + reason: string; + profitLoss?: number; + status: 'PENDING' | 'EXECUTED' | 'CLOSED'; +} +``` + +**Visualización:** +``` +2026-01-25 14:30 ▼ BUY @ 45,230 |████████░░| 85% Strength +2026-01-25 13:15 ─ WAIT |██░░░░░░░░| 25% Confidence +2026-01-25 12:00 ▲ SELL @ 45,100 |██████░░░░| 70% Strength +2026-01-25 10:45 ─ NEUTRAL |████░░░░░░| 45% Confidence +``` + +**Filtros:** +- Por tipo de señal (BUY, SELL, CLOSE) +- Por rango temporal +- Por fuerza de señal (mínimo %) +- Por estado (PENDING, EXECUTED, CLOSED) + +--- + +### 5. AccuracyMetrics (Métricas de Precisión) + +**Ruta:** `apps/frontend/src/components/ml/AccuracyMetrics.tsx` + +**Propósito:** Mostrar KPIs de desempeño del modelo ML + +**Props:** +```typescript +interface AccuracyMetricsProps { + metrics: { + totalSignals: number; + successfulTrades: number; + failedTrades: number; + winRate: number; // % + profitFactor: number; + sharpeRatio: number; + maxDrawdown: number; // % + totalProfit: number; + periods: { + period: 'TODAY' | 'WEEK' | 'MONTH' | '3M' | 'YTD' | 'ALL_TIME'; + value: number; + }[]; + }; + period?: 'TODAY' | 'WEEK' | 'MONTH' | '3M' | 'YTD' | 'ALL_TIME'; +} +``` + +**Layout de Métricas:** +``` +┌────────────────────────────────────────────┐ +│ ACCURACY METRICS (YTD) │ +├────────────────────────────────────────────┤ +│ Win Rate: 68.5% │ Profit Factor: 2.3 │ +│ Total Trades: 47 │ Sharpe Ratio: 1.85 │ +│ Success: 32/47 │ Max Drawdown: 12.3% │ +│ Total Profit: +8,450 USDT │ +└────────────────────────────────────────────┘ +``` + +**Gráficos:** +- Equity Curve (línea) +- Win/Loss distribution (pie) +- Drawdown (area) +- Monthly returns (bar) + +--- + +### 6. ICTAnalysisCard (Tarjeta de Análisis ICT) + +**Ruta:** `apps/frontend/src/components/ml/ICTAnalysisCard.tsx` + +**Propósito:** Visualizar análisis de Institucionales Context Theory (ICT) + +**Props:** +```typescript +interface ICTAnalysisCardProps { + analysis: { + symbol: string; + timestamp: Date; + orderBlock: OrderBlock; + fairValueGap: FairValueGap[]; + liquidityLevels: LiquidityLevel[]; + swingStructure: SwingStructure; + breaker: Breaker; + disorderedMarket: boolean; + }; +} + +interface OrderBlock { + level: number; + strength: 'weak' | 'moderate' | 'strong'; + timeframe: string; + confirmed: boolean; +} + +interface FairValueGap { + high: number; + low: number; + timestamp: Date; + mitigated: boolean; +} +``` + +**Secciones:** +1. **Order Blocks:** Mostrar niveles de orden bloqueados +2. **Fair Value Gaps:** Visualizar gaps de precio +3. **Liquidity Levels:** Mostrar zonas de liquidez +4. **Swing Structure:** Estructura de oscilaciones +5. **Market State:** Ordenado vs Desordenado + +**Visualización:** +``` +ORDER BLOCKS +├─ Level 45,200 [STRONG] ████████ Confirmed +├─ Level 44,950 [MODERATE] ████░░░░ Pending +└─ Level 44,700 [WEAK] ██░░░░░░ Watching + +FAIR VALUE GAPS +├─ 45,100 - 45,200 [MITIGATED] ✓ +└─ 44,900 - 45,000 [ACTIVE] ◆ + +LIQUIDITY LEVELS +├─ 45,500 [BUY] ▲ High +├─ 44,500 [SELL] ▼ High +└─ 43,000 [SUPPORT] ═══ Key Level +``` + +--- + +### 7. EnsembleSignalCard (Tarjeta de Señal de Ensemble) + +**Ruta:** `apps/frontend/src/components/ml/EnsembleSignalCard.tsx` + +**Propósito:** Mostrar consenso de múltiples modelos ML + +**Props:** +```typescript +interface EnsembleSignalCardProps { + symbol: string; + models: ModelSignal[]; + ensemble: { + direction: 'BULLISH' | 'BEARISH' | 'NEUTRAL'; + confidence: number; // 0-100 + agreementLevel: number; // % de modelos que concuerdan + optimalEntry: number; + optimalExit: number; + riskRewardRatio: number; + }; +} + +interface ModelSignal { + name: string; // 'AMD Model', 'ICT Model', 'Neural Network', etc. + direction: 'BULLISH' | 'BEARISH' | 'NEUTRAL'; + confidence: number; + weight: number; // % en ensemble + lastUpdate: Date; +} +``` + +**Visualización (Radar Chart):** +``` + BULLISH + ╱╲ + ╱ ╲ + 40° ╱ ╲ 40° + ╱╱ ╲╲ + ╱ ┏━━━━┓ ╲ + ╱ ┃ MLP ┃ ╲ + ╱ ┗━━━━┛ ╲ + ╱ ┏━━━━┓ ╲ + ╱ ┃AMD ┃ ╲ + ╱ ┗━━━━┛ ╲ +BEARISH ICT NEUTRAL +``` + +**Tabla de Modelos:** +| Modelo | Dirección | Confianza | Peso | +|--------|-----------|-----------|------| +| AMD Model | BULLISH | 85% | 30% | +| ICT Model | BULLISH | 78% | 30% | +| Neural Net | BEARISH | 45% | 20% | +| LSTM | BULLISH | 82% | 20% | +| **ENSEMBLE** | **BULLISH** | **81%** | **100%** | + +--- + +### 8. TradeExecutionModal (Modal de Ejecución de Trades) + +**Ruta:** `apps/frontend/src/components/ml/TradeExecutionModal.tsx` + +**Propósito:** Interfaz para ejecutar trades basados en señales ML + +**Props:** +```typescript +interface TradeExecutionModalProps { + isOpen: boolean; + signal: Signal; + prediction: Prediction; + onExecute: (trade: TradeOrder) => Promise; + onCancel: () => void; +} + +interface TradeOrder { + symbol: string; + side: 'BUY' | 'SELL'; + quantity: number; + entryPrice: number; + stopLoss: number; + takeProfit: number[]; + leverage?: number; + orderType: 'MARKET' | 'LIMIT'; + timeInForce: 'GTC' | 'IOC' | 'FOK'; +} +``` + +**Pasos del Formulario:** +1. **Confirmación de Señal:** Mostrar detalles de la señal +2. **Parámetros de Orden:** Cantidad, SL, TP +3. **Risk Management:** Cálculo de riesgo/recompensa +4. **Revisión:** Confirmar todos los parámetros +5. **Ejecución:** Enviar orden + +**Validaciones:** +- Verificar saldo disponible +- Validar niveles SL/TP +- Confirmar riesgo máximo por operación +- Verificar límites de exposición + +**Forma HTML:** +```jsx +
+
+ +
+ +
+ + + +
+ +
+ + +
+ +
+ + +
+
+``` + +--- + +## ML Service Integration + +### Arquitectura del Servicio + +**Ruta:** `apps/frontend/src/services/ml/mlService.ts` + +**Configuración Base:** +```typescript +const ML_SERVICE_CONFIG = { + baseURL: 'http://localhost:3083', + wsURL: 'ws://localhost:3083/ws', + timeout: 10000, + retryAttempts: 3, + retryDelay: 1000, + rateLimitPerUser: { + requestsPerMinute: 60, + concurrent: 5 + } +}; +``` + +### Endpoints Principales + +#### 1. Obtener Señales + +**GET** `/api/signals` + +```typescript +interface SignalsRequest { + symbol: string; + timeframe?: '1h' | '4h' | '1d'; + limit?: number; + offset?: number; +} + +interface SignalsResponse { + data: Signal[]; + total: number; + timestamp: Date; +} + +// Uso +const signals = await mlService.getSignals({ + symbol: 'BTCUSDT', + timeframe: '1h', + limit: 50 +}); +``` + +--- + +#### 2. Obtener Predicciones + +**GET** `/api/predictions` + +```typescript +interface PredictionsRequest { + symbol: string; + timeframe?: '1h' | '4h' | '1d'; +} + +interface PredictionsResponse { + data: Prediction[]; + confidence: number; + timestamp: Date; +} + +// Uso +const predictions = await mlService.getPredictions({ + symbol: 'ETHUSDT', + timeframe: '4h' +}); +``` + +--- + +#### 3. Análisis ICT + +**GET** `/api/ict-analysis` + +```typescript +interface ICTAnalysisRequest { + symbol: string; + timeframe: string; +} + +interface ICTAnalysisResponse { + orderBlocks: OrderBlock[]; + fairValueGaps: FairValueGap[]; + liquidityLevels: LiquidityLevel[]; + swingStructure: SwingStructure; + timestamp: Date; +} + +// Uso +const ictAnalysis = await mlService.getICTAnalysis({ + symbol: 'BTCUSDT', + timeframe: '1h' +}); +``` + +--- + +#### 4. Señal de Ensemble + +**GET** `/api/ensemble-signal` + +```typescript +interface EnsembleRequest { + symbol: string; + models?: string[]; +} + +interface EnsembleResponse { + direction: 'BULLISH' | 'BEARISH' | 'NEUTRAL'; + confidence: number; + modelSignals: ModelSignal[]; + agreementLevel: number; + timestamp: Date; +} + +// Uso +const ensemble = await mlService.getEnsembleSignal({ + symbol: 'BTCUSDT' +}); +``` + +--- + +#### 5. Métricas de Precisión + +**GET** `/api/accuracy-metrics` + +```typescript +interface MetricsRequest { + period?: 'TODAY' | 'WEEK' | 'MONTH' | '3M' | 'YTD' | 'ALL_TIME'; +} + +interface MetricsResponse { + winRate: number; + profitFactor: number; + sharpeRatio: number; + totalSignals: number; + successfulTrades: number; + timestamp: Date; +} + +// Uso +const metrics = await mlService.getMetrics({ + period: 'MONTH' +}); +``` + +--- + +### WebSocket Real-time + +**Conexión:** +```typescript +mlService.connectWebSocket({ + symbols: ['BTCUSDT', 'ETHUSDT'], + channels: ['signals', 'predictions', 'indicators'], + onMessage: handleMessage, + onError: handleError, + onClose: handleClose +}); +``` + +**Eventos:** +```typescript +// Signal Update +{ + type: 'signal', + symbol: 'BTCUSDT', + data: { ... } +} + +// Prediction Update +{ + type: 'prediction', + symbol: 'ETHUSDT', + data: { ... } +} + +// Indicator Update +{ + type: 'indicator', + symbol: 'BTCUSDT', + indicator: 'rsi', + value: 65.5 +} +``` + +--- + +## Estado Global (Zustand) + +### Store Principal + +**Ruta:** `apps/frontend/src/stores/mlSignalsStore.ts` + +```typescript +interface MLSignalsStore { + // Estado + activeTab: 'signals' | 'ict-analysis' | 'ensemble'; + selectedSymbol: string; + signals: Signal[]; + predictions: Prediction[]; + indicators: Indicator[]; + metrics: Metrics; + isConnected: boolean; + loading: boolean; + error?: string; + + // Acciones + setActiveTab: (tab: string) => void; + setSymbol: (symbol: string) => void; + updateSignals: (signals: Signal[]) => void; + updatePredictions: (predictions: Prediction[]) => void; + setConnected: (connected: boolean) => void; + setError: (error: string | undefined) => void; + reset: () => void; +} + +export const useMlSignalsStore = create((set) => ({ + // Implementación +})); +``` + +--- + +## Queries de TanStack Query + +### Query Configuration + +```typescript +// Queries +export const mlQueries = { + all: () => ['ml'], + signals: (symbol: string) => [...mlQueries.all(), 'signals', symbol], + predictions: (symbol: string) => [...mlQueries.all(), 'predictions', symbol], + metrics: (period?: string) => [...mlQueries.all(), 'metrics', period], + ictAnalysis: (symbol: string) => [...mlQueries.all(), 'ict', symbol], + ensemble: (symbol: string) => [...mlQueries.all(), 'ensemble', symbol], +}; + +// Hook para obtener señales +export const useSignals = (symbol: string, options?: UseQueryOptions) => { + return useQuery({ + queryKey: mlQueries.signals(symbol), + queryFn: () => mlService.getSignals({ symbol }), + staleTime: 30000, // 30s + refetchInterval: 60000, // 60s + ...options + }); +}; +``` + +--- + +## Gestión de Errores y Estado + +### Error Boundary + +```typescript +interface MLErrorBoundaryProps { + children: React.ReactNode; + fallback?: React.ReactNode; +} + +export const MLErrorBoundary: React.FC = ({ + children, + fallback +}) => { + // Implementación + return ; +}; +``` + +### Error Handling + +```typescript +// Tipos de errores +type MLError = + | ConnectionError + | DataValidationError + | RateLimitError + | InvalidSignalError + | ExecutionError; + +// Handler global +const handleMLError = (error: MLError) => { + switch (error.type) { + case 'ConnectionError': + // Reintentar conexión con backoff exponencial + break; + case 'RateLimitError': + // Mostrar advertencia, esperar cooldown + break; + case 'ExecutionError': + // Mostrar error al usuario, registrar + break; + } +}; +``` + +--- + +## Validaciones de Datos + +### Schemas Zod + +```typescript +import { z } from 'zod'; + +const SignalSchema = z.object({ + id: z.string().uuid(), + timestamp: z.date(), + type: z.enum(['BUY', 'SELL', 'CLOSE_LONG', 'CLOSE_SHORT']), + price: z.number().positive(), + strength: z.number().min(0).max(100), + status: z.enum(['PENDING', 'EXECUTED', 'CLOSED']) +}); + +const PredictionSchema = z.object({ + symbol: z.string().regex(/^[A-Z]{1,10}USDT$/), + predictedLow: z.number().positive(), + predictedHigh: z.number().positive(), + confidence: z.number().min(0).max(100), + direction: z.enum(['BULLISH', 'BEARISH', 'NEUTRAL']) +}); +``` + +--- + +## Responsive Design + +### Breakpoints + +```typescript +const breakpoints = { + mobile: '320px', + tablet: '768px', + desktop: '1024px', + wide: '1440px' +}; +``` + +### Layout Mobile + +- **Dashboard:** Stack vertical de tabs +- **Signals:** Cards en single column +- **Charts:** Fullscreen optimizado +- **Modals:** Fullscreen en mobile, centered en desktop + +--- + +## Performance Optimization + +### Code Splitting + +```typescript +// Lazy load ML components +const MLDashboard = lazy(() => import('./pages/MLDashboard')); +const TradeExecutionModal = lazy(() => + import('./components/ml/TradeExecutionModal') +); +``` + +### Memoization + +```typescript +// Memoizar componentes costosos +export const AMDPhaseIndicator = React.memo( + AMDPhaseIndicatorComponent, + (prevProps, nextProps) => { + return prevProps.phase === nextProps.phase && + prevProps.confidence === nextProps.confidence; + } +); +``` + +### Virtual Scrolling + +```typescript +// Para listas grandes de señales + } +/> +``` + +--- + +## Testing Strategy + +### Unit Tests + +```typescript +// tests/components/ml/AMDPhaseIndicator.test.tsx +describe('AMDPhaseIndicator', () => { + it('should render accumulation phase', () => { + const { getByText } = render( + + ); + expect(getByText(/Accumulation/i)).toBeInTheDocument(); + }); +}); +``` + +### Integration Tests + +```typescript +// tests/integration/mlDashboard.test.tsx +describe('ML Dashboard Integration', () => { + it('should load signals and display them', async () => { + const { getByText } = render(); + await waitFor(() => { + expect(getByText(/BUY Signal/)).toBeInTheDocument(); + }); + }); +}); +``` + +### E2E Tests + +```typescript +// tests/e2e/ml-signals.spec.ts +describe('ML Signals E2E', () => { + it('should execute trade from signal', () => { + cy.visit('/dashboard/ml'); + cy.contains('BUY Signal').click(); + cy.get('[data-testid="execute-btn"]').click(); + cy.contains('Trade Executed').should('be.visible'); + }); +}); +``` + +--- + +## Accesibilidad (WCAG 2.1) + +### Requirements + +- ARIA labels en todos los componentes interactivos +- Contraste mínimo 4.5:1 en textos +- Navegación por teclado completa +- Descripciones de imágenes y gráficos +- Announces de cambios dinámicos con aria-live + +### Ejemplo + +```jsx +
+ +
+``` + +--- + +## Seguridad + +### OWASP Top 10 + +1. **Injection:** Validación de todas las entradas con Zod +2. **Authentication:** JWT tokens en headers +3. **XSS:** Sanitización de datos dinámicos con DOMPurify +4. **CSRF:** Tokens CSRF en forms +5. **Rate Limiting:** Cliente + Servidor + +### Ejemplo + +```typescript +import DOMPurify from 'dompurify'; + +const sanitizedContent = DOMPurify.sanitize(userContent); +``` + +--- + +## Deployment + +### Build Process + +```bash +# Frontend build +cd apps/frontend +npm run build # Vite production build +npm run lint # ESLint checks +npm run typecheck # TypeScript validation + +# Output +dist/ +├── index.html +├── assets/ +│ ├── js/ +│ ├── css/ +│ └── images/ +``` + +### Environment Variables + +``` +VITE_API_BASE_URL=http://localhost:3080 +VITE_ML_ENGINE_URL=http://localhost:3083 +VITE_WS_URL=ws://localhost:3083/ws +VITE_APP_ENV=development +``` + +--- + +## Roadmap Futuro + +1. **Dark Mode:** Tema oscuro completo +2. **Custom Indicators:** Permitir usuarios agregar indicadores personalizados +3. **Backtesting UI:** Interfaz para backtesting de estrategias +4. **Mobile App:** React Native para iOS/Android +5. **Voice Commands:** Ejecución de trades por voz +6. **ML Model Marketplace:** Mercado de modelos ML + +--- + +## Referencias + +- [React Documentation](https://react.dev) +- [Zustand Store](https://github.com/pmndrs/zustand) +- [TanStack Query](https://tanstack.com/query) +- [Vite Guide](https://vitejs.dev) +- [TypeScript Handbook](https://www.typescriptlang.org/docs) + +--- + +## Changelog + +| Versión | Fecha | Cambios | +|---------|-------|---------| +| 1.0.0 | 2026-01-25 | Especificación inicial completa | +| 0.9.0 | 2025-12-15 | Borrador de componentes | + +--- + +*ET-ML-008 | ML Signals Frontend Specification v1.0.0 | Trading Platform* + diff --git a/docs/02-definicion-modulos/OQI-006-ml-signals/historias-usuario/US-ML-008-ver-ensemble-signal.md b/docs/02-definicion-modulos/OQI-006-ml-signals/historias-usuario/US-ML-008-ver-ensemble-signal.md new file mode 100644 index 0000000..952cb7e --- /dev/null +++ b/docs/02-definicion-modulos/OQI-006-ml-signals/historias-usuario/US-ML-008-ver-ensemble-signal.md @@ -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; + 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 diff --git a/docs/02-definicion-modulos/OQI-006-ml-signals/historias-usuario/US-ML-009-ver-ict-analysis.md b/docs/02-definicion-modulos/OQI-006-ml-signals/historias-usuario/US-ML-009-ver-ict-analysis.md new file mode 100644 index 0000000..f5c01f5 --- /dev/null +++ b/docs/02-definicion-modulos/OQI-006-ml-signals/historias-usuario/US-ML-009-ver-ict-analysis.md @@ -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; + 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 diff --git a/docs/02-definicion-modulos/OQI-006-ml-signals/historias-usuario/US-ML-010-scan-multisimbolo.md b/docs/02-definicion-modulos/OQI-006-ml-signals/historias-usuario/US-ML-010-scan-multisimbolo.md new file mode 100644 index 0000000..e2a4b5d --- /dev/null +++ b/docs/02-definicion-modulos/OQI-006-ml-signals/historias-usuario/US-ML-010-scan-multisimbolo.md @@ -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; + updateProgress: (progress: ScanProgress) => void; + updateFilters: (filters: Partial) => 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 diff --git a/docs/02-definicion-modulos/OQI-007-llm-agent/especificaciones/ET-LLM-007-frontend.md b/docs/02-definicion-modulos/OQI-007-llm-agent/especificaciones/ET-LLM-007-frontend.md new file mode 100644 index 0000000..93e2370 --- /dev/null +++ b/docs/02-definicion-modulos/OQI-007-llm-agent/especificaciones/ET-LLM-007-frontend.md @@ -0,0 +1,1393 @@ +--- +id: "ET-LLM-007" +title: "Especificación Frontend del Módulo LLM Agent" +type: "Technical Specification" +status: "Done" +priority: "Alta" +epic: "OQI-007" +project: "trading-platform" +version: "1.0.0" +created_date: "2025-12-15" +updated_date: "2026-01-25" +--- + +# ET-LLM-007: Especificación Frontend del Módulo LLM Agent + +**Épica:** OQI-007 - LLM Strategy Agent +**Versión:** 1.0 +**Fecha:** 2025-12-15 +**Estado:** Implementado +**Prioridad:** P1 - Alto + +--- + +## Resumen + +Esta especificación define la arquitectura, componentes y servicios del frontend para el módulo LLM Strategy Agent. El frontend proporciona una interfaz conversacional interactiva que permite a los usuarios comunicarse con el agente LLM para análisis de trading, consultas estratégicas y visualización de señales en tiempo real. + +--- + +## Arquitectura General + +``` +┌──────────────────────────────────────────────────────────────────────┐ +│ FRONTEND (React) │ +│ Port: 3000 │ +├──────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────┐ │ +│ │ Pages │ │ Components │ │ Services │ │ +│ ├────────────────────┤ ├────────────────────┤ ├────────────────┤ │ +│ │ • AssistantPage │ │ • ChatWindow │ │ • llmAgentSvc │ │ +│ │ • ChatPage │ │ • ChatMessage │ │ • llmProviders │ │ +│ │ • StrategyPage │ │ • ChatInput │ │ • tokenService │ │ +│ │ • AnalysisPage │ │ • SignalCard │ │ • storageService│ │ +│ │ │ │ • ToolCallCard │ │ │ │ +│ │ │ │ • MessageList │ │ │ │ +│ │ │ │ • SidebarConv │ │ │ │ +│ │ │ │ • LoadingSpinner │ │ │ │ +│ │ │ │ • ErrorBoundary │ │ │ │ +│ └────────────────────┘ └────────────────────┘ └────────────────┘ │ +│ │ │ │ │ +│ └──────────────────────┼──────────────────────┘ │ +│ │ │ +│ ┌────────────────────────▼──────────────────────────┐ │ +│ │ Zustand Store │ │ +│ │ ┌─────────────┐ ┌───────────┐ ┌─────────────┐ │ │ +│ │ │ chatStore │ │ uiStore │ │ signalStore │ │ │ +│ │ └─────────────┘ └───────────┘ └─────────────┘ │ │ +│ └────────────────────┬───────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────▼──────────────────────────────┐ │ +│ │ Query Client (React Query) │ │ +│ │ Caching, Sincronización, Invalidación │ │ +│ └────────────────────┬───────────────────────────────┘ │ +│ │ │ +└──────────────────────────────┼───────────────────────────────────────┘ + │ + ┌───────────────────┼───────────────────┐ + │ │ │ +┌──────────▼────────┐ ┌───────▼────────┐ ┌──────▼──────────┐ +│ LLM Agent API │ │ Backend API │ │ WebSocket │ +│ :3085 │ │ :3080 │ │ :3085/ws │ +│ │ │ │ │ │ +│ POST /chat │ │ GET /profiles │ │ message:send │ +│ GET /history │ │ GET /signals │ │ agent:stream │ +│ POST /strategies │ │ POST /orders │ │ agent:complete │ +│ DELETE /convs │ │ GET /portfolio │ │ error │ +│ │ │ │ │ │ +└───────────────────┘ └────────────────┘ └─────────────────┘ +``` + +--- + +## Stack Tecnológico Frontend + +```yaml +Core: + - React: 18.2.0 + - TypeScript: 5.3.0 + - Vite: 6.2.0 + - React Router: 6.18.0 + +State Management: + - Zustand: 4.4.7 + - React Query (TanStack Query): 5.14.0 + +Styling: + - Tailwind CSS: 3.3.0 + - Headless UI: 1.7.0 + - Radix UI: 1.0.0 + +Components & UI: + - React Markdown: 8.0.0 + - Highlight.js: 11.8.0 + - Recharts: 2.10.0 (para gráficos simples) + - lightweight-charts: 4.1.1 (para charts de trading) + +Utilities: + - axios: 1.6.0 + - date-fns: 2.30.0 + - clsx: 2.0.0 + - zustand: 4.4.7 + +Testing: + - Vitest: 1.0.0 + - @testing-library/react: 14.1.0 + - @testing-library/user-event: 14.5.0 + +Dev Tools: + - @types/react: 18.2.0 + - @types/node: 20.9.0 + - ESLint: 8.50.0 + - Prettier: 3.0.0 +``` + +--- + +## Estructura de Directorios Frontend + +``` +apps/frontend/src/ +├── pages/ +│ ├── AssistantPage.tsx # Página principal del asistente +│ ├── ChatPage.tsx # Página de chat conversacional +│ ├── StrategyPage.tsx # Análisis de estrategias +│ └── AnalysisPage.tsx # Análisis detallados +│ +├── modules/ +│ └── llm-agent/ +│ ├── components/ +│ │ ├── ChatWindow.tsx +│ │ ├── ChatMessage.tsx +│ │ ├── ChatInput.tsx +│ │ ├── SignalCard.tsx +│ │ ├── ToolCallCard.tsx +│ │ ├── MessageList.tsx +│ │ ├── SidebarConversations.tsx +│ │ ├── LoadingIndicator.tsx +│ │ └── ErrorBoundary.tsx +│ │ +│ ├── hooks/ +│ │ ├── useChat.ts +│ │ ├── useLlmAgent.ts +│ │ ├── useSignals.ts +│ │ ├── useWebSocket.ts +│ │ └── useMessageStream.ts +│ │ +│ ├── services/ +│ │ ├── llmAgentService.ts # Cliente API LLM Agent +│ │ ├── websocketService.ts # Conexión WS +│ │ ├── llmProviderService.ts # Adaptadores OpenAI/Claude +│ │ └── tokenService.ts # Conteo de tokens +│ │ +│ ├── stores/ +│ │ ├── chatStore.ts # Zustand chat store +│ │ ├── uiStore.ts # UI state +│ │ └── signalStore.ts # Señales y análisis +│ │ +│ ├── types/ +│ │ ├── index.ts +│ │ ├── chat.types.ts +│ │ ├── signal.types.ts +│ │ └── api.types.ts +│ │ +│ ├── utils/ +│ │ ├── messageFormatters.ts +│ │ ├── tokenCounter.ts +│ │ ├── errorHandlers.ts +│ │ └── conversationHelpers.ts +│ │ +│ ├── hooks.test.ts +│ ├── services.test.ts +│ └── components.test.tsx +│ +├── shared/ +│ ├── components/ +│ │ ├── Button.tsx +│ │ ├── Card.tsx +│ │ ├── Modal.tsx +│ │ └── Spinner.tsx +│ │ +│ └── hooks/ +│ └── useWindowSize.ts +│ +├── stores/ +│ └── authStore.ts # Estado de autenticación global +│ +├── services/ +│ ├── api.ts # Configuración axios +│ ├── authService.ts # JWT, login, logout +│ └── storageService.ts # LocalStorage helpers +│ +├── App.tsx +├── main.tsx +├── index.css +└── types.d.ts +``` + +--- + +## Componentes Principales + +### 1. ChatWindow Component + +**Ubicación:** `apps/frontend/src/modules/llm-agent/components/ChatWindow.tsx` + +**Responsabilidad:** Contenedor principal que orquesta el chat conversacional + +```typescript +interface ChatWindowProps { + conversationId: string; + onClose?: () => void; + initialMessage?: string; + theme?: 'light' | 'dark'; +} + +export function ChatWindow({ + conversationId, + onClose, + initialMessage, + theme = 'light' +}: ChatWindowProps) { + // State management + const { messages, isLoading, streamingContent } = useChatStore(); + const { connected, connect, disconnect } = useWebSocket(); + + // Lifecycle + useEffect(() => { + // Conectar y cargar historial + }, [conversationId]); + + // Handlers + const handleSendMessage = (content: string) => { + // Enviar mensaje vía WS + }; + + const handleCancel = () => { + // Cancelar generación + }; + + return ( +
+ + + +
+ ); +} +``` + +**Features:** +- Soporte para múltiples conversaciones +- Auto-scroll a últimos mensajes +- Indicador de conexión +- Tema claro/oscuro +- Responsive design (mobile-first) + +--- + +### 2. ChatMessage Component + +**Ubicación:** `apps/frontend/src/modules/llm-agent/components/ChatMessage.tsx` + +**Responsabilidad:** Renderizar mensaje individual con soporte para markdown, código y tool calls + +```typescript +interface ChatMessageProps { + message: Message; + isStreaming?: boolean; + onFeedback?: (rating: number, comment?: string) => void; +} + +export function ChatMessage({ + message, + isStreaming, + onFeedback +}: ChatMessageProps) { + const [showFeedback, setShowFeedback] = useState(false); + const [feedback, setFeedback] = useState(); + + return ( +
+
+ {message.role === 'user' ? : } +
+ +
+ {/* Renderizar contenido */} + + + {/* Tool calls si existen */} + {message.toolCalls && ( +
+ {message.toolCalls.map((tool) => ( + + ))} +
+ )} + + {/* Signals si existen */} + {message.signals && ( +
+ {message.signals.map((signal) => ( + + ))} +
+ )} + + {/* Feedback */} + {message.role === 'assistant' && ( + + )} +
+ +
+ + {formatTime(message.createdAt)} + + {message.tokensInput && ( + + {message.tokensInput + (message.tokensOutput || 0)} tokens + + )} +
+
+ ); +} +``` + +**Features:** +- Renderizado seguro de Markdown +- Highlight de código con Highlight.js +- Soporte para LaTeX matemático +- Feedback rating (thumbs up/down + comentario) +- Copyable code blocks +- Display de tokens consumidos + +--- + +### 3. ChatInput Component + +**Ubicación:** `apps/frontend/src/modules/llm-agent/components/ChatInput.tsx` + +**Responsabilidad:** Input de usuario con soporte para multi-line, attachments y comandos + +```typescript +interface ChatInputProps { + onSend: (content: string, files?: File[]) => void; + onCancel?: () => void; + isLoading?: boolean; + disabled?: boolean; + maxLength?: number; + placeholder?: string; +} + +export function ChatInput({ + onSend, + onCancel, + isLoading, + disabled, + maxLength = 4000, + placeholder = 'Escribe tu consulta aquí...' +}: ChatInputProps) { + const [content, setContent] = useState(''); + const [files, setFiles] = useState([]); + const textareaRef = useRef(null); + + // Auto-resize textarea + useEffect(() => { + if (textareaRef.current) { + textareaRef.current.style.height = 'auto'; + textareaRef.current.style.height = + Math.min(textareaRef.current.scrollHeight, 200) + 'px'; + } + }, [content]); + + const handleSend = () => { + if (!content.trim()) return; + onSend(content, files); + setContent(''); + setFiles([]); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + // Enviar con Ctrl+Enter o Cmd+Enter + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { + e.preventDefault(); + handleSend(); + } + }; + + return ( +
+ {/* File attachments preview */} + {files.length > 0 && ( +
+ {files.map((file) => ( + setFiles(f => f.filter(x => x !== file))} + /> + ))} +
+ )} + +
+ {/* Attach files */} + + +