# Colecciones y registros

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

Source: https://developers.backresto.com/es/docs/collections

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

```http
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:

```json
{
  "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](https://developers.backresto.com/es/docs/access).

## 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í. Un tombstone 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 un tombstone: 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
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](https://developers.backresto.com/es/docs/deliveries) 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](https://developers.backresto.com/es/docs/support) 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ías | Se 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-pictures` | `restaurants`, `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](https://developers.backresto.com/es/docs/support).

## 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ágina | Colecciones |
| --- | --- |
| [Temperaturas](https://developers.backresto.com/es/docs/temperatures) | `temperature-records`, `temperature-readings`, `cooking-temperature-records` |
| [Entregas](https://developers.backresto.com/es/docs/deliveries) | `deliveries`, `delivery-pictures` |
| [Etiquetas de trazabilidad](https://developers.backresto.com/es/docs/traceability-labels) | `traceability-labels` |
| [Limpieza](https://developers.backresto.com/es/docs/cleaning) | `cleaning-tasks`, `cleaning-task-records`, `cleaning-task-pictures` |
| [Freidoras](https://developers.backresto.com/es/docs/fryers) | `fryer-equipment`, `fryer-checks` |
| [Enfriamiento y procesos alimentarios](https://developers.backresto.com/es/docs/food-processes) | `cooling`, `freezing`, `reheating`, `transport` |
| [Análisis de superficie](https://developers.backresto.com/es/docs/surface-analyses) | `surface-analyses` |
| [Documentos](https://developers.backresto.com/es/docs/drive-files) | `drive-files` |
| [Catálogo](https://developers.backresto.com/es/docs/catalogue) | `suppliers`, `products`, `preparations` |
| [Instalaciones y personas](https://developers.backresto.com/es/docs/facilities) | `restaurants`, `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](https://api.backresto.com/openapi.json) 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

```http
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: 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.

```js
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](https://developers.backresto.com/es/docs/rate-limits) 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.

```http
GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}/assets
```

```json
{
  "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](https://developers.backresto.com/es/docs/errors) con un `requestId`.
