trading-platform/docs/02-definicion-modulos/OQI-001-fundamentos-auth/especificaciones/ET-AUTH-006-frontend.md
Adrian Flores Cortes cdec253b02 [TASK-2026-01-25-FRONTEND-ANALYSIS] docs: Add frontend specifications and user stories
- Add 5 frontend specification documents (ET-*-frontend.md):
  - ET-AUTH-006: Authentication module frontend spec
  - ET-ML-008: ML Signals module frontend spec
  - ET-LLM-007: LLM Agent module frontend spec
  - ET-PFM-008: Portfolio Manager frontend spec (design)
  - ET-MKT-003: Marketplace frontend spec (design)

- Add 8 new user stories:
  - US-AUTH-013: Global logout
  - US-AUTH-014: Device management
  - US-ML-008: Ensemble signal view
  - US-ML-009: ICT analysis view
  - US-ML-010: Multi-symbol scan
  - US-LLM-011: Execute trade from chat
  - US-PFM-013: Rebalance alerts
  - US-PFM-014: PDF report generation

- Update task index with completed analysis

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 01:47:27 -06:00

25 KiB

id title type status rf_parent epic version created_date updated_date
ET-AUTH-006 Frontend Specification for Auth Module Specification Done RF-AUTH-003 OQI-001 1.0 2026-01-25 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


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:

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:

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:

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:

<SocialLoginButtons
  providers={['google', 'github']}
  showLabels={true}
  onSuccess={handleSuccess}
/>

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:

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:

const phoneRegex = /^\+[1-9]\d{1,14}$/;
const otpRegex = /^\d{6}$/;

TwoFactorForm

Ubicación: src/components/auth/TwoFactorForm.tsx

Props:

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:

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:

class AuthService {
  // Login
  login(email: string, password: string): Promise<LoginResponse>

  // Registro
  register(data: RegisterRequest): Promise<RegisterResponse>

  // Logout
  logout(): Promise<void>

  // Renovar tokens
  refreshTokens(refreshToken: string): Promise<TokenResponse>

  // Obtener usuario actual
  getCurrentUser(): Promise<UserData>

  // OAuth
  getOAuthUrl(provider: string, redirectTo?: string): Promise<{ authUrl: string; state: string }>
  oauth(provider: string, code: string, state: string): Promise<LoginResponse>
  unlinkOAuth(provider: string): Promise<void>

  // Teléfono
  sendPhoneOTP(phone: string, channel: 'sms' | 'whatsapp'): Promise<{ expiresAt: Date }>
  verifyPhoneOTP(phone: string, code: string): Promise<LoginResponse>

  // 2FA
  setupTwoFactor(): Promise<{ secret: string; qrCode: string; otpauthUrl: string }>
  enableTwoFactor(code: string): Promise<{ backupCodes: string[] }>
  verifyTwoFactor(tempToken: string, code: string): Promise<LoginResponse>
  disableTwoFactor(code: string): Promise<void>

  // Contraseña
  forgotPassword(email: string): Promise<void>
  resetPassword(token: string, password: string): Promise<void>

  // Email
  verifyEmail(token: string): Promise<void>
  resendVerificationEmail(email: string): Promise<void>

  // Sesiones
  getSessions(): Promise<Session[]>
  revokeSession(sessionId: string): Promise<void>
  revokeAllOtherSessions(): Promise<void>
}

export const authService = new AuthService();

Configuración Base:

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

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<void>;
  register: (data: RegisterRequest) => Promise<void>;
  logout: () => Promise<void>;
  refreshUser: () => Promise<void>;
  clearError: () => void;

  // 2FA
  setRequires2FA: (requires: boolean) => void;
  setTempToken: (token: string) => void;
}

export const useAuthStore = create<AuthState>((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()

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 <div>Bienvenido {user?.firstName}</div>;
}

useAuthForm()

function useAuthForm(formType: 'login' | 'register' | 'resetPassword') {
  const form = useForm<LoginFormData | RegisterFormData>({
    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()

function useSessionManager() {
  const [sessions, setSessions] = useState<Session[]>([]);
  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

// 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

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

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

function ProtectedRoute({ children }: { children: ReactNode }) {
  const { isAuthenticated, isLoading } = useAuth();

  if (isLoading) return <LoadingSpinner />;
  if (!isAuthenticated) return <Navigate to="/auth/login" />;

  return children;
}

// Uso:
<Routes>
  <Route path="/auth/login" element={<LoginPage />} />
  <Route path="/dashboard" element={
    <ProtectedRoute>
      <DashboardPage />
    </ProtectedRoute>
  } />
</Routes>

Testing

Unit Tests

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

describe('Login Flow', () => {
  it('debe completar flujo de login y redirigir', async () => {
    render(<LoginPage />);

    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

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

const LoginPage = lazy(() => import('./pages/auth/LoginPage'));
const RegisterPage = lazy(() => import('./pages/auth/RegisterPage'));

<Suspense fallback={<LoadingSpinner />}>
  <Routes>
    <Route path="/auth/login" element={<LoginPage />} />
  </Routes>
</Suspense>

Memoización

const SocialLoginButtons = memo(function SocialLoginButtons(props) {
  // Componente
});

Request Caching

const { data: user } = useQuery({
  queryKey: ['auth', 'me'],
  queryFn: () => authService.getCurrentUser(),
  staleTime: 5 * 60 * 1000, // 5 minutos
});

Referencias