Rela AIRela AI Docs
Seguridad Alimentaria

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:

AntesAhora
Sensor muerto enviando 0 °C fabricaba excursiones falsasFiltro CC-H1: fuentes stale se descartan
Cold chain alertaba por separado del resto del sistemaCC-H2: feed al alert aggregator con source_system=cold_chain
Imposible saber QUÉ sensor abrió la excursiónCC-H3: source_id persistido en cada excursión
Door open/close/open = 2 incidentes artificialesCC-M2: re-open dentro de 5 min flap window
Mutaciones de temperature_max invisiblesCC-M5: audit trail fire-and-forget
Grace rígido "warning → critical"CC-M1: modo escalate o suppress (HACCP-style)
Imposible saber QUÉ lote estaba dentroCC-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 --> JOURNEY

Filtros y protecciones

  1. CC-H1 — staleness: una fuente marcada stale por el sensor watchdog se ignora antes de evaluar el rango. Un sensor muerto enviando 0 °C no puede fabricar excursiones falsas.
  2. 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.
  3. CC-H2 — aggregator feed: toda excursión nueva o escalada publica al alert aggregator con source_system=cold_chain, severidad canónica y excursion_id de referencia.
  4. CC-M1 — grace configurable:
    • escalate (default): cada excursión abre en warning, promueve a critical si 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 en critical.

Configuración del activo

Dashboard

  1. Activos → tu equipo → editar.
  2. Activa Cadena de Frío.
  3. 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: escalate o suppress.
    • Fuente de eventos: el source_id que 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 resuelta

Resultado: 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=15min

Cambia 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 --> cancelled
  • POST /api/v1/cold-chain/journeys/{id}/legs/{leg_id}/start — marca el leg como active y, en la primera activación, estampa actual_start_at en el journey. Activaciones posteriores NO sobreescriben el dispatch original.
  • POST /api/v1/cold-chain/journeys/{id}/legs/{leg_id}/complete — marca el leg como completed con ended_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:

CampoSignificado
peak_deviationCuánto se desvió del rango (unidad del activo).
duration_minutesCuánto tiempo lleva fuera.
sample_countCuá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étodoPathQué hace
POST/api/v1/cold-chain/readingsIngesta directa con batch_id / product_type.
GET/api/v1/cold-chain/statusEstado por activo + peak/duration/sample.
GET/api/v1/cold-chain/excursionsHistorial con filtros asset_id, resolved.
POST/api/v1/cold-chain/journeysCrea journey + legs.
GET/api/v1/cold-chain/journeysLista, filtra por status y batch_id.
POST/api/v1/cold-chain/journeys/{id}/legsAñade un leg.
POST/api/v1/cold-chain/journeys/{id}/legs/{leg_id}/startMarca leg active.
POST/api/v1/cold-chain/journeys/{id}/legs/{leg_id}/completeMarca 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 critical con started_at = t=0, duration=60min, severity=critical, batch_id=LOT-42, product_type=insulina.
  • El hook de CC-M6 añade excursion_id al 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=True requiere cold_chain_source_id vá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).
  • suppress no 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.status no se cierra automáticamente al completar el último leg; requiere un PATCH explícito a delivered.

Colecciones MongoDB per-tenant

ColecciónContenido
_cold_chain_excursionsExcursiones persistidas (esquema completo con batch_id, product_type, source_id, sample_count, peak_deviation, duration_minutes).
_cold_chain_excursion_buffersBuffers volátiles de modo suppress. Se dropean al resolver in-band o al promover.
_cold_chain_journeysShipments con legs embebidos.
_audit_trailEntradas fire-and-forget con action=asset_cold_chain_updated.

Hallazgos cerrados en esta iteración

IDQué se cerró
CC-H1Filtro de staleness antes de evaluar rango.
CC-H2Feed al alert aggregator con source_system dedicado.
CC-H3source_id persistido en excursión (propagado desde webhook).
CC-M1Flag cold_chain_grace_mode con modo HACCP-style.
CC-M2Flap-window de 5 min para re-open de excursiones consecutivas.
CC-M3Endpoint POST /readings para ingesta sin machine_events.
CC-M4Validación de cold_chain_source_id en create/update asset.
CC-M5Audit trail en mutaciones de los 8 campos regulados.
CC-M6Journey + Leg con atribución automática.
CC-L1batch_id + product_type persistidos en excursión.
CC-L2Rango de excursion_grace_minutes ampliado a 1–1440.
CC-L3peak_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.

En esta página