- v1.2 · actual
- v1.1 · anterior
- v1 · deprecado
- v0 · deprecado
Estás viendo el contrato actual (v1.2) de
order.completed. v1.2 agrega data.fiscalRepresentation: la numeración fiscal que el punto de venta obtuvo antes de inyectar la orden. Viaja siempre: en null cuando no se intentó numerar, y con contenido cuando sí —numberingStatus dice cómo terminó—. Que traiga contenido no significa que el comprobante esté autorizado. Solo aditivo — nada de lo que ya leías cambió.El bloque lleva el documento fiscal vigente de la orden: los campos que significan lo mismo en todo país arriba, los identificadores del ente dentro de countryData con el vocabulario de su país, y los documentos anteriores en history. Cuando una anulación numera, la nota de crédito pasa arriba y la factura baja al histórico — con compensates apuntando a ella.order.completed dispara cuando una orden se inyecta exitosamente y está pagada. Lleva el snapshot V4 de la orden como trigger.data — todos los campos que tu flow necesita para actuar sobre la orden sin volver a llamar a Fire.
Condición de disparo
Fire emiteorder.completed exactamente una vez por orden, la primera vez que ambos son verdaderos en momento de inyección:
order.status === "COMPLETED"order.paymentStatus === "SUCCEEDED"
PENDING de pago, o que fallan el pago, nunca producen order.completed. Las cancelaciones después de completar producen un evento separado order.cancelled — no retraen order.completed.
Qué hay en trigger.data
trigger.data es el snapshot V4 de la orden — el mismo objeto que se persiste en flow_queue.trigger_data y se expone a los templates de tu flow.
Las claves top-level, en orden:
Ejemplo — payload real de producción (BR, sanitizado)
El ejemplo abajo viene de una fila real deflow_queue.trigger_data (tenant sandbox brasileño, canal KIOSK, servicio dine-in). Los campos PII se reemplazan por placeholders; el resto de los campos y formas son verbatim.
Los valores monetarios son strings con el importe entero escalado ×10.000 — cuatro decimales fijos.
"229000" es 22,9 BRL, no 229.000. Para leerlo, dividí por 10.000.Es la escala con la que FIRE almacena: evita el drift de punto flotante al sumar impuestos a través de varias integraciones. Parseá con una librería decimal, nunca con parseFloat.La excepción es paymentMethods[].totalBill, que el origen a veces manda como número JSON — manejá los dos casos.Referencia de campos
Identificadores top-level
string
UUID interno de la orden en Fire. Estable a través de entregas; úsalo junto con
event.id para trazabilidad.string | null
Código corto legible mostrado en recibos y pantallas KDS (p. ej.
95K, OC-br-001). null cuando el canal no asigna uno.string
Día de negocio al que pertenece esta orden, en
YYYY-MM-DD. Calculado en hora local de la tienda, así que una orden hecha a las 01:00 puede pertenecer al día de negocio anterior según el corte de fin de día.string
El ID de orden tal como lo provee el canal/agregador en la inyección. Úsalo para reconciliar con sistemas upstream (POS, dashboards de agregador).
string | null
Timestamp ISO 8601 UTC de cuándo se hizo la orden originalmente. Distinto de
event.createdAt, que es cuándo arrancó la ejecución del flow.string
Siempre
"COMPLETED" para este evento.string
Siempre
"SUCCEEDED" para este evento.boolean
true si se canjearon puntos de fidelidad en esta orden.boolean
true si el cliente acumuló puntos de fidelidad.boolean
true si se aplicó algún descuento.string
Nota free-text del cliente para toda la orden. Empty string cuando no se setea.
data.store
object
Snapshot de la tienda en el momento que se completó la orden.
data.client
object | null
Cliente que hizo la orden.
null para órdenes de canal totalmente anónimas. Cuando la venta no identifica al comprador —en cualquier país— client viaja poblado con el marcador de consumidor final: govIdType: "FINAL_CONSUMER" y govIdNumber en ceros. No llega traducido a la regla de cada régimen — el NIT genérico de la DIAN, por ejemplo, lo resuelve el proveedor fiscal.data.payments
object
Desglose de dinero.
data.fulfillment
object
Cómo se entrega la orden.
data.kds
object
Contexto del kitchen display.
data.device y data.operator
object
{ uid, name, platform, metadata.ip } — dispositivo de origen. Los campos pueden ser null para canales no físicos.object
{ uid, name, session.uid } — staff/cajero que procesó la orden. Todos los campos null para canales self-service (kiosko, web).data.orderLines
object[]
Productos pedidos. Totalmente camelCase (transformado por el builder V4).
data.marketing, data.metadata, data.channel
object | null
Loyalty + cupones.
null en la mayoría de países hoy; reservado para uso futuro.object
Bag free-form para extras a nivel de orden. A menudo
{}.object
{ uid, code, metadata }. Ejemplos de code: KIOSK, APP, IFOOD, RAPPI.Datos fiscales
La data fiscal se incluye solo cuando la tienda tiene emisión fiscal habilitada (
store.storeFiscalConfig.enabled === true). Para países sin fiscal o tiendas sin configuración, las tres ubicaciones de abajo están ausentes o en null.order.completed lleva información fiscal en tres ubicaciones distintas. Cada una sirve un propósito distinto:
1. data.store.storeFiscalConfig — identidad del emisor y config del proveedor
Identifica la entidad legal que emite el documento y cómo autenticar con el proveedor fiscal. Las credenciales NO están aquí intencionalmente — el nodo fiscal las obtiene por proveedor/account.
2. data.payments.metadata.fiscal — agregados fiscales a nivel de orden
Totales agregados estilo SEFAZ, listos para enviar al proveedor fiscal (tu proveedor fiscal en Brasil). Los valores son strings escalados ×10.000, igual que el resto del evento.
3. data.orderLines[n].metadata.fiscal — clasificación fiscal por línea
Códigos fiscales por producto. Usados por el proveedor fiscal para clasificar cada línea en el documento.
taxes[n].metadata (en payments.totals[].taxes[], orderLines[].price.totalPrice[].taxes[] y orderLines[].lineTotals[].taxes[]) con códigos como cst, cBenef, cClassTrib, reducao, rateNominal, rateEffective.
Variaciones por país
El ejemplo de arriba es de una tienda brasileña — el caso más complejo. La forma del V4 es idéntica en todos los países, incluido el desglose granular detaxes[]: cada país popula payments.totals[].taxes[] y los taxes por línea con sus impuestos locales en la misma forma { base, name, rate, amount, metadata }. Lo que cambia por país:
- Nombres de impuesto — BR usa
ICMS,PIS,COFINS,IBS_UF,IBS_MUN,CBS; otros países llevan sus impuestos locales (ej.IVA) con la misma estructura. - Códigos de
metadata— BR lleva códigos SEFAZ (cst,cBenef,cClassTrib,reducao,rateNominal,rateEffective); otros países sus propios códigos. - Emisión fiscal SEFAZ — solo Brasil.
payments.metadata.fiscal,orderLines[].metadata.fiscal(ncm/cfop/csosn) y los eventosorder.invoiced/order.reversedsolo aplican a BR. Los demás países igual llevan sutaxes[], pero estos bloques SEFAZ están ausentes.
- Brasil (BR)
- Argentina (AR)
- Chile (CL)
- Colombia (CO)
- Ecuador (EC)
- Venezuela (VE)
Las tiendas brasileñas con
storeFiscalConfig.enabled === true llevan el payload fiscal completo — ver la sección Datos fiscales arriba. Marcadores de país:store.locationInfo.country.code: "BR"·name: "Brasil"·timezone: "America/Sao_Paulo"store.locationInfo.currencyCode: "BRL"store.storeFiscalConfig.govIdType: "CNPJ"(14 dígitos)store.storeFiscalConfig.secondaryGovIdType: "INSCRICAO_ESTADUAL"payments.totals[].currencyCode: "BRL",paymentMethods[].currencyCode: "BRL",orderLines[].selectedCurrency: "BRL"- Populados:
payments.metadata.fiscal(vBC / vNF / vICMS / vPIS / vCOFINS / vTotTrib …),orderLines[].metadata.fiscal(ncm / cfop / csosn),lineTotals[].taxes[](ICMS, PIS, COFINS, IBS_*)
Tabla de referencia rápida
A medida que más países tengan un pipeline fiscal dedicado, sus eventos fiscales llegarán como
fiscal.*.{cc} (p. ej. fiscal.authorized.co, fiscal.authorized.ec). Hasta entonces, solo order.completed y order.cancelled disparan para tiendas no-BR — los bloques fiscales se quedan en null / vacíos.Handler de ejemplo
data.policy y data.lastKnown
Todos los eventos de orden llevan estos dos bloques, no solo este. Se agregaron
junto con el ciclo de pago diferido y son agregados en v1.1, aditivos: los consumidores
existentes siguen funcionando sin cambios.
order.opened,
el evento donde estos bloques más importan.
Errores comunes
- Decimales como strings × 10000.
payments.totals[0].total === "229000"significa 22.9 BRL. Usa una librería decimal; nunca conparseFloat. - El casing es mixto en
payments.totals[]y partes depaymentMethods[]. Lee tantocurrencyCodecomocurrencyCodedefensivamente. El builder V4 transforma la mayor parte del snapshot pero pasa los objetos de payment sin cambios. fulfillment.deliverypuede estar presente incluso para servicios non-delivery con ceros placeholder. Ramifica siempre porfulfillment.service.code.clientpuede ser un placeholder “FINAL_CONSUMER” populado en BR — no esnull. TratagovIdType === "FINAL_CONSUMER"como anónimo para analítica.event.ides el ID de ejecución del flow, no el ID de la orden. Usaevent.idpara idempotencia (cambia por entrega), yorderIdcomo llave de negocio.- Routing multi-tenant. Usa
store.account.uid,store.vendor.uidystore.codepara enrutar al tenant correcto en tu sistema, aunque Fire ya da scope al flow de su lado.
Eventos relacionados
order.cancelled
Dispara cuando esta orden se cancela después.
order.invoiced
Solo Brasil — dispara cuando SEFAZ autoriza el documento fiscal de la orden.
data.fiscalRepresentation
La numeración fiscal que el punto de venta obtuvo antes de inyectar la orden:
cobra, pide los identificadores, imprime el comprobante y recién después inyecta.
Por eso viaja en la orden y no en un evento fiscal aparte — cuando la orden nace,
esto ya ocurrió.
La clave viaja siempre. Llega en null cuando no se intentó numerar
—agregadores, países sin representación fiscal, o comercios con la numeración
desactivada— y con el bloque cuando sí se intentó.
Que traiga bloque significa que se intentó numerar, no que se numeró:
numberingStatus dice cómo terminó el intento, y failure por qué cuando no
terminó bien.
Ramificá por valor, no por presencia de la clave:
lastKnown.fiscal, que es lo que sí se mueve.
Lo que sí lo reemplaza es un documento nuevo. El bloque lleva el documento
fiscal vigente de la orden. Mientras solo hubo uno, era siempre la factura;
cuando una anulación produce una nota de crédito, arriba queda la nota —
documentType dice cuál es— y la factura baja a history, entera y con sus
propios identificadores del ente. No se pierde: se mueve. compensates apunta a
ella por su número, así que la relación entre los dos queda explícita.
Cuando la numeración falla
Una venta puede cobrarse y quedarse sin comprobante fiscal. Ese caso también viaja, y hay que contemplarlo: los identificadores vienen ennull y el motivo
en failure.
failure.scope:
TECHNICAL— imprimí “en trámite” y seguí. Puede resolverse solo.FUNCTIONAL— hay un dato mal y reintentar no lo arregla. Requiere corrección.
lastKnown.fiscal.sourceEvent ahora informa la procedencia real. Antes se
deducía del estado, y un processing sembrado al inyectar se reportaba como
fiscal.callback sin que ningún callback hubiera ocurrido. Ahora ese caso dice
order.injected. Si ramificás por este campo, contemplá el valor nuevo.
