Saltar al contenido principal

Manejo de Errores

Aprende cómo manejar errores de manera elegante en tu integración con la API de LosCenotes.

Formato de Respuesta de Error

Todos los errores de la API siguen una estructura consistente:

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Uno o más campos son inválidos",
"status": 400,
"details": {
"field": "email",
"constraint": "Formato de email inválido"
},
"requestId": "req_1234567890abcdef",
"timestamp": "2024-01-15T10:30:00Z"
},
"_sandbox": true // Solo en modo sandbox
}

Códigos de Estado HTTP

La API de LosCenotes usa códigos de estado HTTP estándar:

Código de EstadoSignificadoDescripción
200OKPetición exitosa
201CreatedRecurso creado exitosamente
400Bad RequestParámetros de petición inválidos
401UnauthorizedClave API inválida o faltante
403ForbiddenPermisos insuficientes
404Not FoundRecurso no encontrado
409ConflictEl recurso ya existe
422Unprocessable EntityFalló la validación
429Too Many RequestsLímite de tasa excedido
500Internal Server ErrorError del servidor
503Service UnavailableServicio temporalmente no disponible

Códigos de Error

Errores de Autenticación

INVALID_API_KEY

{
"success": false,
"error": {
"code": "INVALID_API_KEY",
"message": "La clave API proporcionada es inválida",
"status": 401
}
}

AUTHENTICATION_REQUIRED

{
"success": false,
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "Se requiere autenticación para este endpoint",
"status": 401
}
}

INSUFFICIENT_PERMISSIONS

{
"success": false,
"error": {
"code": "INSUFFICIENT_PERMISSIONS",
"message": "Tu clave API no tiene permisos para esta acción",
"status": 403
}
}

Errores de Validación

VALIDATION_ERROR

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Uno o más campos son inválidos",
"status": 400,
"details": [
{
"field": "name",
"constraint": "El nombre debe tener entre 1 y 100 caracteres"
},
{
"field": "email",
"constraint": "Formato de email inválido"
}
]
}
}

MISSING_REQUIRED_FIELD

{
"success": false,
"error": {
"code": "MISSING_REQUIRED_FIELD",
"message": "Falta un campo requerido",
"status": 400,
"details": {
"field": "cenoteId"
}
}
}

Errores de Recursos

RESOURCE_NOT_FOUND

{
"success": false,
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "El recurso solicitado no fue encontrado",
"status": 404,
"details": {
"resource": "cenote",
"id": "c3n0t3-not-found"
}
}
}

RESOURCE_ALREADY_EXISTS

{
"success": false,
"error": {
"code": "RESOURCE_ALREADY_EXISTS",
"message": "Ya existe un recurso con estos identificadores",
"status": 409,
"details": {
"resource": "reservation",
"conflict": "duplicate_booking"
}
}
}

Errores de Lógica de Negocio

CENOTE_NOT_AVAILABLE

{
"success": false,
"error": {
"code": "CENOTE_NOT_AVAILABLE",
"message": "El cenote no está disponible para la fecha seleccionada",
"status": 422,
"details": {
"cenoteId": "c3n0t3-1234",
"date": "2024-01-15",
"reason": "mantenimiento"
}
}
}

INSUFFICIENT_CAPACITY

{
"success": false,
"error": {
"code": "INSUFFICIENT_CAPACITY",
"message": "No hay suficiente capacidad para la reservación solicitada",
"status": 422,
"details": {
"requested": 10,
"available": 5,
"cenoteId": "c3n0t3-1234",
"date": "2024-01-15"
}
}
}

Errores de Límite de Tasa

RATE_LIMIT_EXCEEDED

{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Demasiadas peticiones. Por favor intenta más tarde",
"status": 429,
"details": {
"limit": 1000,
"window": "1 hora",
"resetTime": "2024-01-15T11:00:00Z"
}
}
}

Mejores Prácticas de Manejo de Errores

1. Siempre Verifica el Campo Success

// Ejemplo de TypeScript
const response = await client.cenotes.get(cenoteId);

if (!response.success) {
console.error("Error de API:", response.error);

// Maneja códigos de error específicos
switch (response.error.code) {
case "RESOURCE_NOT_FOUND":
// Maneja no encontrado
break;
case "RATE_LIMIT_EXCEEDED":
// Implementa estrategia de backoff
break;
default:
// Maneja error genérico
break;
}

return;
}

// Éxito - usa response.data
console.log(response.data);

2. Implementa Backoff Exponencial para Límites de Tasa

async function apiCallWithRetry<T>(
apiCall: () => Promise<T>,
maxRetries: number = 3,
baseDelay: number = 1000
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await apiCall();
} catch (error) {
if (error.status === 429 && attempt < maxRetries - 1) {
const delay = baseDelay * Math.pow(2, attempt);
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
throw error;
}
}

throw new Error("Máximo de reintentos excedido");
}

// Uso
const cenotes = await apiCallWithRetry(() =>
client.cenotes.list({ page: 1, perPage: 10 })
);

3. Valida la Entrada Antes de las Llamadas API

function validateReservationData(data: ReservationRequest): string[] {
const errors: string[] = [];

if (!data.cenoteId) {
errors.push("El ID del cenote es requerido");
}

if (!data.date || new Date(data.date) < new Date()) {
errors.push("Se requiere una fecha futura válida");
}

if (!data.visitors || data.visitors < 1) {
errors.push("Se requiere al menos un visitante");
}

return errors;
}

// Uso
const validationErrors = validateReservationData(reservationData);
if (validationErrors.length > 0) {
console.error("Errores de validación:", validationErrors);
return;
}

4. Maneja Errores de Red

import axios from "axios";

try {
const response = await client.cenotes.list();
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.code === "ECONNREFUSED") {
console.error("Conexión rechazada - el servicio puede estar caído");
} else if (error.code === "ETIMEDOUT") {
console.error("Tiempo de espera agotado - intenta más tarde");
} else if (!error.response) {
console.error("Error de red:", error.message);
}
}

throw error;
}

5. Registra Errores para Depuración

interface ErrorLog {
timestamp: string;
requestId?: string;
endpoint: string;
method: string;
errorCode: string;
errorMessage: string;
context?: any;
}

function logError(error: any, context: Partial<ErrorLog>): void {
const errorLog: ErrorLog = {
timestamp: new Date().toISOString(),
requestId: error.requestId,
endpoint: context.endpoint || "desconocido",
method: context.method || "desconocido",
errorCode: error.code || "UNKNOWN_ERROR",
errorMessage: error.message || "Ocurrió un error desconocido",
context: context.context,
};

console.error("Error de API LosCenotes:", JSON.stringify(errorLog, null, 2));

// Envía a tu servicio de logging
// logger.error(errorLog);
}

Manejo de Errores por SDK

SDK de TypeScript

import { LosCenotesClient, LosCenotesError } from "@loscenotes/partner-sdk";

try {
const cenote = await client.cenotes.get("invalid-id");
} catch (error) {
if (error instanceof LosCenotesError) {
console.error("Error de LosCenotes:", {
code: error.code,
message: error.message,
status: error.status,
details: error.details,
});
} else {
console.error("Error inesperado:", error);
}
}

SDK de Python

from loscenotes import LosCenotesClient, LosCenotesError

try:
cenote = client.cenotes.get('invalid-id')
except LosCenotesError as e:
print(f"Error de LosCenotes: {e.code} - {e.message}")
print(f"Estado: {e.status}")
print(f"Detalles: {e.details}")
except Exception as e:
print(f"Error inesperado: {e}")

SDK de PHP

<?php
use LosCenotes\LosCenotesClient;
use LosCenotes\Exceptions\LosCenotesException;

try {
$cenote = $client->cenotes->get('invalid-id');
} catch (LosCenotesException $e) {
error_log("Error de LosCenotes: " . $e->getCode() . " - " . $e->getMessage());
error_log("Estado: " . $e->getStatus());
error_log("Detalles: " . json_encode($e->getDetails()));
} catch (Exception $e) {
error_log("Error inesperado: " . $e->getMessage());
}
?>

Probando Escenarios de Error

En Modo Sandbox

El entorno sandbox proporciona endpoints especiales para probar escenarios de error:

# Prueba error 404
curl -X GET https://service-gateway.loscenotes.com/v1/cenotes/test-not-found \
-H "Authorization: Bearer sk_test_your_api_key"

# Prueba error 429 de límite de tasa
curl -X GET https://service-gateway.loscenotes.com/v1/test/rate-limit \
-H "Authorization: Bearer sk_test_your_api_key"

# Prueba error de validación
curl -X POST https://service-gateway.loscenotes.com/v1/test/validation-error \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{"invalid": "data"}'

Monitoreo de Errores

Configura Seguimiento de Errores

// Configura seguimiento de errores
function setupErrorTracking() {
// Ejemplo de Sentry
Sentry.init({
dsn: process.env.SENTRY_DSN,
beforeSend(event) {
// Filtra errores de API de LosCenotes que son esperados
if (event.tags?.errorCode === "RESOURCE_NOT_FOUND") {
return null;
}
return event;
},
});
}

// Rastrea errores de API
function trackApiError(error: LosCenotesError, context: any) {
Sentry.captureException(error, {
tags: {
errorCode: error.code,
apiEndpoint: context.endpoint,
apiMethod: context.method,
},
extra: {
requestId: error.requestId,
apiResponse: error.details,
},
});
}

Problemas Comunes y Soluciones

Problema: Errores 500 Intermitentes

Solución: Implementa lógica de reintento con backoff exponencial

const retryableStatusCodes = [500, 502, 503, 504];

if (retryableStatusCodes.includes(error.status)) {
// Reintenta con backoff exponencial
await retryWithBackoff(apiCall);
}

Problema: Autenticación Falla Repentinamente

Posibles Causas:

  • Clave API expirada
  • Permisos de clave API cambiados
  • Desajuste de reloj (para firmas basadas en tiempo)

Solución:

if (error.code === "INVALID_API_KEY") {
// Verifica si la clave API necesita renovación
await renewApiKey();

// Reintenta la petición
return await retryRequest(originalRequest);
}

Problema: Fallas de Validación de Webhook

Solución:

// Asegura verificación adecuada de firma
function verifyWebhookSignature(payload: string, signature: string): boolean {
const secret = process.env.LOSCENOTES_WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");

return crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expectedSignature, "hex")
);
}

Próximos Pasos