trading-platform/orchestration/analisis/_archive/2026-01-25/OQI-001-GAPS.md
Adrian Flores Cortes 3c2d98707c [TASK-2026-01-30-ANALISIS-INTEGRACION] chore: Sprint 3 - Purga y limpieza
- Archivados 5 análisis obsoletos a _archive/2026-01-25/
- MASTER-ANALYSIS-PLAN marcada SUPERSEDIDA
- FRONTEND-COMPREHENSIVE-AUDIT marcada COMPLETADA (7+ entregables)
- FRONTEND-MODULE-DOCS marcada CANCELADA (P3, sin progreso)
- BLOCKER-001-TOKEN-REFRESH marcada POSTERGADA
- Actualizado PROJECT-STATUS.md y _INDEX.yml

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 15:26:40 -06:00

22 KiB

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:

// SecuritySettings.tsx línea 237-241
<button type="button" className="...">
  Setup  {/* <- NO TIENE onClick handler */}
</button>

// Necesita:
const [showQRSetup, setShowQRSetup] = useState(false)
const [qrCode, setQrCode] = useState<string>('')
const [twoFASecret, setTwoFASecret] = useState<string>('')
const [verificationCode, setVerificationCode] = useState<string>('')

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:

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:

<form className="space-y-4 max-w-md">
  <input type="password" placeholder="Enter current password" />
  <input type="password" placeholder="Enter new password" />
  <input type="password" placeholder="Confirm new password" />
  <button type="submit">Update Password</button>  {/* <- Sin handler */}
</form>

Implementación Necesaria:

const [passwordForm, setPasswordForm] = useState({
  currentPassword: '',
  newPassword: '',
  confirmPassword: ''
})
const [passwordError, setPasswordError] = useState<string | null>(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
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)
  }
)
  1. 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:

// Login.tsx
const [loginAttempts, setLoginAttempts] = useState(0)
const [accountLocked, setAccountLocked] = useState(false)
const [lockExpiresAt, setLockExpiresAt] = useState<Date | null>(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:

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:

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:

const [otpExpiresAt, setOtpExpiresAt] = useState<Date | null>(null)
// ... nunca se renderiza en UI

Implementación Necesaria:

{step === 'otp' && (
  <div className="text-center text-sm text-gray-400">
    OTP expires in: <CountdownTimer expiresAt={otpExpiresAt} />
  </div>
)}

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 <span>{remaining}</span>
}

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:

<button
  type="button"
  onClick={handleResendOTP}
  disabled={isLoading}  {/* <- Falta: disabled durante cooldown */}
  className="..."
>
  Reenviar codigo
</button>

Problema:

  • Usuario puede spamear resend
  • Costo SMS se multiplica

Implementación:

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:
<button disabled={resendCooldown > 0 || isLoading}>
  {resendCooldown > 0 ? `Reenviar en ${resendCooldown}s` : 'Reenviar codigo'}
</button>

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:

<div className="space-y-2">
  <div className="flex items-center gap-2">
    <div className="flex-1 h-2 bg-gray-700 rounded-full overflow-hidden">
      <div
        className={`h-full transition-all ${
          strength === 'weak' ? 'w-1/3 bg-red-500' :
          strength === 'fair' ? 'w-2/3 bg-yellow-500' :
          'w-full bg-green-500'
        }`}
      />
    </div>
    <span className="text-xs text-gray-400">{strength}</span>
  </div>

  <p className="text-xs text-gray-500">
    Estimated crack time: ~100 years
  </p>
</div>

G-019: Session Timeout Warning

Prioridad: P1 Tipo: UX Esfuerzo: M

Descripción: Sin advertencia previa a expiración de sesión.

Implementación:

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:

{status === 'no-token' && (
  <div className="text-center">
    {/* ... */}
    <Link to="/login" className="btn btn-secondary block">
      Volver a Iniciar Sesion
    </Link>
    <button
      onClick={handleResendEmail}
      className="text-sm text-primary-400 hover:underline mt-4"
    >
      ¿No recibiste el email? Reenviar
    </button>
  </div>
)}

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:

// Antes
<input type="email" placeholder="tu@email.com" />

// Después
<input
  type="email"
  placeholder="tu@email.com"
  aria-label="Email address"
  aria-required="true"
  aria-describedby="email-error"
/>
<div id="email-error" role="alert" aria-live="polite">
  {emailError && <span className="text-red-400">{emailError}</span>}
</div>

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:

// ErrorBoundary.tsx
class ErrorBoundary extends React.Component {
  state = { hasError: false }

  static getDerivedStateFromError() {
    return { hasError: true }
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="min-h-screen flex items-center justify-center">
          <div className="text-center">
            <h1 className="text-2xl font-bold text-white mb-4">
              Something went wrong
            </h1>
            <button onClick={() => window.location.href = '/'}>
              Go Home
            </button>
          </div>
        </div>
      )
    }

    return this.props.children
  }
}

// En módulo auth
<ErrorBoundary>
  <AuthModule />
</ErrorBoundary>

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