Colecciones y registros

Las veintiséis colecciones de registros, el sobre de snapshot que comparten y cómo recorrer una.

Una colección es un tipo de registro: temperature-records, cooling, deliveries y otros veintitrés. Todas se leen igual, a través de los mismos cuatro endpoints, y todos los registros vuelven en el mismo sobre: los campos que rodean a data y que dicen cuándo se capturó y si sigue existiendo.

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 scope 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í. Un tombstone es el único caso en que puede faltar, así que dé por hecho que está presente en todos los demás.
deletedbooleantrue significa un tombstone: 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 propio pipeline por receivedAt lo mantiene correcto, e informar sobre capturedAt lo mantiene veraz.

Qué es un snapshot 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 una carga del 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í. Las entregas son la única excepción: esas dos colecciones llevan el historial que la aplicación todavía conservaba cuando se abrieron.

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.

Los tombstones 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.

Cuánto tiempo se quedan los registros

Los registros operativos se quedan aquí 90 días, contados desde el momento en que ocurrieron en la cocina (data.timestamp), no desde que los recibimos. Después, un proceso nocturno los traslada a un archivo en frío bloqueado, que se conserva cinco años, y dejan de aparecer en cualquier lectura: listas, registro individual y sus archivos adjuntos.

Se conservan 90 díasSe conservan durante toda la vida de la cuenta
temperature-readings, temperature-records, cooking-temperature-records, cleaning-task-records, cleaning-task-pictures, cooling, freezing, reheating, transport, fryer-checks, surface-analyses, traceability-labels, deliveries, delivery-picturesrestaurants, users, areas, equipment, sensors, cooking-equipment, suppliers, products, preparations, cleaning-tasks, fryer-equipment, drive-files

Un registro archivado no es un tombstone, y es la única excepción a la regla de los tombstones de arriba. Se va sin deleted: true, porque nadie lo borró: se trasladó. Si mantiene una copia, conserve lo que ya tiene y no trate nunca como un borrado un registro que falta en un recorrido. Para todo lo que tenga más de 90 días, su copia es la fuente; una inspección que necesite nuestro archivo pasa por soporte.

Las veintiséis colecciones

Cada una tiene <collection>:read como scope: cooling necesita cooling:read, y así sucesivamente. Están documentadas por dominio, campo a campo, en las páginas siguientes.

PáginaColecciones
Temperaturastemperature-records, temperature-readings, cooking-temperature-records
Entregasdeliveries, delivery-pictures
Etiquetas de trazabilidadtraceability-labels
Limpiezacleaning-tasks, cleaning-task-records, cleaning-task-pictures
Freidorasfryer-equipment, fryer-checks
Enfriamiento y procesos alimentarioscooling, freezing, reheating, transport
Análisis de superficiesurface-analyses
Documentosdrive-files
Catálogosuppliers, products, preparations
Instalaciones y personasrestaurants, users, areas, equipment, sensors, cooking-equipment

Las temperaturas de una entrega están siempre en Celsius: una lectura tomada en Fahrenheit se convierte al llegar, y todo temperatureRecords[].unit dice "C".

Empiece por Instalaciones y personas si va a leer cualquiera de las otras: los umbrales de los equipos y las zonas son lo que convierte una medición a secas en algo que se puede juzgar.

El documento OpenAPI lleva las mismas formas, generadas a partir del servicio en ejecución: cuando una página de aquí y ese documento no coinciden, el documento tiene razón y nosotros tenemos una página que arreglar.

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: usted recorre una colección, no una ventana de ella. Para leer una semana de entregas, recorra deliveries y quédese con los registros cuyo data.timestamp caiga en esa semana.

Un recorrido es un snapshot 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 los tombstones. 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

Cuatro colecciones llevan un archivo: cleaning-task-pictures, delivery-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 con una lista de URL firmadas, cada una válida durante quince minutos, y todas las URL de una misma respuesta comparten la misma caducidad. Cada una lleva su propia autorización, de modo que la descarga no necesita cabecera Authorization, lo que también significa que el enlace es en sí mismo una credencial al portador: durante quince minutos, cualquiera que lo tenga puede leer ese archivo. Conviene saber dos cosas.

Son archivos, no solo fotos. Una prueba de limpieza o una fotografía de entrega es una imagen, 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 vale la pena repetirlo porque el fallo es silencioso: descargue los bytes ahora, guarde los bytes en lugar del enlace, no ponga nunca la URL donde sobreviva a su propósito (un correo, un ticket, una caché de larga duración), y vuelva a llamar al endpoint cuando necesite una URL nueva. Es barato, y vuelve a comprobar la concesión.

Los fallos con los que se topará

403: su clave alcanza el restaurante, pero no con el scope 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 veintiséis 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-29.