Saltar al contenido principal

Conceptos de facturación

Lo que necesitas entender de la factura hondureña para leer bien las respuestas de la API. Si tu sistema imprime el comprobante, esta página te importa.

CAI y correlativo

El CAI (Código de Autorización de Impresión) lo emite el SAR y autoriza a tu empresa a emitir facturas dentro de un rango de números, con una fecha límite. El invoiceNumber que devuelve la API es el correlativo dentro de ese rango.

Ambos los asigna Contadito. Tú no los mandas ni los eliges.

Dos consecuencias que sí te afectan:

  • El rango se agota. Cuando eso pase, la emisión falla hasta que tu empresa registre un rango nuevo. No es un problema que puedas resolver reintentando.
  • Cada factura consume un número. Por eso existe la idempotencia obligatoria: un duplicado no se "borra".

ISV, exento y exonerado

Son tres cosas distintas y la API las devuelve por separado.

ConceptoCampoQué significa
GravadotaxBreakdown[].taxedAmountBase sobre la que sí se cobra ISV.
ExentoexemptedAmountEl bien o servicio, por su naturaleza, no está sujeto a ISV (medicinas, libros, canasta básica…). Depende del producto.
ExoneradoexoneratedAmountEl bien sí es gravado, pero el comprador tiene una dispensa. Depende del cliente, y por eso exige su RTN.

De ahí sale la regla de la API: una venta exonerada requiere clientId, y ese cliente debe tener legalId (RTN de 14 dígitos). Si no, obtienes EXTERNAL_INVOICE_EXONERATION_REQUIRES_CLIENT o EXTERNAL_INVOICE_EXONERATION_REQUIRES_RTN.

La Orden de Compra Exenta

La exoneración se declara con el campo purchaseOrderNumber del body. Aunque el nombre suena a la orden de compra del cliente, no lo es: una Orden de Compra Exenta es un documento legal emitido con su propio correlativo a favor de quien tiene una exoneración de ISV vigente.

Dos consecuencias que importan:

  • Manda un purchaseOrderNumber y la factura completa queda exonerada: el motor de totales quita todo el impuesto en cuanto el campo viene con cualquier contenido. El SAR exige que ese documento identifique al comprador por RTN, así que sin un cliente con RTN válido la solicitud se rechaza.
  • No lo uses para guardar la referencia de compra de tu cliente. Ese número no se registra para eso, y meterlo ahí exonera la factura por error — con el riesgo de que el SAR cobre el impuesto retroactivamente.

El desglose de impuestos

"taxBreakdown": [
{ "rate": 15, "taxAmount": 300, "taxedAmount": 2000 },
{ "rate": 18, "taxAmount": 90, "taxedAmount": 500 }
]

Honduras tiene más de una tasa de ISV (15 % general, 18 % para ciertos bienes). Una factura con productos de tasas distintas trae una entrada por tasa, y el comprobante impreso debe mostrarlas desglosadas.

No asumas que el arreglo trae un solo elemento, ni que rate siempre es 15.

Cómo se arma el total

importe = Σ (unitPrice × quantity) de cada línea, antes de descuento
− discountTotal = descuentos aplicados
= base
de esa base:
exemptedAmount → no paga ISV (por el producto)
exoneratedAmount → no paga ISV (por el cliente)
resto → gravado, genera totalTaxes
+ totalTaxes
= total
  • total es lo que el cliente paga. Es el número que cobras.
  • totalInLetters es el total escrito en palabras, obligatorio en el comprobante. Ya te viene resuelto: no lo generes tú.

Precios: catálogo por defecto, negociado si lo mandas

Si mandas solo externalProductId y quantity, el unitPrice sale de la lista de precios configurada para tu empresa. Es el caso normal y el recomendado: el precio vive en un solo lugar.

Cuando una venta se cierra a un precio distinto —un descuento pactado, una tarifa de contrato— puedes mandarlo en la línea con items[].price. Ese precio aplica solo a esa factura: no crea ni modifica nada en el catálogo.

Lo que no puedes mandar es el impuesto. La tasa de ISV y la condición de exento salen siempre del producto en el catálogo de Contadito, mandes o no un precio. Si la tasa que ves no es la que esperabas, se corrige en el catálogo, no en tu solicitud.

Moneda y tasa de cambio

Un producto puede estar denominado en dólares. En ese caso price también va en dólares —siempre en la moneda del producto— y se convierte a lempiras con la misma tasa de cambio que se le habría aplicado al precio de catálogo. La previsualización te devuelve esa tasa en appliedDollarExchangeRate; en una factura ya emitida, unitPrice viene ya convertido.

El caso que sorprende: productos parcialmente gravados

Hay productos cuyo precio se divide en una porción exenta y una gravada. Ese monto exento por unidad está definido en el catálogo y no se reescala con el precio que mandes: si bajas el precio, la porción exenta se queda igual y la gravada absorbe la diferencia completa.

Si facturas productos así con precio negociado, verifica el resultado con POST /v1/invoices/preview antes de emitir.

Contado, no crédito

En v1 toda factura se emite pagada: isPaid siempre viene en true. Mandar isCredit: true se rechaza con 422 EXTERNAL_INVOICE_CREDIT_NOT_SUPPORTED.

El campo existe hoy solo para que agregar crédito en v2 no sea un cambio incompatible. Ver Límites de la v1.

Anulación

isAnnulled te dice si la factura fue anulada. En v1 no hay endpoint para anular: se hace desde la app de Contadito. Tu integración puede leer el campo, pero no cambiarlo.