Depuración de incidentes en producción: un enfoque sistemático
Un marco probado para diagnosticar y resolver incidentes en producción rápido, desde la primera alerta hasta la causa raíz y la prevención.

Cuando se rompe la producción
Los incidentes en producción son situaciones de alta presión y poco tiempo que ponen a prueba cuán preparados están tu equipo y tus sistemas. La diferencia entre una recuperación de 5 minutos y una caída de 3 horas suele ser la preparación y el proceso, no el talento individual.
Este es el marco que uso.
El ciclo OODA para incidentes
El ciclo OODA del estratega militar John Boyd se adapta bien a la respuesta de incidentes:
Observar → Orientarse → Decidir → Actuar → repetir
Nunca te saltes pasos. El error más común en los incidentes es saltar a Actuar sin Observar: hacer cambios basados en suposiciones en lugar de evidencia, lo que puede empeorar las cosas.
Fase 1: Observar — Recopilar señal
Primeros 5 minutos: entender el alcance y el impacto.
# What is actually broken?
# Check error rates across services
curl "https://metrics.internal/api/query?q=rate(http_errors_total[5m])"
# Which users are affected?
# Check if it's a subset (region, plan, feature flag cohort)
SELECT
COUNT(*) as affected_users,
COUNT(CASE WHEN region = 'us-east' THEN 1 END) as us_east,
COUNT(CASE WHEN plan = 'free' THEN 1 END) as free_tier
FROM error_events
WHERE created_at > NOW() - INTERVAL '10 minutes';
# When did it start?
# Compare current error rate to baseline
SELECT
date_trunc('minute', created_at) as minute,
COUNT(*) as errors
FROM error_events
WHERE created_at > NOW() - INTERVAL '30 minutes'
GROUP BY 1
ORDER BY 1;Preguntas clave:
- ¿Cuál es la tasa de error? (3% vs 100% cambia la respuesta)
- ¿Qué servicio/endpoint se ve afectado?
- ¿Cuándo empezó?
- ¿Se desplegó algo alrededor de esa hora?
- ¿Está mejorando o empeorando?
Fase 2: Orientarse — Formar hipótesis
Correlaciona tus observaciones con cambios recientes. La causa más probable de un incidente en producción es un cambio reciente.
# Check recent deployments
git log --oneline --since="2 hours ago" --all
# → b43224f feat: add fetchDataVersion function [30 min ago]
# Check infrastructure changes
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=UpdateFunctionCode \
--start-time "2 hours ago"
# Check database query performance
SELECT
query,
mean_exec_time,
calls,
total_exec_time
FROM pg_stat_statements
WHERE mean_exec_time > 1000 -- Queries slower than 1 second
ORDER BY mean_exec_time DESC
LIMIT 10;Forma una lista ordenada de hipótesis:
- Lo más probable: el despliegue de hace 30 minutos
- Posible: consulta a la base de datos repentinamente lenta
- Menos probable: degradación de API de terceros
Fase 3: Decidir — Mitigar vs corregir
Elige la acción correcta según la situación:
Rollback — el más rápido cuando la causa es un despliegue reciente y el rollback es seguro. Preferir esto cuando los usuarios están impactados activamente.
Feature flag off — para incidentes causados por una funcionalidad específica. Instantáneo y sin necesidad de despliegue.
Scale up — para incidentes relacionados con capacidad (alta carga, presión de memoria).
Fix forward — solo cuando el rollback no es posible y entiendes la causa raíz.
# Rollback a Kubernetes deployment
kubectl rollout undo deployment/api-server
# Disable a feature flag (example with Vercel Edge Config)
curl -X PATCH https://api.vercel.com/v1/edge-config/{id}/items \
-H "Authorization: Bearer $TOKEN" \
-d '{"items": [{"operation": "update", "key": "features.new-checkout", "value": {"enabled": false}}]}'
# Scale up if the issue is capacity
kubectl scale deployment/api-server --replicas=10El sesgo hacia la acción: durante un incidente activo, una mitigación imperfecta ahora supera una corrección perfecta en 30 minutos. Primero detén la hemorragia.
Fase 4: Verificar y monitorear
Después de aplicar la mitigación, confirma la recuperación antes de dar por terminado.
// Monitor key metrics for 10-15 minutes after mitigation
const metricsToWatch = [
"http.error_rate", // Should return to baseline
"db.query_duration_p95", // Should drop if DB was the cause
"api.response_time_p99", // Should recover
"active_user_sessions", // Should stop declining
];
// Set up a dashboard view filtered to last 30 minutes
// Watch for the inflection point where the incident started to resolveNo declares la victoria demasiado pronto. Algunos problemas tardan 5-10 minutos en propagarse por el sistema después de una corrección.
Post-incidente: La post-mortem sin culpas
Dentro de las 48 horas, escribe una post-mortem. El objetivo no es asignar culpas, sino prevenir que se repita.
## Incident: API 500 Errors — 2026-03-15 14:30 UTC
### Summary
For 23 minutes, 8% of API requests returned 500 errors, affecting ~1,200 users.
The cause was a missing database index added in deployment v2.4.1, causing
query timeouts under normal load.
### Timeline
- 14:30 — Deployment v2.4.1 rolled out to production
- 14:38 — Alert: error rate exceeded 5% threshold
- 14:41 — On-call acknowledged, began investigation
- 14:47 — Identified slow query in pg_stat_statements
- 14:52 — Added missing index with CREATE INDEX CONCURRENTLY
- 14:53 — Error rate returned to baseline
### Root Cause
The migration added a `user_id` filter to a query that previously had no
user scope. Without an index on `user_id`, this query did a full table scan
on 8M rows under load.
### Why It Reached Production
- The migration was tested on a dev database with ~500 rows
- Query performance wasn't tested under load
- No slow query monitoring was in place for new migrations
### Action Items
- [ ] Add EXPLAIN ANALYZE to migration PR checklist
- [ ] Set up pg_stat_statements alerts for new slow queries
- [ ] Create load test suite that runs against staging with production-scale dataLos mejores equipos tratan las post-mortems como herramientas de aprendizaje, no como castigo. "¿Cómo permitió nuestro sistema esto?" en lugar de "¿quién causó esto?"
Observabilidad: Investiga antes de necesitarla
El peor momento para configurar logging y monitoreo es durante un incidente.
// Structured logging — make logs queryable
import pino from "pino";
const logger = pino({
level: process.env.LOG_LEVEL ?? "info",
formatters: {
level: (label) => ({ level: label }),
},
});
// Every important operation logs with context
async function processOrder(orderId: string, userId: string) {
const start = Date.now();
logger.info({ orderId, userId, event: "order.process.start" });
try {
const result = await fulfillOrder(orderId);
logger.info({
orderId,
userId,
event: "order.process.success",
durationMs: Date.now() - start,
});
return result;
} catch (error) {
logger.error({
orderId,
userId,
event: "order.process.error",
error: error.message,
durationMs: Date.now() - start,
});
throw error;
}
}Si no puedes responder "¿qué estaba haciendo el sistema 30 segundos antes del incidente?" a partir de tus logs, necesitas mejor observabilidad.
La producción siempre te sorprenderá. La pregunta es si serás capaz de entender qué pasó.


