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

1131 lines
25 KiB
Markdown

---
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
<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:**
```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<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:**
```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<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()
```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 <div>Bienvenido {user?.firstName}</div>;
}
```
### useAuthForm()
```typescript
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()
```typescript
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
```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 <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
```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(<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`
```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'));
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/auth/login" element={<LoginPage />} />
</Routes>
</Suspense>
```
### 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)