rut: RUT emisor (12 dígitos)branchOffice: número de sucursal DGI
Antes de poder emitir
Tres cosas tienen que existir antes de emitir. Si falta la sucursal o el certificado, falla el request entero, antes de crear ningún comprobante. Si falta el CAE de un tipo, falla por comprobante, dentro del array de respuesta.
La sucursal la crea pymo en el alta de la empresa. El certificado y los CAE se
cargan por API, con
POST /v1/companies/{rut}/certs y
POST /v1/companies/{rut}/cfesActiveNumbers/{code}/upload-xml. Cargar un CAE no
cambia nada en DGI: registra en pymo una numeración que DGI ya autorizó.
El CAE es por tipo, no por empresa: tener CAE de eFactura (111) no habilita
emitir eTickets (101). Sirve uno con active: true y expireDate en el
futuro, evaluado contra la hora de Uruguay.
Cada emisión consume un número del rango (range.first → range.last) de forma
atómica. Cuando el rango se agota el CAE se desactiva solo, y a partir de ahí ese
tipo responde DGI_MISSING_CAE hasta que se cargue uno nuevo. Hay un aviso
previo configurable por porcentaje de rango restante, así que no debería llegarse
al final por sorpresa.
DGI_MISSING_CAE es de los pocos errores recuperables sin cambiar el request:
cargado el CAE, reenviar el mismo clientEmissionId emite (ver
Idempotencia, sección del CAE faltante).
Qué se manda
No hay una clavecfes. Cada clave del body es el código de tipo de CFE y su
valor es la lista de comprobantes de ese tipo, así que un mismo request puede
llevar eFacturas y eTickets a la vez. Las únicas claves que no son un tipo son
emailsToNotify y phonesToNotify; cualquier otra se ignora en silencio.
clientEmissionId, IdDoc, Receptor,
Totales e Items con al menos una línea. La estructura completa, campo por
campo, está en la Referencia de la API bajo el esquema
SolicitudEmision.
Los nombres de los campos son los de DGI, no los del gateway, y las reglas de
cada uno son las del documento Formato CFE de DGI. Acá se documenta la estructura y
lo que el gateway hace con ella; lo que no está acá se resuelve contra ese
documento, no adivinando.
Dos que conviene tener claros:
IdDoc.SerieyIdDoc.Nrosólo se mandan para los tipos que admiten serie y número propios. En el resto los asigna el gateway desde el CAE, y mandarlos no hace nada.Receptor.DocRecepcon dígito verificador inválido no falla en la emisión. El gateway firma y respondeSUCCESS; DGI rechaza el sobre después, de forma asíncrona. Es el caso más fácil de confundir con un éxito.
Cómo responde
El flujo:POST sendCfes→ el gateway asigna CAE, firma y guarda el CFE → respondeDGI_CFES_RECEIVE_SUCCESScon los datos del comprobante.- El gateway envía el sobre a DGI, de forma asíncrona.
- El estado final (
PROCESSED_ACCEPTED/PROCESSED_REJECTED) se consulta porGET .../sentCfeso llega por el webhookCFE_STATUS_CHANGE.
Respuesta de éxito
message.code = DGI_CFES_RECEIVE_SUCCESS. El payload trae cfesIds, un array
con un objeto por CFE creado. Campos exactos que devuelve el servicio:
Campos a persistir
Todos los de arriba, y en particular:Lotes: éxito parcial
Un request puede llevar varios comprobantes y cada uno se resuelve por separado. El arraycfesIds es heterogéneo: los que salieron traen los datos
del comprobante, los que fallaron traen un error, en el mismo array y sin nada
que los separe.
Una entrada fallida tiene esta forma:
- Una entrada con
status: "FAIL"es un error. Una conserieynroes un comprobante emitido. - No te fíes del orden. Para saber a cuál de tus comprobantes corresponde,
usá
clientEmissionId: está enreceivedDataWithError.clientEmissionIden el error y en el nivel superior en el éxito. receivedDataWithError.cfetrae el comprobante como lo interpretó el gateway, con los totales ya calculados. Sirve para ver qué entendió de lo que mandaste.
Errores
Códigos de error de la emisión:El rechazo de DGI (
PROCESSED_REJECTED) no viene en la respuesta del POST:
llega asíncrono. En GET .../sentCfes, el motivo viaja en
cfeHistory[].data.digestAck:Estados del CFE
El estado vive enactualCfeStatus, y el historial completo con sus fechas en
cfeHistory[]. Se consulta con GET .../sentCfes o llega por el webhook
CFE_STATUS_CHANGE.
Son diecisiete valores, no cuatro. La distinción que importa al escribir el
polling es si el estado puede cambiar solo: si es transitorio hay que seguir
consultando, si es final no.
Transitorios: seguí consultando
Finales: el comprobante ya no se mueve
FAKE_CFES_HOMOLOGATION existe para el proceso de homologación y no aparece en
operación normal.
Corregir o anular un comprobante ya emitido
No se puede borrar ni editar un CFE emitido. Se emite otro comprobante que lo referencia: una nota de crédito para anular o descontar, una nota de débito para agregar. Ambas son comprobantes fiscales por derecho propio, consumen su propia numeración y también se informan a DGI.
La sucursal no se manda: se infiere del comprobante referenciado. El tipo de la
nota tampoco: sale del tipo del original (una NC de una eFactura
111 es 112),
y si ese tipo no admite notas el gateway responde que no las admite en lugar de
inventar una.
Cuando no se mandan items, el gateway construye la línea a partir de la primera
del original, heredando IndFact, UniMed y NomItem. Si no puede inferirlos,
falla en lugar de adivinar.