Best Practices / Mejores Prácticas
Production-ready guidelines for integrating with the LosCenotes Partner API.
Directrices preparadas para producción para integrar con la API de Partners de LosCenotes.
Security Best Practices / Mejores Prácticas de Seguridad
1. API Key Management / Gestión de Llaves API
Store Keys Securely / Almacena Llaves de Forma Segura
// ✅ Good - Environment variables / Correcto - Variables de entorno
const apiKey = process.env.LOSCENOTES_API_KEY;
// ✅ Good - Secure key management service / Correcto - Servicio de gestión de llaves
const apiKey = await keyVault.getSecret('loscenotes-api-key');
// ❌ Bad - Hard-coded keys / Incorrecto - Llaves escritas en código
const apiKey = 'pk_live_1234567890abcdef';
// ❌ Bad - Client-side exposure / Incorrecto - Exposición en frontend
// Never put API keys in frontend code / Nunca pongas llaves API en código frontend
Rotate Keys Regularly / Rota Llaves Regularmente
- Generate a new API key / Genera una nueva llave
- Update your application / Actualiza tu aplicación
- Verify the new key works / Verifica que la nueva llave funcione
- Revoke the old key / Revoca la llave anterior
2. Request Security / Seguridad de Solicitudes
Always Use HTTPS / Siempre Usa HTTPS
// ✅ Always HTTPS / Siempre HTTPS
const baseUrl = 'https://service-gateway.loscenotes.com';
// ❌ Never HTTP / Nunca HTTP
const baseUrl = 'http://service-gateway.loscenotes.com';
Validate Input Data / Valida Datos de Entrada
function validateReservationData(data) {
const errors = [];
// UUID validation
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!data.cenoteId || !uuidRegex.test(data.cenoteId)) {
errors.push('Valid cenoteId is required');
}
// Date validation
if (!data.date || new Date(data.date) < new Date()) {
errors.push('Valid future date is required');
}
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!data.guestEmail || !emailRegex.test(data.guestEmail)) {
errors.push('Valid email is required');
}
// Age breakdown validation
if (!data.ageBreakdown || !data.ageBreakdown.adult) {
errors.push('At least one adult is required');
}
return errors;
}
// Usage / Uso
const errors = validateReservationData(reservationData);
if (errors.length > 0) {
console.error('Validation errors:', errors);
return { success: false, errors };
}
Performance Best Practices / Mejores Prácticas de Rendimiento
1. Caching Strategies / Estrategias de Caché
Cache Cenote Data / Cachea Datos de Cenotes
class CenoteCache {
constructor(ttlMs = 5 * 60 * 1000) { // 5 minutes default
this.cache = new Map();
this.ttl = ttlMs;
}
async getCenotes(apiKey) {
const cacheKey = 'cenotes_list';
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
// Fetch fresh data
const response = await fetch(
'https://service-gateway.loscenotes.com/partner/cenotes',
{
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
}
}
);
const data = await response.json();
// Cache the result
this.cache.set(cacheKey, {
data: data.data,
timestamp: Date.now()
});
return data.data;
}
}
// Usage / Uso
const cache = new CenoteCache();
const cenotes = await cache.getCenotes(apiKey);
2. Request Batching / Agrupación de Solicitudes
// ❌ Bad - Many individual requests / Incorrecto - Muchas solicitudes individuales
for (const cenoteId of cenoteIds) {
const cenote = await fetchCenote(cenoteId);
results.push(cenote);
}
// ✅ Good - Use list endpoint with filters / Correcto - Usa endpoint de lista con filtros
const cenotes = await fetch(
`https://service-gateway.loscenotes.com/partner/cenotes?ids=${cenoteIds.join(',')}`,
{ headers: { 'X-API-Key': apiKey } }
);
3. Pagination / Paginación
async function getAllCenotes(apiKey) {
const allCenotes = [];
let currentPage = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(
`https://service-gateway.loscenotes.com/partner/cenotes?page=${currentPage}&perPage=50`,
{
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
}
}
);
const data = await response.json();
allCenotes.push(...data.data);
hasMore = data.pagination.currentPage < data.pagination.lastPage;
currentPage++;
}
return allCenotes;
}
Error Handling / Manejo de Errores
Comprehensive Error Handler / Manejador de Errores Completo
async function apiRequest(endpoint, options = {}) {
const baseUrl = 'https://service-gateway.loscenotes.com';
const apiKey = process.env.LOSCENOTES_API_KEY;
try {
const response = await fetch(`${baseUrl}${endpoint}`, {
...options,
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json',
...options.headers
}
});
const data = await response.json();
if (!data.success) {
return handleApiError(data.error);
}
return { success: true, data: data.data };
} catch (error) {
return handleNetworkError(error);
}
}
function handleApiError(error) {
const errorMessages = {
'INVALID_API_KEY': 'API key is invalid. Check your credentials.',
'RATE_LIMIT_EXCEEDED': 'Rate limit exceeded. Try again later.',
'RESOURCE_NOT_FOUND': 'Requested resource not found.',
'VALIDATION_ERROR': 'Invalid request data.',
'INSUFFICIENT_CAPACITY': 'Not enough capacity available.'
};
return {
success: false,
error: {
code: error.code,
message: errorMessages[error.code] || error.message,
details: error.details
}
};
}
function handleNetworkError(error) {
if (error.name === 'TypeError') {
return {
success: false,
error: {
code: 'NETWORK_ERROR',
message: 'Network error. Check your internet connection.'
}
};
}
return {
success: false,
error: {
code: 'UNKNOWN_ERROR',
message: error.message
}
};
}
Monitoring / Monitoreo
1. Logging / Registro
function logApiCall(endpoint, method, startTime, response) {
const duration = Date.now() - startTime;
const logEntry = {
timestamp: new Date().toISOString(),
endpoint,
method,
duration: `${duration}ms`,
success: response.success,
statusCode: response.statusCode,
rateLimit: {
remaining: response.headers?.['x-ratelimit-remaining'],
limit: response.headers?.['x-ratelimit-limit']
}
};
if (!response.success) {
logEntry.error = response.error;
console.error('API Error:', JSON.stringify(logEntry, null, 2));
} else {
console.log('API Call:', JSON.stringify(logEntry));
}
}
2. Health Checks / Verificaciones de Salud
async function checkApiHealth(apiKey) {
const startTime = Date.now();
try {
const response = await fetch(
'https://service-gateway.loscenotes.com/partner/cenotes?perPage=1',
{
headers: { 'X-API-Key': apiKey }
}
);
const duration = Date.now() - startTime;
return {
healthy: response.ok,
latency: duration,
timestamp: new Date().toISOString()
};
} catch (error) {
return {
healthy: false,
error: error.message,
timestamp: new Date().toISOString()
};
}
}
// Run health check periodically / Ejecuta verificación periódicamente
setInterval(async () => {
const health = await checkApiHealth(process.env.LOSCENOTES_API_KEY);
console.log('API Health:', health);
}, 60000); // Every minute / Cada minuto
Production Checklist / Lista de Verificación de Producción
Before Going Live / Antes de Ir a Producción
- API Key: Using production key (
pk_live_*) / Usando llave de producción - Environment Variables: API key stored in environment variables / Llave almacenada en variables de entorno
- Error Handling: Comprehensive error handling implemented / Manejo de errores implementado
- Rate Limiting: Exponential backoff implemented / Backoff exponencial implementado
- Logging: API calls are logged / Llamadas API registradas
- Monitoring: Health checks configured / Verificaciones de salud configuradas
- Caching: Appropriate caching for static data / Caché apropiado para datos estáticos
- Validation: Input validation before API calls / Validación antes de llamadas API
Security Checklist / Lista de Seguridad
- API keys never exposed in frontend / Llaves nunca expuestas en frontend
- HTTPS only / Solo HTTPS
- Input sanitization / Sanitización de entrada
- Rate limit handling / Manejo de límites de tasa
- Error messages don't leak sensitive info / Mensajes de error no filtran info sensible
Next Steps / Siguientes Pasos
- API Reference Overview - Complete API documentation
- Pricing & Age Groups / Precios y Edades - Price calculations
- Checkout & Payments / Checkout y Pagos - Payment processing