Skip to main content
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 emite order.completed exactamente una vez por orden, la primera vez que ambos son verdaderos en momento de inyección:
  • order.status === "COMPLETED"
  • order.paymentStatus === "SUCCEEDED"
Las órdenes que siguen en 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 de flow_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.
No es la escala de todos los caminos. El request de numeración fiscal lleva los importes sin escalar, tal como se cobraron. Si tomás un importe de este evento para declararlo al ente, dividí primero.

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.
Además, metadata por impuesto vive dentro de cada 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 de taxes[]: 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 eventos order.invoiced / order.reversed solo aplican a BR. Los demás países igual llevan su taxes[], pero estos bloques SEFAZ están ausentes.
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.
El detalle campo por campo está en 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 con parseFloat.
  • El casing es mixto en payments.totals[] y partes de paymentMethods[]. Lee tanto currencyCode como currencyCode defensivamente. El builder V4 transforma la mayor parte del snapshot pero pasa los objetos de payment sin cambios.
  • fulfillment.delivery puede estar presente incluso para servicios non-delivery con ceros placeholder. Ramifica siempre por fulfillment.service.code.
  • client puede ser un placeholder “FINAL_CONSUMER” populado en BR — no es null. Trata govIdType === "FINAL_CONSUMER" como anónimo para analítica.
  • event.id es el ID de ejecución del flow, no el ID de la orden. Usa event.id para idempotencia (cambia por entrega), y orderId como llave de negocio.
  • Routing multi-tenant. Usa store.account.uid, store.vendor.uid y store.code para 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ó.
Que este bloque exista NO significa que el comprobante esté autorizado. Son los números que se imprimieron en la caja; el veredicto del ente lo da lastKnown.fiscal.status. Un ticket que diga “autorizado” solo porque el bloque está presente declara algo que puede no haber pasado.
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:
El veredicto del ente no lo altera. Lo que el cliente se llevó impreso no cambia porque el organismo después autorice o rechace; para eso está 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 en null y el motivo en failure.
Ramificá por 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.