Skip to main content
Lo que el proveedor devuelve no se queda en la respuesta síncrona. Se adjunta a la orden y viaja en todos sus eventos, así que cada campo de la respuesta tiene un consumidor final que no es FIRE. Esta página existe para cerrar ese círculo: si estás implementando el endpoint, acá ves qué pasa con lo que devolvés.

El recorrido

El punto de venta numera antes de que la orden exista: cobra, pide los números, imprime, y recién después inyecta la venta. Al inyectar, FIRE busca la numeración de ese orderCode y la pega a la orden. Desde ahí viaja en data.fiscalRepresentation de order.opened, order.completed, order.invoiced, order.cancelled y order.reversed.
Los importes del evento no están en la misma escala que los del request de numeración.Y no es un detalle marginal: el evento es de donde sale la venta que emitís. La numeración te da los identificadores; los importes, las líneas y el comprador que declarás al ente los tomás de acá. Por eso este es el lugar donde la escala puede morder.Todo lo monetario de data.payments viaja como entero en string, multiplicado por 10.000 — es la escala con la que FIRE almacena, para hacer aritmética con enteros y no arrastrar error de punto flotante al sumar impuestos.Un ejemplo con Colombia — cada país viaja en su moneda, pero la escala es la misma:Dividí por 10.000 todo importe que tomes del evento antes de declararlo al ente. Para el hash del CUFE usá los del request de numeración, que son los mismos valores y ya vienen sin escalar.No es una inconsistencia del dato —es el mismo importe en dos convenciones— pero descubrirlo tarde cuesta caro: si no dividís, declarás 500.000.000 COP por una venta de 50.000 COP —diez mil veces el monto—, el documento queda bien formado y el ente lo acepta.

Campo por campo

Ecuador, que es el bloque document de /fiscal/ec/prekeys: Un ejemplo completo, con datos reales:
El bloque de arriba siempre tiene las mismas claves, en null las que no apliquen — es contra eso que programás. Lo que cambia por país vive adentro de countryData, y ahí viajan solo las claves del país que numeró: un comprobante ecuatoriano no lleva cufe, ni uno colombiano claveAcceso.

Los cuatro casos, completos

Estos son todos los estados que puede tener el bloque, y qué respuesta tuya los produce. El bloque de arriba siempre trae las mismas 12 claves: lo que cambia son los valores y el contenido de countryData.
Devolviste 2xx con document.
El integrador imprime y concilia. countryData trae el vocabulario del SRI y nada de otros países.
Devolviste no-2xx con retryable: false.
Es una venta cobrada sin comprobante fiscal. El integrador compensa de su lado y devuelve el comprobante por el callback. Reintentar no lo arregla: hay que corregir el dato.
Devolviste no-2xx con retryable: true.
Mismo bloque que el anterior; cambia el numberingStatus y el scope. No hay comprobante, pero puede haberlo: el canal reintenta con el mismo orderCode.
Hubo timeout o se cortó la conexión. Vos nunca producís este estado: decirlo implicaría haber contestado.
Es el estado más delicado, y el que más fácil se malinterpreta. No significa “no hay comprobante”: significa no sabemos. Pudiste haber numerado, consumido un secuencial y emitido el documento, y la respuesta se perdió volviendo.Un integrador que lo lea como “no hay comprobante” y compense emitiendo otro, declara la misma venta dos veces ante el ente. Con FAILED_RETRYABLE esa compensación es correcta; con PENDING es un error caro.Por eso tu deduplicación tiene que ser por orderCode: el reintento llega con el mismo orderCode y vos devolvés el mismo documento con reused: true, en vez de numerar otro. No esperes un header de idempotencia — no te mandamos ninguno.

Y el caso sin numeración

Cuando la venta no pasó por ningún proveedor —el comercio no factura, o el país no tiene gateway fiscal— el bloque entero viaja en null:
Nunca se omite la clave. El integrador ramifica por valor:
documentType hoy siempre es SALE_INVOICE en los eventos de la orden. CREDIT_NOTE existe en el contrato —lo produce operation: "CANCEL"— pero la numeración de la anulación todavía no se adjunta a la orden. Cuando se habilite, es el mismo bloque con documentType: "CREDIT_NOTE".

Tres campos que conviene entender bien

provider y metadata — dos bloques, dos destinos

Se parecen y no son lo mismo, así que viajan por separado: Ninguno de los dos lleva providerCode: ese es nuestro identificador de adaptador y ya viaja aparte, arriba del bloque.
provider.reference es la única razón por la que este bloque tiene forma. Cuando algo sale mal, es lo que el integrador te cita para que encuentres la operación en tus registros. Enterrado en una bolsa opaca —donde por contrato nadie debe programar— no cumplía esa función.
Lo que pongas en metadata llega al integrador sin tocar, en providerMetadata. No lo interpretamos, no lo validamos, no lo renombramos. Eso tiene dos caras:
Es el único campo de la respuesta que no controlamos. Si metés ahí algo sensible —una credencial, un identificador interno de tu infraestructura, un dato de otro cliente— lo estás publicando al integrador del punto de venta.
Y del otro lado: nadie debe programar contra sus claves. Está declarado opaco justamente para que puedas cambiarlo sin romper a nadie. Si un dato es lo bastante importante como para que el integrador ramifique por él, no va en metadata — va en el contrato.
No repitas ahí adentro lo que ya tiene su lugar. Mandar failure dentro de metadata, o el name del bloque de al lado, guarda el mismo hecho dos veces — y dos copias se desincronizan. Y no cambies la bolsa entre una emisión y su reintento idempotente: quien lea el evento va a ver que “cambió” algo que no cambió.

graphic — es lo único que no se puede derivar

Todo lo demás de la respuesta se puede reconstruir o componer. graphic no: el QR de la NFC-e brasileña es una URL firmada con un hash que solo puede construir el emisor. Si no llega, el comprobante se imprime sin QR. Viaja tal cual, sin transformar: FIRE se lo pasa al punto de venta, que lo renderiza con su propia librería. No generamos imágenes de este lado — el tamaño y la resolución dependen de la impresora, y eso solo lo sabe quien imprime.

failure — le habilita la compensación al otro extremo

Cuando la numeración falla, el error no se queda en un log: viaja en el evento. La venta se cobró igual y el integrador necesita saber que quedó sin comprobante fiscal. Con eso puede compensar de su lado y devolver el comprobante por el callback. Sin eso, una venta cobrada sin comprobante es indistinguible de una cuenta que no factura. Por eso failure.code tiene que ser estable y failure.message accionable: no los lee solo nuestro equipo de soporte, los lee el sistema del cliente final.

Lo que NO viaja a los eventos

Y el veredicto del ente, aparte

fiscalRepresentation son los números que se imprimieron, y no cambian nunca. Que existan no significa que el ente haya autorizado el comprobante. El veredicto llega después por tu callback y viaja en otro lugar del evento:
Son dos ciclos de vida distintos y el contrato los mantiene separados a propósito: uno es inmutable y el otro se actualiza.