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.
| Concepto | Campo | Qué significa |
|---|---|---|
| Gravado | taxBreakdown[].taxedAmount | Base sobre la que sí se cobra ISV. |
| Exento | exemptedAmount | El bien o servicio, por su naturaleza, no está sujeto a ISV (medicinas, libros, canasta básica…). Depende del producto. |
| Exonerado | exoneratedAmount | El 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
purchaseOrderNumbery 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
totales lo que el cliente paga. Es el número que cobras.totalInLetterses 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.