> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fire.rest/llms.txt
> Use this file to discover all available pages before exploring further.

# store.business_day_closed

> Una tienda cerró su día de negocio — un evento thin con la identidad del cierre, timing, quién lo cerró y una ref mínima de tienda.

<Tabs>
  <Tab title="v1 · current">
    Estás viendo el contrato **actual (v1)** de `store.business_day_closed` — un **payload thin**: identidad del cierre, timing, `closedBy` y una **`store` mínima**. Ya no arrastra las métricas agregadas del día, los breakdowns ni el detalle de anomalías — eso lo consultás por `businessDayId` cuando lo necesites.
  </Tab>

  <Tab title="v0 · deprecated">
    <Warning>El contrato **v0** (el snapshot completo con `sales`/`metrics`/breakdowns embebidos) está **deprecado** — se mantiene solo como referencia histórica. Abrilo acá: [store.business\_day\_closed — v0](/es/events-v0/store-day-closed).</Warning>
  </Tab>
</Tabs>

`store.business_day_closed` dispara cuando el pipeline automático de cierre de día de Fire termina de construir el snapshot inmutable del día operativo de una tienda. El payload es **thin**: lleva la **identidad** del cierre (`businessDayId`), el **timing**, **quién lo cerró** y una **ref mínima de tienda** — lo suficiente para reaccionar a "el día cerró" y matchearlo de tu lado. Los agregados pesados (ventas, métricas, breakdowns por canal/pago, detalle de force-closed y cancelaciones) viven en el snapshot y se obtienen por `businessDayId` — **no** vienen embebidos en el evento.

A diferencia de los eventos a nivel orden, este es un evento **a nivel tienda**: uno por tienda cerrada, no uno por orden.

## Condición de disparo

Fire emite `store.business_day_closed` una vez por snapshot `(storeId, businessDayDate)`, la primera vez que **todo** lo siguiente es verdadero:

* La tienda tenía un día de negocio abierto (`isBusinessDayOpen === true`) para la fecha que se cierra
* El pipeline de cierre (`CloseBusinessDayService.execute()`) completó todos los pasos sin error (force-close, cálculo de métricas, persistencia del snapshot)
* No existe ya un snapshot para ese par `(storeId, businessDayDate)` (guard de idempotencia)

|                         |                                                                 |
| ----------------------- | --------------------------------------------------------------- |
| Cobertura               | **Universal** — todos los países. Sin gating por país.          |
| Llave de idempotencia   | `event.id` (también `trigger.data.businessDayId`)               |
| Dispara más de una vez  | No, salvo reintento. Un snapshot por (storeId, fecha).          |
| Origen                  | Pipeline cron (v1). El cierre manual aún no emite.              |
| Latencia vs cierre real | Síncrono — el evento se encola apenas `saveSnapshot()` retorna. |

## Qué hay en `trigger.data`

Un payload **thin**: identidad (`businessDayId`, `businessDayDate`, `timezone`, `status`), timing (`openedAt`, `closedAt`), el `closedBy` resuelto, y una ref mínima de `store` (`uid`, `code`, `externalId`, `countryCode`, `timezone`, `currencyCode`). Sin métricas, breakdowns ni arrays de anomalías — consultá el snapshot por `businessDayId` si necesitás el detalle.

## Ejemplo — payload real (sanitizado)

```json theme={null}
{
  "event": {
    "id": "43214dd1-e260-4245-945d-5bbdfc4a54be",
    "type": "store.business_day_closed",
    "createdAt": "2026-05-18T21:59:01.306Z"
  },
  "data": {
    "businessDayId": "2c332116-79ec-4c63-a53d-8b87fff5a654",
    "businessDayDate": "2026-03-31",
    "timezone": "America/Sao_Paulo",
    "status": "CLOSED",
    "openedAt": "2026-03-31T09:00:02.55Z",
    "closedAt": "2026-05-18T21:58:09.882Z",
    "closedBy": {
      "uid": "00000000-0000-0000-0000-000000000000",
      "name": "Cierre Automático — Sandbox",
      "type": "system"
    },
    "store": {
      "uid": "a4019cad-bbac-4269-9f8d-f29654e92c45",
      "code": "BR-SP-001",
      "externalId": "200400500",
      "countryCode": "BR",
      "timezone": "America/Sao_Paulo",
      "currencyCode": "BRL"
    }
  },
  "_meta": { "executionId": "...", "flowId": "...", "flowName": "...", "attempt": 0, "triggerEntityId": "2c332116-79ec-4c63-a53d-8b87fff5a654" }
}
```

## Referencia de `data.*`

### Identidad y temporal

<ResponseField name="businessDayId" type="string">
  UUID del snapshot de día de negocio persistido en la base de Fire. También aparece como `_meta.triggerEntityId`. Usalo como llave de idempotencia de tu lado — los reenvíos del mismo cierre comparten el mismo `businessDayId` — y como llave de lookup para traer los agregados del día desde la API de snapshots de Fire cuando los necesites.
</ResponseField>

<ResponseField name="businessDayDate" type="string">
  Fecha calendario que representa el cierre, en la timezone de la tienda (ej. `"2026-03-31"`). **No** la fecha UTC en que disparó el evento.
</ResponseField>

<ResponseField name="timezone" type="string">
  Timezone IANA de la tienda (ej. `"America/Sao_Paulo"`). Usala para interpretar `businessDayDate`.
</ResponseField>

<ResponseField name="status" type="string">
  Siempre `"CLOSED"` para este evento. No hay otro valor posible.
</ResponseField>

<ResponseField name="openedAt" type="string | null">
  Timestamp ISO 8601 UTC de apertura del día. Puede ser `null` para tiendas que nunca abrieron explícitamente (data legacy); para tiendas cerradas por cron v1 siempre está presente.
</ResponseField>

<ResponseField name="closedAt" type="string">
  Timestamp ISO 8601 UTC en que el pipeline persistió el snapshot. Usalo para medir SLI/SLO en vez de `event.createdAt` (que es el momento del dispatch de la cola).
</ResponseField>

### `closedBy` — identidad del operador

<ResponseField name="closedBy" type="object">
  Identidad del operador ya resuelta. **Sin lookups en runtime downstream.**

  <Expandable title="closedBy">
    <ResponseField name="uid" type="string">
      Para `type: "system"` (v1): el UUID `accounts.id` (o un nil UUID estable `"00000000-0000-0000-0000-000000000000"` para cuentas sin system-user configurado). Para `type: "user"` (v2, aún no emitido): el UUID del usuario en la tabla de auth de Fire.
    </ResponseField>

    <ResponseField name="name" type="string">
      Label legible. Para system: típicamente `"Cierre Automático — {accountName}"`. Para user: el display name del usuario.
    </ResponseField>

    <ResponseField name="type" type="string">
      `"system"` para cierres automáticos por cron (v1). `"user"` queda reservado para cierres manuales (v2, no implementado).
    </ResponseField>
  </Expandable>
</ResponseField>

### `store` — ref mínima de tienda

<ResponseField name="store" type="object">
  Una referencia mínima de tienda — identidad más país/timezone/moneda. El bloque store canónico completo (name, address, vendor, account, fiscal config) **no** viene acá; si lo necesitás, tomalo de los eventos [`order.completed`](/es/events/order-completed#store) de la misma tienda.

  <Expandable title="store">
    <ResponseField name="uid" type="string | null">UUID de la tienda en Fire.</ResponseField>
    <ResponseField name="code" type="string | null">Código legible de la tienda (ej. `"BR-SP-001"`).</ResponseField>
    <ResponseField name="externalId" type="string | null">El identificador externo controlado por Fire (`settings.externalId`), no lo que haya inyectado un POS. Usalo como llave de reconciliación de tu lado.</ResponseField>
    <ResponseField name="countryCode" type="string | null">Código de país ISO de la tienda (ej. `"BR"`).</ResponseField>
    <ResponseField name="timezone" type="string | null">Timezone IANA de la tienda. Espeja `data.timezone`.</ResponseField>
    <ResponseField name="currencyCode" type="string | null">Código de moneda ISO 4217 de la tienda (ej. `"BRL"`).</ResponseField>
  </Expandable>
</ResponseField>

## Ejemplo de handler

```js theme={null}
async function onStoreDayClosed(data) {
  const { businessDayId, businessDayDate, timezone, store, closedBy } = data;

  // 1. Idempotencia — saltar si ya procesamos este cierre
  const existing = await db.dayCloses.findUnique({ where: { businessDayId } });
  if (existing) return;

  // 2. Registrar el cierre (lean — identidad + ref de tienda)
  await db.dayCloses.create({
    data: {
      businessDayId,
      businessDayDate,
      timezone,
      storeExternalId: store.externalId,
      storeCode: store.code,
      countryCode: store.countryCode,
      currencyCode: store.currencyCode,
      closedByName: closedBy.name,
      closedByType: closedBy.type,
    },
  });

  // 3. ¿Necesitás los agregados del día (sales, metrics, anomalías)? Traelos por businessDayId.
  //    NO están en este evento — consultá la API de snapshots de Fire on demand.
  // const snapshot = await fire.businessDay.get(businessDayId);
}
```

## Errores comunes

* **Las métricas del día NO están en este evento.** `sales`, `metrics`, `byChannel`, `byPaymentMethod`, `forceClosedOrders`, `closureStats`, `cancelledOrders` y `metadata` ya no viajan inline. Si dependías de ellos, traé el snapshot por `businessDayId`.
* **`snapshotId` se renombró a `businessDayId`.** Si parseabas `data.snapshotId`, cambiá a `data.businessDayId` (mismo valor — el id de la fila del snapshot).
* **`closedBy.uid` puede ser el nil UUID `"00000000-0000-0000-0000-000000000000"`** — en cierres system donde la cuenta no configuró un system user. Detectá cierres system con `closedBy.type === "system"`, no parseando el `uid`.
* **`businessDayDate` es la fecha calendario de la tienda, no UTC.** Interpretala con `timezone`.
* **Los campos `store.*` pueden ser `null`** ante configuración de tienda incompleta. No asumas non-null.

## Eventos relacionados

<CardGroup cols={2}>
  <Card title="order.completed" icon="receipt" href="/es/events/order-completed">
    Dispara por cada orden al completarse. El bloque store completo vive acá.
  </Card>

  <Card title="order.cancelled" icon="circle-xmark" href="/es/events/order-cancelled">
    Evento por cancelación. Usalo para el detalle de cancelaciones en vez del payload de cierre.
  </Card>
</CardGroup>
