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.

CampoTipoSignificado
idstringEl identificador del registro, estable y único dentro de la colección.
restaurantIdstringEl restaurante al que pertenece. Refleja la ruta.
collectionstringDe qué colección viene. Refleja la ruta.
dataobject | ausenteEl 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.
deletedbooleantrue significa una lápida: el registro se borró en la aplicación.
capturedAtstringCuándo registró el cambio el dispositivo, en milisegundos desde la época Unix como cadena.
receivedAtstringCuándo lo recibió esta API. Posterior a capturedAt, a veces por horas.
sequencestringUna posición monótonamente creciente en el flujo de cambios. Compare dos; no haga aritmética con una.
mutationIdstringEl identificador del cambio que produjo este estado. Clave de idempotencia por nuestra parte; clave de deduplicación por la suya.
sourceVersionstring | nullLa marca de versión del registro de origen, cuando la tiene.
payloadVersionnumberAhora mismo siempre 1. Sube si alguna vez cambia el significado de data.
stateKindstringSiempre 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ónQué es un registro
restaurantsEl local en sí: nombre, dirección, días de cierre, suscripción y ajustes.
usersLas cuentas del personal que registra los controles, y qué módulos usan.
areasLas zonas en que se divide un restaurante: cocina, cámara frigorífica, barra.
equipmentNeveras, congeladores y equipos de cadena de frío, con sus umbrales mín./máx.
sensorsLas sondas inalámbricas, por dirección MAC, y el equipo que vigila cada una.
temperature-recordsUna temperatura que una persona tomó en un equipo, con el turno y la acción correctiva si la hubo.
temperature-readingsUna temperatura que un sensor informó por su cuenta, sin intervención.
suppliersQuién entrega, con sus vías de contacto y su número de cuenta.
productsLos productos recibidos y usados.
preparationsLas elaboraciones propias, con su vida útil y sus alérgenos.
cleaning-tasksEl plan de limpieza: cada tarea, su zona, su periodicidad, si exige una fotografía.
cleaning-task-recordsUna tarea de limpieza hecha de verdad: cuándo y por quién.
cleaning-task-picturesLa fotografía que lo demuestra. Lleva un archivo; véase la sección de archivos adjuntos más abajo.
coolingUn ciclo de enfriamiento: producto, temperatura inicial y final, hora de inicio y de fin.
freezingUna operación de congelación, con la misma forma que el enfriamiento.
reheatingUna operación de recalentamiento, con la misma forma que el enfriamiento.
transportUn producto transportado, con lugar de salida y de llegada, hora y temperatura.
fryer-equipmentLas freidoras.
fryer-checksUn control de la calidad del aceite y qué se decidió: filtrarlo, cambiarlo, dejarlo.
cooking-equipmentHornos y equipos de cocción, con sus umbrales.
cooking-temperature-recordsUna temperatura de cocción, tomada por una persona, en un equipo de cocción.
surface-analysesUn análisis de superficie: qué se analizó, si pasó, y el plan de acción si no.
traceability-labelsUna etiqueta de trazabilidad impresa. Lleva un archivo; véase la sección de archivos adjuntos más abajo.
drive-filesUn 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ámetroObligatorioReglas
limitnoDe 1 a 100. Por defecto, 50.
cursornoEl 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 id dentro de una colección, y sobrescriba cuando el sequence que reciba sea mayor que el que guardó.
  • Respete las lápidas. deleted: true es 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=100 son 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.

Última actualización: 2026-09-19.