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)
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
string
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.string
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.string
Timezone IANA de la tienda (ej.
"America/Sao_Paulo"). Úsala para interpretar businessDayDate y los timestamps metrics.*_order_at.string
Siempre
"CLOSED" para este evento. El campo existe por paridad con el shape del row del snapshot; no hay otro valor posible aquí.string | null
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.string
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
object
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
object
Breakdown de conteo de órdenes del día.
object
Agregados de ingresos. Incluye solo órdenes
COMPLETED + SUCCEEDED en la moneda dominante.object
Indicadores de performance.
Breakdowns
array
Sales por canal (APP, KIOSK, POS, WEB, …). Cada entrada incluye un sub-breakdown
by_fulfillment (DELIVERY, PICKUP, DINE_IN, etc.). Solo órdenes COMPLETED + SUCCEEDED.array
Sales por procesador de pago (
CASH, CARD, PIX, …). Agregado desde payment_methods[].processor a través de las órdenes pagas.Anomalías
array
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.object
Stats agregados de force-close — conteo por razón y total de ingresos no cobrados (suma de
forceClosedOrders[].total).array
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 [].object | null
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.
