store.business_day_closed se dispara cuando el pipeline automatizado de cierre de día de Fire termina de construir el snapshot inmutable del día operativo de una tienda. El payload es una proyección directa de ese snapshot más el bloque store canónico v4 — los consumers (Kinesis, ERP, BI) arman su propio shape sin necesidad de hacer queries de regreso a Fire.
A diferencia de los eventos de orden, este es un evento a nivel store: uno por tienda cerrada, no uno por orden. El snapshot contiene métricas agregadas, breakdowns por canal/método de pago, detalle de órdenes force-closed y detalle de cancelaciones del día.
Condición de disparo
Fire emitestore.business_day_closed una vez por snapshot (storeId, businessDayDate), la primera vez que todas estas condiciones se cumplen:
- La tienda tenía un día operativo abierto (
isBusinessDayOpen === true) para la fecha a cerrar - El pipeline de cierre (
CloseBusinessDayService.execute()) completó los 9 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. A diferencia de fiscal.*.br, sin gating por país. |
| Clave de idempotencia | event.id (también trigger.data.snapshotId) |
| Se dispara más de una vez | No, salvo retry. Un snapshot por (storeId, fecha). |
| Origen | Pipeline cron (v1). El path de cierre manual aún no emite. |
| Latencia vs cierre real | Síncrono — el evento se encola inmediatamente después de que saveSnapshot() retorna. |
Qué hay en trigger.data
El V4StoreDayClosedSnapshot completo — 17 keys top-level cubriendo identidad, el bloque store canónico, agregados de sales/metrics/summary, breakdowns por canal y por método de pago, detalle de órdenes force-closed, detalle de cancelaciones, y metadata extensible.
Ejemplo — payload real (sanitizado)
Referencia de data.*
Identidad y temporal
UUID del snapshot persistido en la DB de Fire. También expuesto como
_meta.triggerEntityId. Úsalo como clave de idempotencia de tu lado — re-entregas del mismo cierre comparten el mismo snapshotId.Fecha calendario que representa el cierre, en la timezone de la tienda (ej.
"2026-03-31"). No es la fecha UTC en la que se disparó el evento.Timezone IANA de la tienda (ej.
"America/Sao_Paulo"). Úsala para interpretar businessDayDate y los timestamps metrics.*_order_at.Siempre
"CLOSED" para este evento. El campo existe por paridad con el shape del row del snapshot; no hay otro valor posible aquí.Timestamp ISO 8601 UTC de cuando se abrió el día. Puede ser
null para tiendas que nunca abrieron explícitamente (data legacy); para tiendas cerradas por el cron v1 siempre está presente.Timestamp ISO 8601 UTC de cuando el pipeline persistió el snapshot. Úsalo para medir SLI/SLO en vez de
event.createdAt (que es el tiempo de despacho de la cola).closedBy — identidad del operador
Identidad del operador ya resuelta. No hace falta hacer lookups en runtime downstream.
store — bloque store canónico
Mismo shape que en order.completed — ver order.completed para la referencia de campos. Aclaraciones clave:
store.externalIdes el identificador controlado por Fire desdesettings.externalId, no lo que el sistema POS haya inyectado. Úsalo como clave de reconciliación del lado partner.store.storeFiscalConfigviaja completo (CNPJ, IE,storeCode3S, etc. para BR; campos análogos para otros países). Las credenciales nunca se incluyen.store.vendor.nameystore.account.namese pueblan desde el metadata de account de Fire.descriptionyloyaltyPlanpueden venirnull/falseaquí incluso cuando están poblados en eventos de orden — esos campos vienen del payload POS inyectado en eventos de orden y no son parte del metadata de account de Fire hoy.
summary, sales, metrics
Breakdown de conteo de órdenes del día.
Agregados de ingresos. Incluye solo órdenes
COMPLETED + SUCCEEDED en la moneda dominante.Indicadores de performance.
Breakdowns
Sales por canal (APP, KIOSK, POS, WEB, …). Cada entrada incluye un sub-breakdown
by_fulfillment (DELIVERY, PICKUP, DINE_IN, etc.). Solo órdenes COMPLETED + SUCCEEDED.Sales por procesador de pago (
CASH, CARD, PIX, …). Agregado desde payment_methods[].processor a través de las órdenes pagas.Anomalías
Detalle de órdenes que seguían
OPEN al momento del cierre. Cada entrada incluye order_id, order_uid, payment_status, total, el snapshot original de payment_methods[], y un closure_reason clasificando por qué la orden no se completó naturalmente.Valores posibles de closure_reason: ABANDONED, PAYMENT_PENDING, PAYMENT_FAILED, PARTIAL_PAYMENT, INTEGRATION_ERROR.Stats agregados de force-close — conteo por razón y total de ingresos no cobrados (suma de
forceClosedOrders[].total).Detalle por orden de cancelaciones (
order_id, order_uid, order_code, cancelled_at, cancellation_reason, cancellation_source de "adapter" o "backoffice", total, currency). Para snapshots creados antes de que este campo existiera (data legacy), este array es [].Bucket extensible. Hoy lleva
cash_reconciliation_summary (siempre, incluso si vacío) y opcionalmente currency_anomaly (cuando se detectó más de una moneda en el día). Puede crecer con el tiempo sin romper el contrato — trata claves desconocidas como forward-compatibles.Ejemplo de handler
Gotchas comunes
closedBy.uidpuede ser el nil UUID"00000000-0000-0000-0000-000000000000"— para cierres system donde el account no configuró un system user custom. UsaclosedBy.type === "system"para detectar cierres system en vez de parsearuid.sales.currencypuede sernullcuando la tienda cerró sin órdenes pagas. No asumas un default — bifurca sobre el null.- Las keys de
closureStats.by_reasonsiempre están presentes, incluso cuando son cero. No filtres por presencia; suma/compara sobre los valores. metrics.first_order_at/last_order_at/peak_hour*están ausentes (nonull, ausentes) cuando la tienda no tuvo órdenes. Usa optional chaining.cancelledOrderses[]para snapshots creados antes de que se agregara el detalle por-orden de cancelaciones. Si lo necesitas en data histórica, consulta directamente la API de snapshots de Fire — los snapshots viejos no se backfillean retroactivamente.store.vendor.descriptionystore.vendor.loyaltyPlanpueden venirnull/falseacá incluso cuando están poblados en eventos a nivel de orden. Esos campos vienen del payload POS inyectado en eventos de orden y no se almacenan en el metadata de account de Fire hoy.
Eventos relacionados
order.completed
Se dispara por cada orden individual al completarse.
store.business_day_closed agrega muchos eventos order.completed.order.cancelled
Evento por cancelación individual. Cada cancelación también aparece en
cancelledOrders[] del cierre del día.
