Instalaciones y personas
El local, su personal, sus zonas y sus máquinas: el mapa fijo del que cuelga toda lectura de temperatura.
Estas son las cosas que no se mueven: el restaurante en sí, las personas que trabajan en él, las zonas en que se divide y las máquinas que mantienen los alimentos a una temperatura. Nada de esto es un control.
Aun así decide qué significan los controles. Una temperatura es un número sin ningún veredicto adjunto hasta que lee los umbrales del equipo en que se tomó, y una lectura pertenece a una parte del edificio solo porque el equipo dice en qué zona está. Equivóquese en esta página y todas las cifras que vengan después estarán mal en silencio.
| Colección | Permiso | Qué es un registro |
|---|---|---|
restaurants | restaurants:read | El local en sí: nombre, dirección, días de cierre, ajustes. |
users | users:read | Una cuenta de personal que registra controles. |
areas | areas:read | Una zona en que se divide el local: cocina, cámara frigorífica, barra. |
equipment | equipment:read | Una nevera, un congelador, un equipo de cadena de frío, con sus umbrales. |
sensors | sensors:read | Una sonda inalámbrica, por dirección MAC. |
cooking-equipment | cooking-equipment:read | Un horno u otro equipo de cocción, con sus umbrales. |
Las seis comparten el sobre de instantánea: id,
deleted, capturedAt, receivedAt, sequence y los demás campos acompañan
al data que se describe más abajo.
restaurants
El local al que está acotada su clave, como registro. Uno por restaurante, así que esta colección suele ser una sola página.
| Campo | Tipo | Significado |
|---|---|---|
name | string, obligatorio | El nombre del local, tal como se muestra en la aplicación. |
address | string, obligatorio | La dirección postal, como una única cadena. |
closingDays | string[], obligatorio | Los días que el local está cerrado. Cadenas a secas, sin restricción del esquema. |
exceptionalClosures | object[] | Cierres puntuales, cada uno un objeto con from y to. |
detailedAddress | object | null | La dirección desglosada en partes, cuando la aplicación la guarda así. |
deliveryAddress | string | null | Dónde se entregan las mercancías, cuando difiere de address. |
deliveryDetailedAddress | object | null | Lo mismo, por partes. |
preferredLanguage | string | null | El idioma en que trabaja el restaurante. |
multiArea | boolean | null | Si el local está dividido en más de una zona. |
settings | object | null | Ajustes de la aplicación. La forma es la de la aplicación y se mueve. |
{
"id": "e5a1d3c9-7b40-4a28-9df6-18c0b2e46a75",
"collection": "restaurants",
"deleted": false,
"capturedAt": "1789300481002",
"receivedAt": "1789300489517",
"sequence": "7",
"data": {
"name": "Le Comptoir de Nanterre",
"address": "12 rue des Anciennes Mairies, 92000 Nanterre",
"closingDays": ["sunday"],
"preferredLanguage": "fr",
"multiArea": true
}
}
El ejemplo muestra solo la mitad operativa. Junto a ella el registro lleva la
comercial: subscription, subscriptions, companyName, billingAddress,
billingEmail, tvaNumber, trialEndDate, customerId, discount. Existen,
están en el documento OpenAPI, y esta página no va a explicárselos.
Lea los campos operativos y deje en paz los comerciales. Describen el contrato del restaurante con nosotros, no su cocina. No son una API de facturación, cambian por motivos que nada tienen que ver con la seguridad alimentaria, y una integración que le muestre a un cliente el estado de su suscripción es un ticket de soporte esperando a ocurrir.
closingDays es una lista de cadenas a secas. El esquema no les impone
ninguna forma, así que léalas y muéstrelas; no haga un switch sobre ellas y no
dé por hecha una grafía ni unas mayúsculas concretas.
users
Las cuentas del personal que registra los controles. Esto es a quien apunta el
userId de un registro de temperatura.
| Campo | Tipo | Significado |
|---|---|---|
firstName | string, obligatorio | Nombre de pila, tal como se introdujo. |
lastName | string, obligatorio | Apellidos, tal como se introdujeron. |
email | string, obligatorio | La dirección de correo electrónico de la cuenta. |
phoneNumber | string | null | Un teléfono de contacto, cuando se dio uno. |
modules | string[] | Qué partes de la aplicación usa esta persona. Cadenas libres. |
preferredNotificationTime | string | null | Cuándo pidió esta persona que se le recuerde. |
isChildren | boolean | null | Una marca interna. El esquema no le da ningún significado; no construya sobre ella. |
isDeleted | boolean | null | La marca de retirada propia de la aplicación, y aquí admite null, a diferencia del resto. |
{
"id": "2774953d-8d9b-4a68-8ec4-1209edd90777",
"collection": "users",
"deleted": false,
"capturedAt": "1789315002664",
"receivedAt": "1789315010218",
"sequence": "31",
"data": {
"firstName": "Amina",
"lastName": "Berthier",
"email": "amina.berthier@example.test",
"phoneNumber": null,
"modules": ["temperatures", "cleaning"],
"isDeleted": false
}
}
Esto son datos personales. email, phoneNumber y los dos campos de nombre
identifican a una persona real, y están en esta API por un solo motivo: un
registro de seguridad alimentaria tiene que decir quién hizo el control. Esa es
la única función que cumplen aquí.
Así que guarde el id y resuelva un nombre para mostrar cuando lo necesite. No
copie datos de contacto a sus propias tablas, sus registros, sus eventos de
analítica o una herramienta de terceros porque resulte que estaban en la carga
útil. El restaurante es el responsable del tratamiento de los datos de su
personal, y cada copia que usted haga es una copia de la que ahora tiene que
responder.
isDeleted admite null en esta colección donde en las demás es obligatorio,
así que trate una marca ausente como «no retirado» en lugar de dejar que cuele
como algo parecido a false sin querer. La diferencia entre esa marca y el
deleted del sobre está
explicada en la página del catálogo.
areas
Una zona del restaurante. Dos campos, y mucho más importante de lo que dos campos sugieren.
| Campo | Tipo | Significado |
|---|---|---|
name | string, obligatorio | La zona tal como la nombra el restaurante: cocina, cámara frigorífica, barra. |
isDeleted | boolean, obligatorio | La marca de retirada de la aplicación. Véase la página del catálogo. |
{
"id": "9c2b17ae-4d80-4f55-b3e1-0a6c8f4d2b19",
"collection": "areas",
"deleted": false,
"capturedAt": "1789301120440",
"receivedAt": "1789301126871",
"sequence": "12",
"data": {
"name": "Chambre froide",
"isDeleted": false
}
}
areaId es la manera en que todo se agrupa por zonas. Los equipos, los
equipos de cocción, las freidoras y las tareas de limpieza llevan uno, y es el
único vínculo entre una lectura y una parte del edificio.
No hay ningún areaId en los registros en sí: se llega hasta él a través del
equipo.
areaId admite null en todos los sitios en que aparece. Una nevera sin zona
es normal: significa que nadie se la asignó, normalmente en un local de una sola
sala donde multiArea es false. Agrupe esas bajo «sin asignar» en lugar de
descartarlas.
equipment
Neveras, congeladores y otros equipos de cadena de frío. El registro que decide si una temperatura era aceptable.
| Campo | Tipo | Significado |
|---|---|---|
index | integer, obligatorio | La posición que le dio el restaurante en su propia lista. Orden de presentación, no un identificador. |
type | string, obligatorio | De qué tipo de equipo se trata. Una cadena sin restricciones: léala, no haga un switch sobre ella. |
name | string, obligatorio | El equipo tal como lo llama la cocina. |
min | number | null | La temperatura aceptable más baja. |
max | number | null | La temperatura aceptable más alta. |
areaId | string | null | La zona en que está. |
equipmentSensorId | string | null | El sensor instalado en él, cuando hay uno. |
state | string | null | El estado actual del equipo, como cadena sin restricciones. |
outOfRangeCount | number | null | Un recuento de lecturas fuera de rango que mantiene la aplicación. |
isDeleted | boolean, obligatorio | La marca de retirada de la aplicación. |
{
"id": "84ce5c13-3237-40d4-901a-cbba59a6406f",
"collection": "equipment",
"deleted": false,
"capturedAt": "1789302455901",
"receivedAt": "1789302461330",
"sequence": "58",
"data": {
"index": 2,
"type": "freezer",
"name": "Congélateur bas",
"min": -22,
"max": -18,
"areaId": "9c2b17ae-4d80-4f55-b3e1-0a6c8f4d2b19",
"equipmentSensorId": "b31f7d64-0e29-42ca-9a57-51d3c8e07f28",
"state": "ok",
"outOfRangeCount": 0,
"isDeleted": false
}
}
min y max son el único veredicto que va a obtener. Un
registro de temperatura lleva un valor, una hora y el
equipo en que se tomó; nada que diga si estaba bien. -14,4 °C es una crisis en
una nevera y un martes cualquiera en un congelador, y este registro es lo que
distingue una cosa de la otra. Toda cifra de cumplimiento que construya cruza
por aquí.
También admiten null. Cuando falta min o max no tiene umbral, y la
respuesta honesta para esa lectura es «desconocido», no «en rango». Dígalo así
en su interfaz en lugar de poner verde por defecto.
Los umbrales son los actuales, no los que se aplicaron. La instantánea le da
el min y el max de hoy; una lectura de marzo se juzgó contra lo que
estuviera fijado en marzo, y el restaurante puede cambiarlos en cualquier
momento. Si vuelve a evaluar lecturas antiguas con las cifras de hoy —que es
todo lo que le permite esta API—, etiquete el resultado como recálculo suyo, no
como lo que vio la cocina.
type y state son cadenas sin restricciones. Los dos son campos de texto
libre en el esquema, sin ninguna lista detrás. Muéstrelos, agrupe por ellos si
no le queda más remedio, y nunca deje que un valor desconocido se cuele por un
switch hasta el silencio.
outOfRangeCount es la cifra de la aplicación, no la suya. El esquema no
dice qué ventana cubre, y no puede reconstruirla a partir de las instantáneas de
aquí. Trátela como una señal de la aplicación y, si necesita un recuento que
pueda defender, calcule el suyo a partir de las lecturas y los umbrales y
etiquételo como suyo.
equipmentSensorId apunta del equipo a su sensor. El registro del sensor apunta
en sentido contrario. Los dos lados admiten null, y que uno esté relleno no
garantiza que el otro lo esté.
sensors
Las sondas inalámbricas. Dos campos, los dos opcionales.
| Campo | Tipo | Significado |
|---|---|---|
macAddress | string | null | La dirección de hardware de la sonda: cómo distinguir un dispositivo físico de otro. |
sensorEquipmentId | string | null | El equipo que vigila este sensor. |
{
"id": "b31f7d64-0e29-42ca-9a57-51d3c8e07f28",
"collection": "sensors",
"deleted": false,
"capturedAt": "1789302501764",
"receivedAt": "1789302509002",
"sequence": "61",
"data": {
"macAddress": "F4:12:9D:3A:77:0B",
"sensorEquipmentId": "84ce5c13-3237-40d4-901a-cbba59a6406f"
}
}
Fíjese en los dos nombres de campo, que son imágenes especulares y fáciles de
intercambiar: un equipo lleva equipmentSensorId, un sensor lleva
sensorEquipmentId. Lea el que no es y obtiene undefined sin ningún error.
Aquí no hay nombre, ni nivel de batería, ni hora del último contacto. Lo que
informó un sensor está en temperature-readings, y esas
lecturas se indexan por equipmentId y no por el sensor: así que una sonda que
se ha quedado muda tiene exactamente el mismo aspecto que una que no tenía nada
que informar.
cooking-equipment
Hornos, armarios de mantenimiento y el resto del lado caliente. La misma idea
que equipment, sin la maquinaria de la cadena de frío.
| Campo | Tipo | Significado |
|---|---|---|
index | integer, obligatorio | Orden de presentación en la lista del propio restaurante. |
type | string, obligatorio | De qué tipo de equipo se trata. Cadena sin restricciones. |
name | string, obligatorio | El equipo tal como lo llama la cocina. |
min | number | null | La temperatura aceptable más baja. |
max | number | null | La temperatura aceptable más alta. |
areaId | string | null | La zona en que está. |
isDeleted | boolean, obligatorio | La marca de retirada de la aplicación. |
{
"id": "3d90f4c8-62b7-4e13-8a05-ff71d6b9c204",
"collection": "cooking-equipment",
"deleted": false,
"capturedAt": "1789303880115",
"receivedAt": "1789303887640",
"sequence": "64",
"data": {
"index": 1,
"type": "oven",
"name": "Four à sole",
"min": 63,
"max": 260,
"areaId": "9c2b17ae-4d80-4f55-b3e1-0a6c8f4d2b19",
"isDeleted": false
}
}
Ni state, ni outOfRangeCount, ni sensor: los equipos de cocción los lee una
persona, así que no hay nada vigilándolos entre control y control. Sus lecturas
son cooking-temperature-records, que apuntan aquí con
cookingEquipmentId: un nombre de campo distinto del de la cadena de frío, y el
sitio habitual donde un cliente lee nada en silencio.
Leer el mapa
Todo lo de esta página es pequeño, cambia despacio y hace falta antes de que ninguna otra colección tenga sentido.
- Cárguelo primero y consérvelo. Recorra
areas,equipment,cooking-equipmentysensorsantes de recorrer ninguna colección de registros, y resuelva nombres y umbrales desde su propia copia en lugar de registro a registro. - Refrésquelo de todos modos. Los equipos se renombran, los umbrales se corrigen, una nevera se muda a otra zona. Una copia tomada una vez y nunca vuelta a recorrer se desvía sin llegar a fallar nunca.
- Guarde id, no copias, y con las personas sobre todo. Guarde el
userIdy resuelva un nombre en el momento de mostrarlo. Véase la nota sobre datos personales de más arriba. - Las dos marcas de borrado, en todas partes. El
deleteddel sobre y elisDeleteddedatason cosas distintas, y la página del catálogo explica cuál es cuál.