Ir al contenido

Tirilla en un punto de venta

Un punto de venta emite la factura, espera el resultado e imprime una tirilla en papel térmico. Esta guía dice qué pedir, qué hacer con cada respuesta y qué va en la tirilla. Está pensada también para el punto de venta sin operador (una terminal de autoservicio): ahí nadie atiende un error, así que cada rama debe terminar sola.

El código de este flujo, con sus pruebas, está en la receta de punto de venta.

  • Vendes al detal y entregas la factura impresa en el momento.
  • Casi todas tus ventas son a consumidor final.
  • Armas tu propia tirilla (32 o 42 columnas). El PDF de foliios es tamaño carta: sirve para el correo, no para la impresora térmica.

Si solo necesitas emitir y enviar por correo, basta con emitir una factura.

  1. Guarda la venta con su Idempotency-Key antes de llamar. Una clave por venta, derivada de la venta (por ejemplo venta-1042). Si el programa se reinicia, repite la misma solicitud con la misma clave (idempotencia).
  2. Emite con ?wait=N explícito. POST /v1/companies/{company_id}/documents?wait=20. Sin wait aplica el valor por defecto del ambiente, que puede ser 0: en ese caso la respuesta es siempre 202, sin print. No dependas del valor por defecto.
  3. Usa un tiempo de espera HTTP mayor que wait. Con wait=20, 30 segundos. El tope de wait es 23.
  4. Solo la primera solicitud espera. Cualquier repetición va con la misma clave, el mismo cuerpo y ?wait=0, y después se consulta con GET.
  5. Decide por el código de la respuesta, con esta tabla.
Respuesta Qué pasó Qué haces Qué imprimes
201, accepted La DIAN validó la factura. Nada más. Tirilla completa.
201, contingency La DIAN no respondió y la factura se expidió en contingencia. Nada más. Tirilla completa, con la leyenda de contingencia.
200, rejected o failed No hay factura válida. Avisa a una persona: corregir y reenviar. Nada que parezca una factura.
202 El documento existe y sigue en proceso. Consulta con GET la URL de Location, tras Retry-After. No emitas de nuevo. Lo que diga el GET; si se acaba tu plazo, comprobante provisional.
202 con outcome_code: dian_outcome_uncertain La DIAN puede tener el documento. Lo mismo que cualquier 202. Nunca otra Idempotency-Key. Igual que 202.
429 con limit: sync_open Todos los cupos de espera de tu empresa están ocupados. No se creó nada. Repite de inmediato con la misma clave y ?wait=0; luego consulta con GET. Lo que diga el GET.
Otro 429, o 503 Límite de frecuencia o servicio no disponible. Espera Retry-After y repite con la misma clave y ?wait=0. Si la espera no cabe en tu plazo, comprobante provisional.
Sin respuesta (red, tiempo agotado) No sabes si llegó. Repite con la misma clave y ?wait=0. Si sigue sin respuesta, comprobante provisional.
400, 401, 403, 409, 422 La solicitud se rechazó. No hay número ni documento. Avisa a una persona. No reintentes sin corregir. Nada que parezca una factura.

Un 202 y un 429 no son errores de la venta. Significan «todavía no lo sé: consulta». Con 429 no hay un id que consultar; por eso se repite el POST con la misma clave y ?wait=0, que no ocupa cupo y devuelve el documento, nuevo o ya existente.

POST ?wait=20 (Idempotency-Key de la venta)
├─ 201 ─ print presente ──────────────► tirilla completa
│ print null ──────────────────► GET otra vez; si sigue null, provisional
├─ 200 (rejected, failed) ────────────► nada; avisar a una persona
├─ 202 ───────────────────────────────► GET hasta estado final o fin del plazo
│ ├─ accepted / contingency ───► tirilla completa
│ ├─ rejected / failed ────────► nada; avisar a una persona
│ └─ fin del plazo ────────────► provisional; seguir consultando después
├─ 429 sync_open ─────────────────────► POST ?wait=0, misma clave ─► como 202
├─ 429 / 503 / sin respuesta ─────────► esperar; POST ?wait=0, misma clave
│ └─ fin del plazo ────────────► provisional; repetir después
└─ 400 / 401 / 403 / 409 / 422 ───────► nada; avisar a una persona

«Fin del plazo» es el tiempo que decides que el comprador puede esperar frente a la máquina. La receta usa 45 segundos.

Cuando el plazo se acaba sin un estado final, no hay factura que imprimir todavía. Imprime un comprobante que diga claramente que no es la factura electrónica de venta, con la referencia de tu venta y cómo pedirla después.

  • No imprimas CUFE, QR ni leyenda de validación: el documento no los tiene.
  • Usa tu referencia de venta, no el number del documento: si la DIAN lo rechaza, ese número puede pasar a otro documento (numeración).
  • Deja la venta en una cola propia. Un proceso de fondo repite la misma solicitud (misma clave, mismo cuerpo, ?wait=0) o consulta con GET hasta un estado final; con webhooks no hace falta consultar en bucle.
  • Cuando quede accepted, la tirilla se puede reimprimir o enviar por correo si el comprador lo dejó.

Qué entregar al comprador mientras tanto es una decisión de tu negocio. foliios solo te dice que la factura aún no existe.

Con 200 (rejected, failed) o un 4xx, la venta ocurrió pero no hay factura. En un punto de venta sin operador nadie lo verá si no avisas:

  • Registra la venta como pendiente de factura y alerta a una persona.
  • Un rejected se corrige y reenvía con el mismo número. La cola de errores es GET .../documents?errors_only=true.
  • Un failed suele ser de configuración: mira dian.failure_code y links.retry.
  • Un 422 es un cuerpo inválido: repetirlo igual falla igual.

Un documento accepted o en contingency trae print. Ejemplo de una factura validada (datos ficticios):

{
"id": "01JD3XEJEMPLO00000000000000",
"environment": "live",
"type": "invoice",
"number": "KA1042",
"status": "accepted",
"issued_at": "2026-11-11T14:32:07-05:00",
"validated_at": "2026-11-11T14:32:09-05:00",
"customer": {"name": "consumidor final", "identification": {"type": "citizenship_card", "number": "222222222222"}},
"currency": "COP",
"totals": {"line_extension_amount": "4000.00", "tax_exclusive_amount": "4000.00", "tax_inclusive_amount": "4760.00", "payable_amount": "4760.00"},
"print": {
"cufe": "9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c",
"qr_text": "NumFac: KA1042\nFecFac: 2026-11-11\nHorFac: 14:32:07-05:00\nNitFac: 900000001\nDocAdq: 222222222222\nValFac: 4000.00\nValIva: 760.00\nValOtroIm: 0.00\nValTolFac: 4760.00\nCUFE: 9f3c9f3c…9f3c\nhttps://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=9f3c9f3c…9f3c",
"qr_url": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=9f3c9f3c…9f3c",
"validated_at": "2026-11-11T14:32:09-05:00",
"status_label": "Documento validado por la DIAN el 11/11/2026 a las 14:32:09",
"software": {
"name": "Caja Ejemplo",
"manufacturer_name": "Software de Ejemplo S.A.S.",
"manufacturer_id": "900000002-7",
"software_id": "56f2ae4e-9812-4fad-9255-08fcfcd5ccb0"
},
"numbering_authorization": {
"number": "18760000001",
"prefix": "KA",
"from": 1,
"to": 50000,
"valid_from": "2026-01-01",
"valid_to": "2027-12-31"
}
}
}

El documento trae más campos; aquí están los que usa la tirilla. En el ejemplo el CUFE se acorta con … dentro de qr_text y qr_url; en la respuesta real va completo.

Situación print
accepted o contingency Presente. Es el caso normal y la tirilla depende de él.
pending, sending, dian_unavailable, rejected, failed Siempre null: no hay nada que imprimir.
accepted o contingency, pero no se pudo armar null. Es una anomalía, no un estado: el documento se lee igual, con su status.

Para el tercer caso: vuelve a leer con GET. Si sigue null, trata la venta como provisional y contacta a soporte. No armes la tirilla solo con number y cufe: le faltarían el QR, la leyenda y el software.

print se arma con los mismos datos que la representación gráfica en PDF del documento: el mismo texto del QR, la misma leyenda y el mismo software y fabricante. El software y el fabricante quedan fijos cuando el documento se emite. Lo que tu tirilla dice con print coincide con el PDF que recibiría el comprador por correo.

print no trae todo lo que lleva el PDF. Esto lo pone tu punto de venta, porque ya lo tiene:

  • Los datos del emisor: razón social, NIT, responsabilidades fiscales, dirección.
  • Las líneas de la venta y la discriminación de impuestos por tarifa.
  • La forma y el medio de pago.
  • Si el software está registrado ante la DIAN con el NIT de un proveedor tecnológico distinto del emisor, el PDF agrega «Proveedor tecnológico: NIT …». print no trae ese NIT.
  • En habilitación (environment: test) el PDF dice «Habilitación DIAN: sin efecto fiscal». print no trae esa leyenda: mira environment.

Estos son los datos de la tirilla del ejemplo de abajo y de dónde sale cada uno.

Dato De dónde sale
La denominación «factura electrónica de venta». Texto fijo, según type.
Nombre o razón social y NIT del vendedor. Tu configuración. La respuesta no lo trae.
Identificación del adquirente, o consumidor final. customer.name y customer.identification.number.
Número consecutivo y la autorización de numeración: número, rango y vigencia. number y print.numbering_authorization. El prefix puede venir vacío.
Fecha y hora de generación. issued_at (hora de Colombia).
Cantidad y descripción de lo vendido. Tu venta: las lines que enviaste.
Valor total de la venta. totals.payable_amount.
Forma de pago. Tu venta: payment.form (por defecto, de contado).
Medio de pago. Tu venta: payment.means (por defecto, efectivo).
Calidad de retenedor, autorretenedor, gran contribuyente o régimen simple. Tu configuración: las responsabilidades fiscales del emisor.
Discriminación del IVA y de los demás impuestos, con su tarifa. Tu venta: los taxes de cada línea. totals da la base (tax_exclusive_amount) y el total con impuestos (tax_inclusive_amount).
El CUFE. print.cufe.
La dirección de internet de la DIAN donde se consulta la factura, contenida en el QR. print.qr_text en el QR; print.qr_url es esa dirección.
Nombre y NIT del fabricante del software y nombre del software. print.software.name, manufacturer_name y manufacturer_id.

Imprime también la leyenda de validación: print.status_label ya la trae con su fecha y hora, y es lo que el PDF muestra.

Detalles que conviene respetar:

  • manufacturer_id puede ser null. La clave está siempre. Si es null, imprime el fabricante sin NIT, como hace el PDF: «Software: … · Fabricante: …».
  • software_id no hace falta en la tirilla. Es el identificador que la DIAN asignó al software; el PDF tampoco lo imprime.
  • Las notas (credit_note, debit_note) no traen numbering_authorization y su código es un CUDE.
  • Fuera de producción, dilo en la tirilla. En el sandbox status_label y qr_text ya dicen que el documento es simulado; en habilitación agrega tu propia leyenda «sin validez fiscal».
FACTURA ELECTRÓNICA DE VENTA
Tienda de Ejemplo S.A.S.
NIT 900000001-9
Responsable de IVA
Calle 1 # 2-3, Ciudad Ejemplo
------------------------------------------
No. KA1042
Generación 2026-11-11 14:32:07
Adquirente: consumidor final 222222222222
------------------------------------------
1 x Cuaderno argollado
4000.00
------------------------------------------
IVA 19% base 4000.00 760.00
Subtotal 4000.00
TOTAL COP 4760.00
Pago: Contado · Efectivo
[ código QR ]
Documento validado por la DIAN el
11/11/2026 a las 14:32:09
CUFE: 9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c
9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f3c9f
3c9f3c9f3c9f3c9f3c
Consulte en catalogo-vpfe.dian.gov.co
Autorización de numeración DIAN
18760000001, prefijo KA del 1 al 50000,
vigente del 2026-01-01 al 2027-12-31
Software: Caja Ejemplo · Fabricante:
Software de Ejemplo S.A.S., NIT
900000002-7

En 32 columnas es igual, con más saltos de línea. Parte el CUFE en varias líneas: son 96 caracteres.

  • Codifica print.qr_text tal cual, con sus saltos de línea. No lo armes tú ni codifiques solo la URL. Es el mismo texto del QR del PDF: número, fecha y hora, NIT del emisor, documento del adquirente, valores, el CUFE y, al final, la URL de consulta.
  • print.qr_url es la última línea de ese texto: la página de consulta de la DIAN para ese CUFE. Úsala para un enlace o para imprimir el dominio junto al QR. La cámara de muchos teléfonos lee un QR de varias líneas pero no lo abre como enlace; por eso conviene la línea «Consulte en catalogo-vpfe.dian.gov.co» y el CUFE impreso.
  • Tamaño. qr_text es largo: elige un tamaño de módulo que tu impresora resuelva bien y prueba la lectura con un teléfono.
  • En el sandbox qr_text solo trae el aviso de documento simulado y el código, y qr_url apunta al catálogo de habilitación, donde ese documento no existe.
  • Omite customer y la factura se emite al consumidor final de la DIAN (222222222222). No pidas datos al comprador para esto.
  • Sin correo no se envía nada. La entrega queda delivery.status: not_applicable y la emisión no falla. El comprador recibe la tirilla impresa.
  • El correo es opcional. La emisión no lo exige.
  • Si el comprador deja un correo, cuando la factura quede accepted o en contingency envíala con POST .../documents/{document_id}/deliver y {"email": "..."} (reenviar). No es idempotente: cada llamada es un correo más.
  • Si pide la factura a su nombre, envía customer con su identification (y emails si lo da) al emitir (emitir una factura).

Si la DIAN no responde y se agotan los reintentos dentro de tu espera, la respuesta es 201 con status: contingency. Es una factura expedida sin validación previa (tipo 04), no un error (contingencia).

  • Imprime la tirilla completa. print.validated_at es null y print.status_label dice «Expedida en contingencia DIAN, sin validación previa». Imprime esa leyenda tal cual; no escribas «validado por la DIAN».
  • El CUFE y el QR ya son los definitivos. foliios transmite después ese mismo documento firmado, con el mismo CUFE, hasta que la DIAN lo acepte.
  • Tú no haces nada más. No reenvíes ni emitas de nuevo. Cuando pase a accepted, print traerá validated_at y la leyenda de validación; el comprador no necesita otra tirilla.
  • foliios no adelanta la contingencia para cumplir tu espera. Si los reintentos duran más que wait, recibes 202 y la contingencia llega por el GET o el webhook.

Si lo que falla es tu conexión o tu sistema y no puedes llegar a foliios, es otro caso: la contingencia del facturador (factura de papel). Cómo opera ahí un punto de venta sin operador es una decisión de tu negocio.

  • Cupo de esperas. Cada empresa tiene 5 emisiones síncronas abiertas a la vez por ambiente (2 en el sandbox). Cada solicitud que espera ocupa una mientras espera (límites).
  • Con el cupo lleno, la siguiente responde 429 concurrency_limited con limit: sync_open y Retry-After (hasta 25 segundos), sin crear nada. No esperes ese tiempo frente al comprador: repite con ?wait=0.
  • Una repetición que espera también ocupa cupo. Por eso las repeticiones van con ?wait=0.
  • Si tienes más de 5 puntos de venta de una misma empresa que pueden vender a la vez, pídele a foliios que ajuste el cupo para tu empresa. Con 5 exactos no hay margen.
  • Las consultas cuentan como lecturas (300 por minuto por empresa). Respeta Retry-After entre consultas.

GET /v1/companies/{company_id}/documents/{document_id} devuelve el mismo print cada vez: mismo CUFE, mismo QR, mismo software y fabricante, aunque tu empresa cambie después su configuración. Guarda el id del documento con la venta y reimprime desde ahí.

Solo cambia una cosa: un documento que estaba en contingencia y ya fue validado trae validated_at y la leyenda de validación en lugar de la de contingencia.

  • La Idempotency-Key sale de la venta y se guarda antes del primer POST.
  • El POST lleva ?wait=N explícito y el cliente HTTP espera más que N.
  • Las repeticiones usan la misma clave, el mismo cuerpo y ?wait=0.
  • 202 y 429 terminan en una consulta, no en un error de venta ni en otra clave.
  • dian_outcome_uncertain nunca genera una clave nueva.
  • 200 y 4xx no imprimen nada que parezca una factura y avisan a una persona.
  • Hay un comprobante provisional, con la leyenda «no es la factura», y una cola que sigue la venta hasta un estado final.
  • La tirilla toma de print el CUFE, el QR (qr_text tal cual), la leyenda, la autorización y el software, y tolera manufacturer_id: null.
  • La tirilla lleva los datos del emisor, las líneas, los impuestos y el pago que pone tu sistema.
  • La contingencia imprime su leyenda y no dice «validado».
  • Un print: null en un documento aceptado se vuelve a leer y, si persiste, se trata como provisional.
  • El QR impreso se lee con un teléfono.
  • Probaste en el sandbox los escenarios dian_unavailable y un rechazo (escenarios).
  • El contenido de la tirilla lo revisó tu asesor.