Cadena de Frío
Monitoreo de temperatura en equipos de refrigeración y transporte con excursiones trazables, grace configurable y journey/leg para shipments.
Cadena de Frío
La cadena de frío es el control ininterrumpido de temperatura desde la producción hasta el consumo. Si un equipo o un shipment refrigerado se sale del rango seguro, el producto puede estar comprometido. Rela-ai monitoriza cada activo, cada lote y cada segmento de transporte, y deja un audit trail verificable para reguladores.
Resumen ejecutivo
Cadena de frío sin cry-wolf: cada desviación pasa por filtros de staleness, flapping, y grace configurable antes de tocar el inbox — y termina adjuntada al lote y al tramo de transporte que la provocó.
Antes de esta iteración, una excursión de 40 segundos por abrir una puerta podía generar tres alertas separadas y cero información del batch afectado. Ahora:
| Antes | Ahora |
|---|---|
| Sensor muerto enviando 0 °C fabricaba excursiones falsas | Filtro CC-H1: fuentes stale se descartan |
| Cold chain alertaba por separado del resto del sistema | CC-H2: feed al alert aggregator con source_system=cold_chain |
| Imposible saber QUÉ sensor abrió la excursión | CC-H3: source_id persistido en cada excursión |
| Door open/close/open = 2 incidentes artificiales | CC-M2: re-open dentro de 5 min flap window |
Mutaciones de temperature_max invisibles | CC-M5: audit trail fire-and-forget |
| Grace rígido "warning → critical" | CC-M1: modo escalate o suppress (HACCP-style) |
| Imposible saber QUÉ lote estaba dentro | CC-L1: batch_id y product_type en la excursión |
| Imposible saber QUÉ tramo de transporte falló | CC-M6: journey + legs con excursion_ids |
¿Para qué sirve?
- Detectar temperaturas fuera de rango en freezers, cámaras y vehículos.
- Diferenciar una puerta abierta 30 segundos de un fallo de compresor de 2 horas.
- Atribuir cada breach al lote y al tramo de transporte responsable.
- Generar audit trail inmutable para HACCP, GDP, WHO TRS 961, FSMA.
- Unificar alertas con el resto del pipeline (mantenimiento, calidad, SPC).
¿Cómo funciona?
flowchart LR
SENSOR[Sensor] --> READING[Lectura]
READING --> STALE{Source stale?}
STALE -- sí --> DROP[Descarte silencioso]
STALE -- no --> BAND{Fuera de rango?}
BAND -- no --> CLEAR[Cierra buffers + resuelve activa]
BAND -- sí --> ACTIVE{Hay excursión activa?}
ACTIVE -- sí --> UPDATE[Update peak, duration, sample_count]
ACTIVE -- no --> FLAP{Flap window 5 min?}
FLAP -- sí --> REOPEN[Re-abre excursión previa]
FLAP -- no --> MODE{Grace mode?}
MODE -- escalate --> OPEN[Abre en warning, escala a critical si dura]
MODE -- suppress --> BUFFER[Buffer volátil: promueve solo si dura]
OPEN --> AGG[Alert Aggregator]
REOPEN --> AGG
BUFFER --> AGG
OPEN --> JOURNEY[Attach a leg activa si aplica]
BUFFER --> JOURNEYFiltros y protecciones
- CC-H1 — staleness: una fuente marcada
stalepor el sensor watchdog se ignora antes de evaluar el rango. Un sensor muerto enviando 0 °C no puede fabricar excursiones falsas. - CC-M2 — flap window: si una excursión resuelta de la misma dirección aparece dentro de 5 minutos, se re-abre en lugar de crear una nueva. Evita fragmentar un incidente real.
- CC-H2 — aggregator feed: toda excursión nueva o escalada publica al alert aggregator con
source_system=cold_chain, severidad canónica yexcursion_idde referencia. - CC-M1 — grace configurable:
escalate(default): cada excursión abre enwarning, promueve acriticalsi supera el grace.suppress(HACCP-style): transientes dentro del grace no persisten; solo se abre excursión si dura más que el grace, y abre directamente encritical.
Configuración del activo
Dashboard
- Activos → tu equipo → editar.
- Activa Cadena de Frío.
- Rellena:
- Temperatura mínima / máxima: límites seguros.
- Unidad: °C o °F.
- Tiempo de gracia (minutos): 1–1440. Hasta 24 horas para shipments farmacéuticos (WHO TRS 961 Annex 9, EU GDP).
- Modo de gracia:
escalateosuppress. - Fuente de eventos: el
source_idque provee las lecturas. Se valida contra_machine_event_sources(CC-M4).
API
PATCH /api/v1/assets/{asset_id}
{
"cold_chain_enabled": true,
"cold_chain_source_id": "src_fridge_01",
"cold_chain_metric": "temperature",
"temperature_min": -22,
"temperature_max": -16,
"temperature_unit": "C",
"excursion_grace_minutes": 15,
"cold_chain_grace_mode": "escalate"
}CC-M5: cualquier cambio en los 8 campos reguladores (cold_chain_enabled, cold_chain_source_id, cold_chain_metric, temperature_min, temperature_max, temperature_unit, excursion_grace_minutes, cold_chain_grace_mode) genera una entrada en _audit_trail con actor, timestamp, snapshot previo y nuevo. Un auditor puede responder "quién movió temperature_max de −18 a −12 el 3 de marzo".
Ingesta de lecturas
Dos caminos, mismo pipeline (mismos filtros, misma dedup, mismo aggregator).
Camino 1 — vía eventos de máquinas
Configura la event_source tipo http / mqtt / opcua. Cada evento con el campo temperature (o el configurado en cold_chain_metric) activa check_cold_chain_from_event.
Camino 2 — ingesta directa POST /api/v1/cold-chain/readings
Para sensores fuera del pipeline de machine_events: loggers Bluetooth, probes de mano al recibir mercancía, recorders de transporte que cargan en batch al llegar al muelle.
POST /api/v1/cold-chain/readings
{
"source_id": "logger-bt-042",
"asset_id": "65a...",
"temperature": -12.4,
"batch_id": "LOT-2026-03-A",
"product_type": "mRNA vaccine"
}Si omites asset_id, el servicio resuelve todos los activos con ese cold_chain_source_id y evalúa cada uno.
CC-L1: batch_id y product_type se propagan a la excursión generada. Un breach en un fridge que tuvo insulina a las 14:00 y yogurt a las 17:00 son dos historias de compliance distintas — el activo es el mismo, el lote dentro rota.
Modos de gracia — escalate vs suppress
Modo escalate (default)
Cada excursión se registra desde la primera lectura. Útil para food-service donde cada apertura de puerta importa.
t=0s Lectura fuera de rango → Excursión abierta (severity=warning)
t=5s Lectura fuera de rango → Update: sample_count=2, peak actualizado
t=30s Lectura dentro de rango → Excursión resueltaResultado: 1 fila en _cold_chain_excursions con duration=0.5min.
Modo suppress (HACCP-style)
Las excursiones transientes se olvidan. Solo se persisten las que duran más que el grace, y abren directamente en critical. Útil para farmacéutica y GDP donde el cry-wolf alerting destruye la señal.
t=0s Lectura fuera de rango → Buffer abierto (NO persiste excursión)
t=30s Lectura fuera de rango → Buffer actualiza sample_count + peak
t=2min Lectura dentro de rango → Buffer borrado silenciosamente (0 excursiones)Si la excursión dura lo suficiente:
t=0s Lectura fuera → Buffer abierto
t=15min Lectura fuera → elapsed >= grace: promueve
→ excursión persistida con severity=critical,
started_at=t=0s, duration=15minCambia el modo por activo via cold_chain_grace_mode. Default escalate preserva el comportamiento histórico para activos que existían antes del flag.
Journey / Leg — shipments refrigerados
CC-M6. Un journey es un shipment de punta a punta. Un leg es un tramo de custodia (warehouse → camión → centro de distribución → farmacia). Los hand-offs entre legs son el momento de mayor riesgo en cold chain, y es donde surgen las preguntas de atribución ("¿qué camión se calentó?").
Modelar un shipment
POST /api/v1/cold-chain/journeys
{
"journey_code": "JRN-2026-03-001",
"origin": "DC Madrid",
"destination": "Farmacia Valencia",
"batch_id": "LOT-42",
"product_type": "insulina",
"legs": [
{"sequence": 0, "from_location": "DC Madrid", "to_location": "Cross-dock"},
{"sequence": 1, "from_location": "Cross-dock", "to_location": "Camión 7", "asset_id": "65a..."},
{"sequence": 2, "from_location": "Camión 7", "to_location": "Farmacia"}
]
}Ciclo de vida
stateDiagram-v2
[*] --> planned
planned --> in_transit: start_leg (primera vez)
in_transit --> in_transit: start_leg / complete_leg
in_transit --> delivered: último leg completed
planned --> cancelled
in_transit --> cancelledPOST /api/v1/cold-chain/journeys/{id}/legs/{leg_id}/start— marca el leg comoactivey, en la primera activación, estampaactual_start_aten el journey. Activaciones posteriores NO sobreescriben el dispatch original.POST /api/v1/cold-chain/journeys/{id}/legs/{leg_id}/complete— marca el leg comocompletedconended_at.
Atribución automática de excursiones
Cuándo check_excursion abre una nueva excursión para un asset_id, el servicio busca un journey in_transit con un leg active que referencie ese asset. Si lo encuentra, añade el excursion_id al array legs.$.excursion_ids del leg. Fire-and-forget: un fallo en la capa de journeys nunca interrumpe el pipeline de cold chain.
Así el audit responde "¿qué tramo de transporte falló?" sin joins manuales entre colecciones.
Dashboard de estado
GET /api/v1/cold-chain/status devuelve, por activo, los tres indicadores que un operador mira de un vistazo:
| Campo | Significado |
|---|---|
peak_deviation | Cuánto se desvió del rango (unidad del activo). |
duration_minutes | Cuánto tiempo lleva fuera. |
sample_count | Cuántas lecturas respaldan la excursión (CC-L3). |
Un critical de 1 muestra es un glitch; uno de 20 muestras durante 20 minutos es un evento real. El tercer número es el que diferencia triage real de ruido.
[
{
"id": "65a...",
"name": "Freezer Unit A",
"temperature_min": -22,
"temperature_max": -16,
"has_excursion": true,
"peak_deviation": 3.2,
"duration_minutes": 18.5,
"sample_count": 37,
"excursion": { "id": "...", "severity": "critical", "source_id": "src_fridge_01", "batch_id": "LOT-42" }
}
]Endpoints
| Método | Path | Qué hace |
|---|---|---|
POST | /api/v1/cold-chain/readings | Ingesta directa con batch_id / product_type. |
GET | /api/v1/cold-chain/status | Estado por activo + peak/duration/sample. |
GET | /api/v1/cold-chain/excursions | Historial con filtros asset_id, resolved. |
POST | /api/v1/cold-chain/journeys | Crea journey + legs. |
GET | /api/v1/cold-chain/journeys | Lista, filtra por status y batch_id. |
POST | /api/v1/cold-chain/journeys/{id}/legs | Añade un leg. |
POST | /api/v1/cold-chain/journeys/{id}/legs/{leg_id}/start | Marca leg active. |
POST | /api/v1/cold-chain/journeys/{id}/legs/{leg_id}/complete | Marca leg completed. |
Casos de uso reales
1. Apertura de puerta breve en freezer de carnes
Grace mode escalate, grace 15 min. Un operario abre la puerta 40 segundos. Temperatura sube de −20 a −16.5 °C.
- t=0s: excursión abierta (warning, duration 0).
- t=20s: update (sample_count=2, peak 3.5 °C).
- t=40s: in-band → resolve. Duration 0.7 min.
- Dashboard muestra
peak=3.5,duration=0.7,sample_count=2. El operador descarta.
2. Shipment de vacunas con breach en tramo 2
Grace mode suppress, grace 60 min. Journey con 3 legs. El camión entra en atasco y la temperatura supera 8 °C durante 75 minutos.
- Leg 2 activa, asset = "Camión 7".
- t=0: buffer abierto (no persiste excursión).
- t=60min: buffer excede grace → promueve. Excursión
criticalconstarted_at= t=0,duration=60min,severity=critical,batch_id=LOT-42,product_type=insulina. - El hook de CC-M6 añade
excursion_idal leg 2. Auditoría responde: "el breach ocurrió en el tramo Cross-dock → Camión 7".
3. Sensor muerto enviando 0 °C
Sensor watchdog marca src_fridge_05 como stale. El reading pipeline recibe 0 °C (que sería fuera de rango contra un límite de −18). CC-H1 descarta la lectura en la primera línea de check_excursion. No hay excursión fantasma.
Limitaciones y supuestos
cold_chain_enabled=Truerequierecold_chain_source_idválido en_machine_event_sources(CC-M4). Si se borra la fuente tras la configuración, el servicio loguea warning pero no bloquea; la validación es early-warning.- El flap window es fijo en 5 minutos. Ajustable solo en código (
_COLD_CHAIN_FLAP_WINDOW_MINUTES). suppressno aplica al flap-reopen: si una excursión PERSISTIDA resuelve y aparece una nueva en la flap window, re-abre independientemente del modo (el ruido ya fue decisión de persistencia previa).journey.statusno se cierra automáticamente al completar el último leg; requiere unPATCHexplícito adelivered.
Colecciones MongoDB per-tenant
| Colección | Contenido |
|---|---|
_cold_chain_excursions | Excursiones persistidas (esquema completo con batch_id, product_type, source_id, sample_count, peak_deviation, duration_minutes). |
_cold_chain_excursion_buffers | Buffers volátiles de modo suppress. Se dropean al resolver in-band o al promover. |
_cold_chain_journeys | Shipments con legs embebidos. |
_audit_trail | Entradas fire-and-forget con action=asset_cold_chain_updated. |
Hallazgos cerrados en esta iteración
| ID | Qué se cerró |
|---|---|
| CC-H1 | Filtro de staleness antes de evaluar rango. |
| CC-H2 | Feed al alert aggregator con source_system dedicado. |
| CC-H3 | source_id persistido en excursión (propagado desde webhook). |
| CC-M1 | Flag cold_chain_grace_mode con modo HACCP-style. |
| CC-M2 | Flap-window de 5 min para re-open de excursiones consecutivas. |
| CC-M3 | Endpoint POST /readings para ingesta sin machine_events. |
| CC-M4 | Validación de cold_chain_source_id en create/update asset. |
| CC-M5 | Audit trail en mutaciones de los 8 campos regulados. |
| CC-M6 | Journey + Leg con atribución automática. |
| CC-L1 | batch_id + product_type persistidos en excursión. |
| CC-L2 | Rango de excursion_grace_minutes ampliado a 1–1440. |
| CC-L3 | peak_deviation, duration_minutes, sample_count en status. |
Beneficios clave
- Audit trail verificable para HACCP, GDP, WHO TRS 961, FSMA.
- Zero cry-wolf: staleness + flapping + grace mode eliminan ruido estructural.
- Atribución real: cada breach sabe su lote, producto y tramo.
- Inbox unificado: misma inbox que mantenimiento y calidad; el operador no cambia de pantalla.
- Farma-ready: grace hasta 24 h, modo suppress, tracking de batch, journey model.
HACCP — Control Sanitario Verificable
Planes HACCP, CCPs con grace period real, acciones correctivas que generan work orders y cadena de custodia por lote. Certificable ante SQF / BRC / FSSC 22000.
Acciones Correctivas
Documenta acciones correctivas para desviaciones HACCP y excursiones de cadena de frio