Manejo de Errores
Una guía completa sobre cómo manejar errores de manera efectiva, desde los casos más comunes hasta situaciones edge case.Jerarquía de Errores
Camarauth SDK usa una jerarquía clara de errores basada enCamarauthError:
Copy
Ask AI
Error (Nativo JS)
└── CamarauthError (Base del SDK)
├── PinExpiredError
├── RefreshTokenExpiredError
├── AuthenticationError
├── NetworkError
└── ValidationError
Tipos de Errores
Errores de PIN
Copy
Ask AI
try {
const result = await client.checkLogin(pin);
} catch (error) {
if (error instanceof CamarauthError) {
switch (error.code) {
case 'PIN_EXPIRED':
// El PIN ha expirado después de 3 minutos
showToast('Tu código ha expirado. Genera uno nuevo.');
regeneratePin();
break;
case 'PIN_NOT_FOUND':
// PIN no existe o fue limpiado
showToast('Código inválido. Intenta de nuevo.');
break;
case 'PIN_ALREADY_VERIFIED':
// PIN ya fue usado por otro usuario
showToast('Este código ya fue utilizado.');
break;
}
}
}
Errores de Autenticación
Token Expirado
Token Expirado
Copy
Ask AI
try {
const user = await client.getProfile(token);
} catch (error) {
if (error.code === 'TOKEN_EXPIRED') {
// Intentar refrescar token automáticamente
try {
const { token: newToken } = await client.refreshToken(refreshToken);
saveToken(newToken);
// Reintentar request original
const user = await client.getProfile(newToken);
} catch (refreshError) {
// Si falla el refresh, redirigir a login
redirectToLogin();
}
}
}
Token Inválido
Token Inválido
Copy
Ask AI
if (error.code === 'TOKEN_INVALID' || error.code === 'TOKEN_NOT_PROVIDED') {
// Token manipulado o no proporcionado
// Limpiar sesión y redirigir
clearSession();
redirectToLogin();
}
Refresh Token Expirado
Refresh Token Expirado
Copy
Ask AI
if (error.code === 'REFRESH_TOKEN_EXPIRED') {
// La sesión ha expirado completamente
// Usuario debe autenticarse de nuevo
clearSession();
showToast('Tu sesión ha expirado. Por favor inicia sesión de nuevo.');
redirectToLogin();
}
Errores de Red
Copy
Ask AI
try {
await client.registerPin(pin);
} catch (error) {
if (error.code === 'NETWORK_ERROR') {
// Sin conexión a internet
showToast('Sin conexión. Verifica tu red.');
// Opcional: Reintentar automáticamente
setTimeout(() => {
retryRegistration();
}, 3000);
}
if (error.code === 'SOCKET_ERROR') {
// Error de conexión WebSocket
console.error('WebSocket error:', error);
// Intentar reconectar
reconnectSocket();
}
}
Patrones de Manejo
1. Error Boundary (React)
Copy
Ask AI
// components/ErrorBoundary.tsx
import { Component, ErrorInfo, ReactNode } from 'react';
import { CamarauthError } from 'camarauth-sdk';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: CamarauthError | null;
}
export class AuthErrorBoundary extends Component<Props, State> {
state: State = {
hasError: false,
error: null
};
static getDerivedStateFromError(error: CamarauthError): State {
return { hasError: true, error };
}
componentDidCatch(error: CamarauthError, errorInfo: ErrorInfo) {
console.error('Auth error:', error);
console.error('Error info:', errorInfo);
// Reportar a servicio de monitoreo
reportError(error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<ErrorDisplay
error={this.state.error}
onRetry={() => this.setState({ hasError: false, error: null })}
/>
);
}
return this.props.children;
}
}
2. Componente de Error Reutilizable
Copy
Ask AI
// components/ErrorDisplay.tsx
import { CamarauthError } from 'camarauth-sdk';
interface ErrorDisplayProps {
error: CamarauthError | null;
onRetry?: () => void;
onDismiss?: () => void;
}
const errorConfig: Record<string, { icon: string; title: string; message: string; action: string }> = {
PIN_EXPIRED: {
icon: '⏰',
title: 'PIN Expirado',
message: 'Tu código ha expirado. Genera uno nuevo.',
action: 'retry'
},
NETWORK_ERROR: {
icon: '📡',
title: 'Sin Conexión',
message: 'Verifica tu conexión a internet.',
action: 'retry'
},
RATE_LIMIT_EXCEEDED: {
icon: '🚦',
title: 'Demasiados Intentos',
message: 'Has alcanzado el límite. Intenta más tarde.',
action: 'wait'
},
default: {
icon: '⚠️',
title: 'Error',
message: 'Ocurrió un error inesperado.',
action: 'retry'
}
};
export function ErrorDisplay({ error, onRetry, onDismiss }: ErrorDisplayProps) {
if (!error) return null;
const config = errorConfig[error.code] || errorConfig.default;
return (
<div className="error-container">
<div className="error-icon">{config.icon}</div>
<h3>{config.title}</h3>
<p>{config.message}</p>
{config.action === 'retry' && onRetry && (
<button onClick={onRetry} className="retry-button">
Intentar de Nuevo
</button>
)}
{onDismiss && (
<button onClick={onDismiss} className="dismiss-button">
Cerrar
</button>
)}
</div>
);
}
3. Hook de Manejo de Errores
Copy
Ask AI
// hooks/useAuthError.ts
import { useState, useCallback } from 'react';
import { CamarauthError } from 'camarauth-sdk';
export function useAuthError() {
const [error, setError] = useState<CamarauthError | null>(null);
const handleError = useCallback((err: unknown) => {
if (err instanceof CamarauthError) {
setError(err);
// Log para debugging
console.error('Auth Error:', {
code: err.code,
message: err.message,
statusCode: err.statusCode,
timestamp: new Date().toISOString()
});
return err;
}
// Error no manejado
const unknownError = new CamarauthError(
'Error desconocido',
'UNKNOWN_ERROR'
);
setError(unknownError);
return unknownError;
}, []);
const clearError = useCallback(() => {
setError(null);
}, []);
return { error, handleError, clearError };
}
4. Retry con Backoff
Copy
Ask AI
// utils/retry.ts
export async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
// Solo reintentar errores recuperables
if (!isRetryableError(error)) {
throw error;
}
// Backoff exponencial con jitter
const jitter = Math.random() * baseDelay;
const delay = (baseDelay * Math.pow(2, i)) + jitter;
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
await sleep(delay);
}
}
throw new Error('Unreachable');
}
function isRetryableError(error: any): boolean {
const retryableCodes = [
'NETWORK_ERROR',
'SOCKET_ERROR',
'TIMEOUT_ERROR',
'RATE_LIMIT_EXCEEDED'
];
return retryableCodes.includes(error?.code);
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
UX para Errores
Estados de UI
Copy
Ask AI
function AuthPage() {
const { status, error, generate } = usePinAuth({...});
const { handleError } = useAuthError();
return (
<div>
{status === 'idle' && (
<button onClick={generate}>Generar PIN</button>
)}
{status === 'polling' && (
<LoadingState message="Esperando verificación..." />
)}
{status === 'error' && error && (
<ErrorDisplay
error={error}
onRetry={generate}
/>
)}
{status === 'expired' && (
<div>
<p>El PIN expiró</p>
<button onClick={generate}>Reintentar</button>
</div>
)}
</div>
);
}
Toast Notifications
Copy
Ask AI
import { toast } from 'sonner';
function useAuthNotifications() {
const {
status,
error
} = usePinAuth({
...options,
onSuccess: (user) => {
toast.success(`¡Bienvenido, ${user.name}!`, {
description: 'Has iniciado sesión correctamente'
});
},
onError: (error) => {
toast.error('Error de autenticación', {
description: getUserFriendlyMessage(error)
});
},
onExpire: () => {
toast.warning('PIN expirado', {
description: 'Genera un nuevo PIN para continuar'
});
}
});
return { status };
}
function getUserFriendlyMessage(error: CamarauthError): string {
const messages: Record<string, string> = {
PIN_EXPIRED: 'Tu código ha expirado',
NETWORK_ERROR: 'Problema de conexión',
RATE_LIMIT_EXCEEDED: 'Demasiados intentos',
TOKEN_EXPIRED: 'Tu sesión expiró',
default: 'Error desconocido'
};
return messages[error.code] || messages.default;
}
Logging de Errores
Error Reporter
Copy
Ask AI
// utils/error-reporter.ts
interface ErrorContext {
userId?: string;
pin?: string;
timestamp: number;
userAgent: string;
url: string;
}
class ErrorReporter {
report(error: Error, context?: Partial<ErrorContext>) {
const fullContext: ErrorContext = {
timestamp: Date.now(),
userAgent: navigator.userAgent,
url: window.location.href,
...context
};
// Enviar al backend
fetch('/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: error.message,
stack: error.stack,
context: fullContext
})
}).catch(console.error);
// Log en desarrollo
if (process.env.NODE_ENV === 'development') {
console.error('[ErrorReporter]', error, fullContext);
}
}
}
export const errorReporter = new ErrorReporter();
Checklist de Manejo de Errores
- Capturar todos los errores posibles del SDK
- Diferenciar errores recuperables vs no recuperables
- Implementar retry con backoff para errores de red
- Mostrar mensajes amigables al usuario (no técnicos)
- Loguear errores para debugging
- Manejar timeouts apropiadamente
- Implementar refresh automático de tokens
- Proporcionar acciones de recuperación (retry, logout)
- Validar inputs antes de enviar al servidor
- Usar Error Boundaries en React

