Colecciones y registros
Los veinticuatro tipos de registro más allá de las entregas: el sobre de instantánea que comparten, qué es una instantánea y cómo recorrer una.
Las entregas son un recurso cuidado, moldeado a mano porque el control de recepción fue lo primero que pidió alguien. Las colecciones son el resto del registro: temperaturas, ciclos de enfriamiento, limpieza, controles de freidora, etiquetas; veinticuatro, servidas a través de una única forma genérica.
Donde el endpoint de entregas le da un objeto diseñado, una colección le da el registro tal como lo guarda la aplicación, envuelto en un sobre que le dice cuándo se capturó y si sigue existiendo. Ese intercambio es deliberado: es lo que permite que un módulo nuevo llegue a esta API la semana en que sale, y no el trimestre siguiente.
Cada colección es una concesión aparte. Una clave lee temperature-records
porque alguien concedió temperature-records:read, y con eso no viene nada
más.
Qué puede leer
GET /v1/restaurants/{restaurantId}/collections
GET /v1/restaurants/{restaurantId}/collections/{collection}/records?limit=…&cursor=…
GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}
GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}/assets
Empiece por el primero. Lista solo las colecciones que se le concedieron a su clave, cada una con el permiso que la abre, para que nunca tenga que adivinar:
{
"data": [
{
"collection": "temperature-records",
"readScope": "temperature-records:read",
"description": "Received shadow snapshots for temperature-records; not complete primary-store history.",
"stateKind": "received-shadow-snapshot",
"payloadVersion": 1
}
]
}
Un data vacío no es un error ni una caída: es una clave sin ninguna concesión
de colección. La mayoría de las claves emitidas antes de que existiera esta
superficie están exactamente así, y ampliar una es un correo
electrónico.
El sobre
Todos los registros de todas las colecciones llevan los mismos campos
exteriores. Lo único que cambia de forma es data.
| Campo | Tipo | Significado |
|---|---|---|
id | string | El identificador del registro, estable y único dentro de la colección. |
restaurantId | string | El restaurante al que pertenece. Refleja la ruta. |
collection | string | De qué colección viene. Refleja la ruta. |
data | object | ausente | El registro en sí. Una lápida es el único caso en que puede faltar, así que dé por hecho que está presente en todos los demás. |
deleted | boolean | true significa una lápida: el registro se borró en la aplicación. |
capturedAt | string | Cuándo registró el cambio el dispositivo, en milisegundos desde la época Unix como cadena. |
receivedAt | string | Cuándo lo recibió esta API. Posterior a capturedAt, a veces por horas. |
sequence | string | Una posición monótonamente creciente en el flujo de cambios. Compare dos; no haga aritmética con una. |
mutationId | string | El identificador del cambio que produjo este estado. Clave de idempotencia por nuestra parte; clave de deduplicación por la suya. |
sourceVersion | string | null | La marca de versión del registro de origen, cuando la tiene. |
payloadVersion | number | Ahora mismo siempre 1. Sube si alguna vez cambia el significado de data. |
stateKind | string | Siempre received-shadow-snapshot. Lea la sección siguiente. |
capturedAt y receivedAt importan los dos. Una tableta en una cámara
frigorífica sin cobertura registra a las 09:00 y sube a las 14:00; ordenar su
propia canalización por receivedAt la mantiene correcta, e informar sobre
capturedAt la mantiene veraz.
Qué es una instantánea y qué no
stateKind dice received-shadow-snapshot, y la formulación está medida.
- Es el estado actual de un registro, no un historial de cada edición. Lea un registro dos veces y obtendrá el aspecto que tiene ahora, las dos veces.
- Es lo que hemos recibido, no lo que tiene el restaurante. Un dispositivo que nunca subió un cambio significa un registro que esta API no ha visto nunca.
- No es un relleno histórico. Una colección empieza para un restaurante el día en que se le activa la captura. Los registros creados antes están en la aplicación y no aquí.
Así que un registro ausente es genuinamente ambiguo: nunca se registró, o se registró y todavía no ha llegado. No construya encima una cifra que se lea como una auditoría, y consulte la nota sobre exhaustividad antes de dar un recuento a nadie.
Las lápidas son el único caso en que la ausencia no es ambigua. deleted: true
sin data significa que el registro existió y se borró, y es la única manera de
enterarse de que algo ha desaparecido.
Las veinticuatro colecciones
Cada una de ellas toma <collection>:read como permiso: cooling necesita
cooling:read, y así con toda la lista.
| Colección | Qué es un registro |
|---|---|
restaurants | El local en sí: nombre, dirección, días de cierre, suscripción y ajustes. |
users | Las cuentas del personal que registra los controles, y qué módulos usan. |
areas | Las zonas en que se divide un restaurante: cocina, cámara frigorífica, barra. |
equipment | Neveras, congeladores y equipos de cadena de frío, con sus umbrales mín./máx. |
sensors | Las sondas inalámbricas, por dirección MAC, y el equipo que vigila cada una. |
temperature-records | Una temperatura que una persona tomó en un equipo, con el turno y la acción correctiva si la hubo. |
temperature-readings | Una temperatura que un sensor informó por su cuenta, sin intervención. |
suppliers | Quién entrega, con sus vías de contacto y su número de cuenta. |
products | Los productos recibidos y usados. |
preparations | Las elaboraciones propias, con su vida útil y sus alérgenos. |
cleaning-tasks | El plan de limpieza: cada tarea, su zona, su periodicidad, si exige una fotografía. |
cleaning-task-records | Una tarea de limpieza hecha de verdad: cuándo y por quién. |
cleaning-task-pictures | La fotografía que lo demuestra. Lleva un archivo; véase la sección de archivos adjuntos más abajo. |
cooling | Un ciclo de enfriamiento: producto, temperatura inicial y final, hora de inicio y de fin. |
freezing | Una operación de congelación, con la misma forma que el enfriamiento. |
reheating | Una operación de recalentamiento, con la misma forma que el enfriamiento. |
transport | Un producto transportado, con lugar de salida y de llegada, hora y temperatura. |
fryer-equipment | Las freidoras. |
fryer-checks | Un control de la calidad del aceite y qué se decidió: filtrarlo, cambiarlo, dejarlo. |
cooking-equipment | Hornos y equipos de cocción, con sus umbrales. |
cooking-temperature-records | Una temperatura de cocción, tomada por una persona, en un equipo de cocción. |
surface-analyses | Un análisis de superficie: qué se analizó, si pasó, y el plan de acción si no. |
traceability-labels | Una etiqueta de trazabilidad impresa. Lleva un archivo; véase la sección de archivos adjuntos más abajo. |
drive-files | Un documento archivado en el drive del restaurante. Lleva un archivo; véase la sección de archivos adjuntos más abajo. |
La forma campo a campo de cada objeto data está en el documento
OpenAPI, que se genera a partir del
servicio en ejecución: es la única descripción que no puede desviarse de lo que
está desplegado.
Recorrer una colección
GET /v1/restaurants/{restaurantId}/collections/{collection}/records?limit=100
| Parámetro | Obligatorio | Reglas |
|---|---|---|
limit | no | De 1 a 100. Por defecto, 50. |
cursor | no | El nextCursor de la página anterior, tal cual. |
Aquí no hay rango de tiempo, a diferencia de las entregas: usted recorre una colección, no una ventana de ella.
Un recorrido es una instantánea coherente. La primera página fija la
posición del flujo, y todas las páginas siguientes se responden en esa misma
posición. Los registros escritos mientras pagina no le mueven las páginas bajo
los pies y no aparecen a mitad del recorrido: los verá en el siguiente. El orden
es por sequence, ascendente.
El cursor está ligado al restaurante, a la colección y al límite. Cambie el tamaño de página a mitad del recorrido y se rechaza: elija un límite y consérvelo durante todo el recorrido.
async function* records({ apiKey, restaurantId, collection }) {
const base = `https://api.backresto.com/v1/restaurants/${restaurantId}/collections/${collection}/records`;
let cursor = null;
do {
const url = new URL(base);
url.searchParams.set('limit', '100');
if (cursor !== null) {
url.searchParams.set('cursor', cursor);
}
const response = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` }
});
if (!response.ok) {
throw new Error(`BackResto ${response.status}`);
}
const page = await response.json();
yield* page.data;
cursor = page.nextCursor;
} while (cursor !== null);
}
Mantener una copia al día
Hoy no hay parámetro since. Una actualización es otro recorrido, y usted
concilia con lo que ya tiene:
- Indexe por
iddentro de una colección, y sobrescriba cuando elsequenceque reciba sea mayor que el que guardó. - Respete las lápidas.
deleted: truees la instrucción de borrado; aplicarla es la única manera de que su copia deje de divergir. - Recorra a una hora sensata. Una colección entera con
limit=100son un puñado de peticiones para un solo restaurante, y el presupuesto es de 300 por minuto; pero varios cientos de restaurantes en el mismo minuto de cron son un pico del que responde usted.
Si un cursor incremental cambiaría lo que puede construir, díganoslo. Es un cambio pequeño sobre un flujo que ya está ordenado: la razón de que no exista es que nadie lo ha necesitado todavía.
Archivos adjuntos
Tres colecciones llevan un archivo: cleaning-task-pictures,
traceability-labels y drive-files. El registro lo nombra en data.asset;
los bytes salen del endpoint de archivos adjuntos.
GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}/assets
{
"data": [
{
"id": "a17c93be4f02",
"contentType": "application/pdf",
"byteLength": 184320,
"sha256": "9f2a…",
"uploadedAt": "1789000000000",
"url": "https://…?X-Amz-Signature=…",
"urlExpiresAt": "1789000900000"
}
]
}
Responde casi igual que las fotografías de entrega: una lista de URL firmadas, cada una válida durante quince minutos y con su propia autorización, de modo que la descarga no necesita cabecera. Conviene conocer dos diferencias.
Son archivos, no solo fotos. Una prueba de limpieza es una fotografía, pero
una etiqueta de trazabilidad o un archivo del drive puede ser un PDF, un CSV,
una hoja de cálculo o un documento de Word: lea contentType en lugar de
suponer una imagen, y no renombre todo a .jpg al entrar.
sha256 está ahí para ahorrarle trabajo. Es el resumen criptográfico de los
bytes: si coincide con algo que ya guardó, no necesita volver a descargarlo.
Solo aparecen los archivos cuya subida ha terminado y se ha verificado: uno todavía en tránsito está ausente, no roto, y lo mismo ocurre en cuanto se borra el registro al que pertenece.
El resto del consejo es idéntico, y vale la pena repetirlo porque el fallo es silencioso: descargue los bytes ahora, guarde los bytes en lugar del enlace, y vuelva a llamar al endpoint cuando necesite una URL nueva.
Los fallos con los que se topará
403 — su clave alcanza el restaurante, pero no con el permiso de esa
colección. El endpoint de colecciones es la vía barata de averiguar cuáles sí
tiene.
404 — ninguna concesión para ese restaurante, o ese registro no existe.
Ambos casos son indistinguibles a propósito.
400 — una colección fuera de las veinticuatro de arriba, o un cursor que
no pertenece a este restaurante, colección y límite.
Los tres son documentos de problema con un requestId.