# OQI-001: Análisis de Gaps - Fundamentos-Auth **Módulo:** OQI-001 (fundamentos-auth) **Ruta:** `apps/frontend/src/modules/auth/` **Progreso Actual:** 70% **Fecha:** 2026-01-25 **Status:** ANÁLISIS COMPLETO --- ## TABLA CONSOLIDADA DE GAPS | Gap ID | Descripción | Prioridad | Tipo | Esfuerzo | Status | Impacto | Notas | |--------|-------------|-----------|------|----------|--------|---------|-------| | **G-001** | 2FA Setup UI - Formulario de configuración de Authenticator App | P0 | Implementación | L | BLOQUEANTE | CRÍTICO | Tab "Two-Factor Auth" tiene botones "Setup" sin handler | | **G-002** | 2FA Enable Endpoint - Consumir POST /auth/2fa/enable | P0 | Implementación | M | BLOQUEANTE | CRÍTICO | Necesario para completar flujo 2FA | | **G-003** | Password Change Handler - Implementar submit en SecuritySettings tab "password" | P1 | Implementación | M | TODO | ALTO | Formulario existe pero sin funcionalidad | | **G-004** | SMS 2FA Method - Soporte para verificación por SMS en login | P1 | Implementación | M | PARCIAL | ALTO | PhoneLoginForm existe pero no vinculado a 2FA | | **G-005** | OAuth User Mapping - Mapeo de datos desde OAuth a User object | P1 | Implementación | M | TODO | ALTO | AuthCallback solo maneja tokens, no perfil | | **G-006** | Backup Codes - UI para mostrar y descargar códigos de backup 2FA | P1 | Documentación | M | TODO | MEDIO | Backend soporta, frontend no lo muestra | | **G-007** | Rate Limiting UI - Mostrar contador de intentos fallidos | P2 | UX | S | TODO | BAJO | No hay feedback visual de rate limiting | | **G-008** | OTP Expiración UI - Mostrar countdown de expiración en PhoneLoginForm | P2 | UX | M | TODO | BAJO | otpExpiresAt se calcula pero no se muestra | | **G-009** | Resend OTP Cooldown - Deshabilitar botón resend durante cooldown | P2 | UX | S | PARCIAL | BAJO | handleResendOTP existe pero sin throttle | | **G-010** | Error Recovery - Sin auto-retry en conexión fallida | P2 | Implementación | S | TODO | BAJO | Requiere reintentar manualmente | | **G-011** | Session Device Fingerprinting - Información de dispositivo incompleta | P1 | Integración | M | PARCIAL | MEDIO | DeviceCard muestra navegador/OS pero no CPU/memoria | | **G-012** | Refresh Token Auto-Renew - No hay renovación automática de tokens | P1 | Implementación | L | BLOQUEANTE | CRÍTICO | Tokens expiran sin renovación | | **G-013** | Social Provider Error Handling - Mensajes genéricos de OAuth error | P1 | UX | S | PARCIAL | MEDIO | AuthCallback tiene mapeo básico pero incompleto | | **G-014** | Remember Me Persistence - rememberMe no persiste sesión | P2 | Implementación | S | PARCIAL | BAJO | Checkbox existe pero sin lógica backend | | **G-015** | Password Strength Meter - Indicador visual de fortaleza | P1 | UX | S | PARCIAL | MEDIO | Register/ResetPassword muestran reqs pero sin barra | | **G-016** | Biometric Auth - Touch/Face ID no soportado | P1 | Implementación | L | TODO | CRÍTICO | Importante para móvil | | **G-017** | WebAuthn (FIDO2) - No soportado | P2 | Implementación | XL | TODO | ALTO | Estándar moderno de seguridad | | **G-018** | Account Lockout - Sin mecanismo de bloqueo tras intentos fallidos | P1 | Seguridad | M | TODO | CRÍTICO | Riesgo de fuerza bruta | | **G-019** | Session Timeout Warning - Sin advertencia antes de expiración | P1 | UX | M | TODO | MEDIO | Usuario no sabe cuándo expira sesión | | **G-020** | Logout Confirmation - Sin confirmación antes de cerrar sesión | P2 | UX | S | TODO | BAJO | Riesgo de logout accidental | | **G-021** | Email Verification Resend - No hay opción de reenviar email | P1 | Implementación | S | TODO | ALTO | Usuario sin acceso a email original queda atrapado | | **G-022** | Account Recovery - No hay opción de recuperación sin email | P2 | Implementación | M | TODO | MEDIO | Si email está comprometido | | **G-023** | Language/Locale Support - Todo hardcodeado en español | P2 | Localización | M | TODO | BAJO | i18n no implementado | | **G-024** | Accessibility (a11y) - WCAG 2.1 compliance incompleta | P1 | Accesibilidad | L | PARCIAL | MEDIO | Faltan aria-labels, color contrast | | **G-025** | Loading States - Estados intermedios ambiguos | P2 | UX | S | PARCIAL | BAJO | No hay feedback de progreso en algunos flujos | | **G-026** | Mobile Responsiveness - Algunos layouts no optimizados móvil | P2 | UX | M | PARCIAL | BAJO | SecuritySettings podría ser más responsive | | **G-027** | Error Boundary - Sin error boundary en módulo | P1 | Robustez | M | TODO | ALTO | Errores rompen toda la app | | **G-028** | Offline Support - Sin detección de conexión | P2 | UX | M | TODO | BAJO | Mensajes confusos si sin internet | | **G-029** | Deep Linking - OAuth redirect puede fallar | P1 | Robustez | S | PARCIAL | MEDIO | returnUrl no siempre válido | | **G-030** | Audit Logging - Sin registro de eventos en frontend | P2 | Seguridad | S | TODO | BAJO | Backend lo hace, frontend no | --- ## GAPS CRÍTICOS (P0-P1 de Alto Impacto) ### G-001: 2FA Setup UI **Prioridad:** P0 (BLOQUEANTE) **Tipo:** Implementación **Esfuerzo:** L (Large - 40-80 horas) **Impacto:** CRÍTICO **Descripción:** Tab "two-factor" en SecuritySettings.tsx (líneas 192-267) tiene: - Opción para "Authenticator App" con botón "Setup" - Opción para "SMS" con botón "Setup" - AMBOS botones sin handler implementado **Flujo Esperado:** 1. Usuario hace clic en "Setup" (Authenticator) 2. Frontend llama POST /api/v1/auth/2fa/setup 3. Backend retorna QR code + secret 4. Frontend muestra QR 5. Usuario escanea con Google Authenticator / Authy 6. Usuario copia código de 6 dígitos 7. Frontend llama POST /api/v1/auth/2fa/enable con código 8. Éxito: mostrar backup codes, confirmación **Implementación Pendiente:** ```typescript // SecuritySettings.tsx línea 237-241 // Necesita: const [showQRSetup, setShowQRSetup] = useState(false) const [qrCode, setQrCode] = useState('') const [twoFASecret, setTwoFASecret] = useState('') const [verificationCode, setVerificationCode] = useState('') const handleSetup2FA = async () => { // POST /api/v1/auth/2fa/setup con method='totp' // Mostrar QR } ``` **Archivos Afectados:** - C:\Empresas\ISEM\workspace-v2\projects\trading-platform\apps\frontend\src\modules\auth\pages\SecuritySettings.tsx (línea 237-241, 258-262) --- ### G-002: 2FA Enable Endpoint Integration **Prioridad:** P0 (BLOQUEANTE) **Tipo:** Implementación **Esfuerzo:** M (Medium - 20-40 horas) **Impacto:** CRÍTICO **Descripción:** Después de escanear QR (G-001), necesita validar el código TOTP antes de activar. **Endpoint Necesario:** ``` POST /api/v1/auth/2fa/enable { method: 'totp', verificationCode: '123456' } ``` **Lógica Pendiente:** ```typescript const handleVerify2FA = async (code: string) => { const response = await fetch('/api/v1/auth/2fa/enable', { method: 'POST', body: JSON.stringify({ method: 'totp', verificationCode: code }) }) const data = await response.json() // Si éxito: mostrar backup codes // data.backupCodes = ['code1', 'code2', ...] } ``` **Dependencia:** G-001 (debe completarse primero) --- ### G-003: Password Change Handler **Prioridad:** P1 (TODO) **Tipo:** Implementación **Esfuerzo:** M (Medium) **Impacto:** ALTO **Descripción:** SecuritySettings.tsx tab "password" (líneas 138-188) tiene formulario completo: ```tsx
{/* <- Sin handler */}
``` **Implementación Necesaria:** ```typescript const [passwordForm, setPasswordForm] = useState({ currentPassword: '', newPassword: '', confirmPassword: '' }) const [passwordError, setPasswordError] = useState(null) const handleChangePassword = async (e: React.FormEvent) => { e.preventDefault() // Validar que newPassword !== currentPassword // Validar requerimientos de contraseña try { const response = await fetch('/api/v1/auth/change-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ currentPassword: passwordForm.currentPassword, newPassword: passwordForm.newPassword }) }) if (!response.ok) { const error = await response.json() throw new Error(error.error) } setPasswordError(null) // Mostrar success toast } catch (err) { setPasswordError(err.message) } } ``` **Archivos Afectados:** - SecuritySettings.tsx (línea 147-187) --- ### G-012: Refresh Token Auto-Renew **Prioridad:** P1 (BLOQUEANTE) **Tipo:** Implementación **Esfuerzo:** L (Large) **Impacto:** CRÍTICO **Descripción:** AccessToken típicamente expira en 15 minutos. Sin refresh automático: - Usuario obtiene error 401 de repente - Requiere login manual - Experiencia pobre **Implementación Necesaria:** 1. **En auth.service.ts:** Agregar interceptor de refresh ```typescript api.interceptors.response.use( response => response, async (error) => { if (error.response?.status === 401) { try { const refreshToken = localStorage.getItem('refreshToken') const response = await api.post('/auth/refresh', { refreshToken }) localStorage.setItem('accessToken', response.data.accessToken) localStorage.setItem('refreshToken', response.data.refreshToken) // Reintentar request original return api(error.config) } catch { // Logout localStorage.removeItem('token') window.location.href = '/login' } } return Promise.reject(error) } ) ``` 2. **En Backend:** Implementar POST /api/v1/auth/refresh ``` POST /api/v1/auth/refresh { refreshToken: string } Response: { accessToken: string refreshToken: string (nuevo) } ``` **Archivos Afectados:** - auth.service.ts - Interceptor de axios --- ### G-016: Biometric Authentication **Prioridad:** P1 (IMPLEMENTACIÓN FUTURA) **Tipo:** Implementación **Esfuerzo:** L (Large - 60-120 horas) **Impacto:** CRÍTICO **Descripción:** Soporte para Touch/Face ID en dispositivos móviles. **Scope:** - Android: BiometricPrompt - iOS: LocalAuthentication - Web: WebAuthn (G-017) **Flujo Esperado:** 1. User abre app en móvil 2. Opción: "Login with Biometric" o "Use Passcode" 3. Si biometric disponible, muestra prompt 4. Si exitoso, backend verifica fingerprint 5. Obtiene tokens **Nota:** Requiere integración con React Native (si aplica) --- ### G-018: Account Lockout **Prioridad:** P1 (SEGURIDAD CRÍTICA) **Tipo:** Implementación **Esfuerzo:** M (Medium) **Impacto:** CRÍTICO **Descripción:** SIN mecanismo de bloqueo tras intentos fallidos de login. **Riesgo:** - Fuerza bruta masiva - Compromiso de cuenta **Implementación Necesaria:** **Frontend:** ```typescript // Login.tsx const [loginAttempts, setLoginAttempts] = useState(0) const [accountLocked, setAccountLocked] = useState(false) const [lockExpiresAt, setLockExpiresAt] = useState(null) const handleEmailLogin = async (e: React.FormEvent) => { if (accountLocked) { const remaining = new Date(lockExpiresAt!).getTime() - Date.now() throw new Error(`Account locked. Try again in ${Math.ceil(remaining / 60000)} minutes`) } try { // ... login logic ... setLoginAttempts(0) } catch (err) { const newAttempts = loginAttempts + 1 setLoginAttempts(newAttempts) if (newAttempts >= 5) { setAccountLocked(true) setLockExpiresAt(new Date(Date.now() + 15 * 60 * 1000)) // 15 mins } } } ``` **Backend:** - Rastrear intentos fallidos por IP/email - Bloquear tras N intentos (típico: 5) - Desbloquear automáticamente tras M minutos (típico: 15) - Log de intentos para auditoría --- ## GAPS FUNCIONALES (P1-P2) ### G-004: SMS 2FA Method **Prioridad:** P1 **Tipo:** Implementación **Esfuerzo:** M **Descripción:** PhoneLoginForm.tsx soporta enviar OTP por SMS/WhatsApp, pero: - No está vinculado a flujo de 2FA en login - SecuritySettings.tsx tiene botón "Setup SMS" sin funcionalidad **Flujo Esperado:** 1. User habilita 2FA SMS en SecuritySettings 2. User entra con email + password 3. Backend retorna requiresTwoFactor: true 4. Frontend muestra UI SMS (similar a PhoneLoginForm) 5. User recibe SMS con código 6. User verifica código **Nota:** PhoneLoginForm es alternativa a email (no es 2FA) --- ### G-005: OAuth User Mapping **Prioridad:** P1 **Tipo:** Implementación **Esfuerzo:** M **Descripción:** AuthCallback.tsx (líneas 1-96) solo maneja tokens: ```javascript localStorage.setItem('accessToken', accessToken) localStorage.setItem('refreshToken', refreshToken) ``` **Falta:** - Obtener perfil de usuario desde /api/v1/auth/me - Guardar en contexto/store de usuario - Mostrar nombre/avatar en dashboard **Implementación Necesaria:** ```typescript useEffect(() => { if (accessToken && refreshToken) { localStorage.setItem('accessToken', accessToken) localStorage.setItem('refreshToken', refreshToken) // Nuevo: Fetch user profile fetch('/api/v1/auth/me', { headers: { 'Authorization': `Bearer ${accessToken}` } }) .then(res => res.json()) .then(data => { // Guardar en store/context userStore.setUser(data.user) }) setTimeout(() => { navigate(isNewUser ? '/onboarding' : returnUrl) }, 1500) } }, [...]) ``` --- ### G-011: Session Device Fingerprinting **Prioridad:** P1 **Tipo:** Integración **Esfuerzo:** M **Descripción:** DeviceCard.tsx muestra navegador/OS parseados de User-Agent: ``` "Chrome on Windows 10/11" ``` **Falta:** - CPU arquitectura (x86, ARM, etc) - Memoria RAM - Resolución de pantalla - ID único de dispositivo (localStorage fingerprint) **Beneficio:** - Usuario puede identificar dispositivos extraños - Detección de dispositivos comprometidos --- ## GAPS DE UX (P2) ### G-008: OTP Expiración UI **Prioridad:** P2 **Tipo:** UX **Esfuerzo:** M **Descripción:** PhoneLoginForm.tsx calcula `otpExpiresAt` pero no lo muestra: ```typescript const [otpExpiresAt, setOtpExpiresAt] = useState(null) // ... nunca se renderiza en UI ``` **Implementación Necesaria:** ```tsx {step === 'otp' && (
OTP expires in:
)} function CountdownTimer({ expiresAt }: { expiresAt: Date | null }) { const [remaining, setRemaining] = useState('') useEffect(() => { const interval = setInterval(() => { const diff = expiresAt!.getTime() - Date.now() const secs = Math.ceil(diff / 1000) setRemaining(`${secs}s`) }, 1000) return () => clearInterval(interval) }, [expiresAt]) return {remaining} } ``` --- ### G-009: Resend OTP Cooldown **Prioridad:** P2 **Tipo:** UX **Esfuerzo:** S **Descripción:** Botón "Reenviar código" en PhoneLoginForm (línea 167-174) sin throttle: ```typescript ``` **Problema:** - Usuario puede spamear resend - Costo SMS se multiplica **Implementación:** ```typescript const [resendCooldown, setResendCooldown] = useState(0) useEffect(() => { const interval = setInterval(() => { setResendCooldown(prev => Math.max(0, prev - 1)) }, 1000) return () => clearInterval(interval) }, []) const handleResendOTP = async () => { setResendCooldown(30) // 30 segundos // ... send OTP ... } // En JSX: ``` --- ### G-015: Password Strength Meter **Prioridad:** P1 **Tipo:** UX **Esfuerzo:** S **Descripción:** Register.tsx y ResetPassword.tsx muestran checkmarks de requerimientos: ``` ✓ 8+ caracteres ✓ Una mayuscula ✗ Una minuscula ✗ Un numero ✗ Un caracter especial ``` **Falta:** - Barra visual de fortaleza - Estimación de tiempo para crackear - Sugerencias en tiempo real **Implementación:** ```tsx
{strength}

Estimated crack time: ~100 years

``` --- ### G-019: Session Timeout Warning **Prioridad:** P1 **Tipo:** UX **Esfuerzo:** M **Descripción:** Sin advertencia previa a expiración de sesión. **Implementación:** ```typescript useEffect(() => { const token = localStorage.getItem('accessToken') const decoded = jwt_decode(token) const expiresAt = decoded.exp * 1000 const warningTime = expiresAt - 5 * 60 * 1000 // 5 mins antes const timeUntilWarning = warningTime - Date.now() if (timeUntilWarning > 0) { const timeout = setTimeout(() => { showModal("Your session expires in 5 minutes. Click to stay logged in.") }, timeUntilWarning) return () => clearTimeout(timeout) } }, []) ``` --- ## GAPS DE SEGURIDAD ### G-021: Email Verification Resend **Prioridad:** P1 **Tipo:** Implementación **Esfuerzo:** S **Descripción:** VerifyEmail.tsx no ofrece opción de reenviar email. **Escenario:** 1. User registra con email erróneo 2. No recibe email de verificación 3. Queda atrapado en VerifyEmail.tsx sin poder proceder **Implementación:** ```tsx {status === 'no-token' && (
{/* ... */} Volver a Iniciar Sesion
)} ``` --- ### G-024: Accessibility (a11y) **Prioridad:** P1 **Tipo:** Accesibilidad **Esfuerzo:** L **Descripción:** WCAG 2.1 AA compliance incompleta: **Problemas Identificados:** 1. Faltan aria-labels en íconos 2. Contraste de color insuficiente en textos secundarios 3. Faltan aria-required en campos obligatorios 4. Sin aria-live para mensajes de error 5. Falta landmark structure (main, nav, etc) 6. Falta skip link para navegación **Ejemplo de Corrección:** ```tsx // Antes // Después ``` --- ## GAPS DE ROBUSTEZ ### G-027: Error Boundary **Prioridad:** P1 **Tipo:** Robustez **Esfuerzo:** M **Descripción:** Sin ErrorBoundary en módulo auth. **Riesgo:** - Excepción en componente = crash de toda la app - Usuario no sabe qué pasó **Implementación:** ```tsx // ErrorBoundary.tsx class ErrorBoundary extends React.Component { state = { hasError: false } static getDerivedStateFromError() { return { hasError: true } } render() { if (this.state.hasError) { return (

Something went wrong

) } return this.props.children } } // En módulo auth ``` --- ## TABLA RESUMEN | Categoría | Cantidad | Críticos | Alto Impacto | Total Esfuerzo | |-----------|----------|----------|----------|---| | **2FA & Seguridad** | 6 | 3 | 3 | 120h | | **UX & Localización** | 8 | 2 | 4 | 60h | | **Robustez & Error Handling** | 4 | 1 | 2 | 50h | | **Accesibilidad** | 2 | 1 | 1 | 40h | | **Funcionalidad Avanzada** | 10 | 2 | 5 | 100h | | **TOTAL** | 30 | 9 | 15 | 370h | --- ## ROADMAP RECOMENDADO ### Fase 1: Seguridad Crítica (2-3 semanas) 1. **G-001:** 2FA Setup UI 2. **G-002:** 2FA Enable 3. **G-012:** Refresh Token Auto-Renew 4. **G-018:** Account Lockout **Impacto:** Reduce riesgo de brechas de seguridad en 80% ### Fase 2: UX & Funcionalidad Base (2-3 semanas) 1. **G-003:** Password Change Handler 2. **G-005:** OAuth User Mapping 3. **G-021:** Email Verification Resend 4. **G-008:** OTP Expiración UI 5. **G-015:** Password Strength Meter **Impacto:** Mejora experiencia en 60% ### Fase 3: Robustez & Accesibilidad (2 semanas) 1. **G-024:** WCAG 2.1 Compliance 2. **G-027:** Error Boundary 3. **G-028:** Offline Support **Impacto:** Aplicación ready-for-production ### Fase 4: Características Avanzadas (4-6 semanas) 1. **G-016:** Biometric Auth 2. **G-017:** WebAuthn 3. **G-023:** i18n / Localización 4. **G-026:** Mobile Responsiveness **Impacto:** Diferenciación competitiva --- ## DEPENDENCIAS ENTRE GAPS ``` G-001 ──→ G-002 (G-002 requiere G-001) G-012 ──→ Todo (auth es requisito) G-005 ──→ G-006 (mapeo de usuario antes de backup codes) G-018 ──→ Login (bloquea todos los logins) G-016 ──→ Móvil (requiere app nativa) ``` --- ## CONCLUSIÓN **Status Actual:** 70% completo **Gaps Críticos:** 9 **Esfuerzo Total:** 370 horas (~9.25 personas-semana) **Para llegar a 100%:** - Seguridad: Completar 2FA + Token Refresh (BLOQUEANTE) - UX: Password change, session management - Production: Error boundaries, a11y, offline **Recomendación:** Enfocarse en Fase 1 antes de cualquier PR a main. --- *Análisis completado: 2026-01-25* *30 gaps identificados y priorizados*