Bandeja REST del cliente local
Referencia técnica para bandeja rest del cliente local dentro del cliente local.
Para quién es esta guía
Esta guía es el punto de partida para integrar con el cliente local distribuido.
Está pensada para desarrolladores que quieren:
- enviar XML a la bandeja REST local
- numerar y firmar CFE en el equipo del emisor
- operar aun cuando la aplicación principal no usa la API REST pública
- entender qué endpoints usar y en qué orden
Diferencia clave: cliente local vs API REST pública
La bandeja REST del cliente local no es la misma integración que la API REST online de CFE.
Diferencias operativas importantes:
- la API REST pública vive en el servidor y trabaja sobre internet
- la bandeja REST local vive en el equipo donde corre el cliente local
- la API pública expone endpoints como
/api/v1/comprobante/emitir - la bandeja local expone endpoints por loopback como
/sign-cfe,/enqueuey/validar-xml - la API pública trabaja principalmente con JSON de negocio
- la bandeja local trabaja principalmente con XML CFE listo para validar, numerar y firmar
- la autenticación y permisos de la API pública no equivalen a los de la bandeja local
Si un integrador ya consume POST /api/v1/comprobante/emitir, no debe asumir que puede apuntar el mismo request al cliente local. Son contratos distintos y resuelven problemas distintos.
URL base
Base URL por defecto:
http://127.0.0.1:18787
Reglas prácticas:
- responde por loopback
- el puerto puede variar por configuración
- el canal REST puede estar deshabilitado por perfil
- el sistema integrador debe tratar esta URL como una dependencia local, no como una API SaaS
Qué XML hay que enviar
La bandeja REST local espera un XML CFE base, sin firma, para que el módulo local complete el proceso.
En el flujo normal:
- el integrador arma el XML
- el cliente local valida el XML
- el cliente local asigna
SerieyNro - el cliente local agrega
CAEData - el cliente local firma el comprobante
- luego lo deja pronto para envío o lo encola según la operación usada
En otras palabras, el XML de entrada no debería venir ya firmado.
Tampoco conviene depender de una numeración definitiva armada por el integrador para los tipos normales. El módulo local es quien controla el folio local y el lease CAE.
Requisitos mínimos del XML
El XML debe cumplir estas condiciones:
- respetar la estructura CFE de DGI
- usar el namespace
http://cfe.dgi.gub.uy - incluir
Encabezado,IdDoc,Emisor,TotalesyDetallecuando el tipo lo requiera - informar un
RUCEmisorque coincida con el emisor configurado en el cliente local - ser válido contra los XSD que el runtime del cliente local tiene empaquetados
Para tipos normales, el integrador debería considerar Serie, Nro y CAEData como datos controlados por el cliente local.
Referencia de XSD de DGI
La referencia formal del documento sigue siendo la definición de DGI. El integrador debería tomar como base los XSD y la documentación técnica oficial publicados por DGI.
Referencias útiles:
- portal de e-Factura DGI:
https://www.efactura.dgi.gub.uy/ - consulta QR DGI usada por el ecosistema CFE:
https://www.efactura.dgi.gub.uy/consultaQR/cfe
Para despliegue real, lo que manda en ejecución es el runtime local empaquetado. Si se actualizan XSD o assets de runtime, hay que redistribuir el bundle del cliente local.
Ejemplo de XML base
Ejemplo orientativo de un eTicket contado para enviar a la bandeja REST local:
<?xml version="1.0" encoding="UTF-8"?>
<CFE xmlns="http://cfe.dgi.gub.uy" version="1.0">
<eTck>
<Encabezado>
<IdDoc>
<TipoCFE>101</TipoCFE>
<FchEmis>2026-04-16</FchEmis>
<MntBruto>1</MntBruto>
<FmaPago>1</FmaPago>
</IdDoc>
<Emisor>
<RUCEmisor>211111110019</RUCEmisor>
<RznSoc>Empresa Demo SA</RznSoc>
<NomComercial>Empresa Demo</NomComercial>
<GiroEmis>Servicios</GiroEmis>
<Telefono>22000000</Telefono>
<CorreoEmisor>facturacion@empresa.test</CorreoEmisor>
<EmiSucursal>Casa Central</EmiSucursal>
<CdgDGISucur>1</CdgDGISucur>
<DomFiscal>Av. Demo 1234</DomFiscal>
<Ciudad>Montevideo</Ciudad>
<Departamento>Montevideo</Departamento>
</Emisor>
<Totales>
<TpoMoneda>UYU</TpoMoneda>
<MntNoGrv>0.00</MntNoGrv>
<MntNetoIvaTasaBasica>100.00</MntNetoIvaTasaBasica>
<TasaBasica>22.00</TasaBasica>
<MntIVATasaBasica>22.00</MntIVATasaBasica>
<MntTotal>122.00</MntTotal>
<CantLinDet>1</CantLinDet>
</Totales>
</Encabezado>
<Detalle>
<Item>
<NroLinDet>1</NroLinDet>
<IndFact>3</IndFact>
<NomItem>Servicio mensual</NomItem>
<Cantidad>1.000</Cantidad>
<UniMed>NIU</UniMed>
<PrecioUnitario>100.00</PrecioUnitario>
<MontoItem>100.00</MontoItem>
</Item>
</Detalle>
</eTck>
</CFE>
Notas sobre el ejemplo:
- es un ejemplo de arranque, no una matriz completa por tipo de CFE
- los campos obligatorios exactos dependen del tipo de comprobante y del caso fiscal
- el XML final firmado no es este mismo; el cliente local agregará numeración, CAE y firma
Secuencia recomendada para empezar a integrar
Orden recomendado para una primera integración estable:
- confirmar que el cliente local responde en
GET /health - validar el XML con
POST /validar-xml - consultar folio local con
GET /proximo-serie-nrosi la UI necesita previsualizar serie o número - emitir síncrono con
POST /sign-cfeo asíncrono conPOST /enqueue - usar
POST /pdfoPOST /reprintsegún necesidad operativa
Operaciones disponibles en la bandeja REST local
| Método | Endpoint | Cuándo usarlo |
|---|---|---|
GET | /health | Verificación rápida de que el servicio está vivo |
GET | /state | Diagnóstico del estado local, drafts y cola |
POST | /validar-xml | Validar XML antes de consumir folios o firmar |
GET | /proximo-serie-nro | Obtener el próximo folio local esperado |
POST | /sign-cfe | Numerar y firmar con respuesta inmediata |
POST | /enqueue | Aceptar trabajo en forma asíncrona |
POST | /pdf | Generar PDF de un comprobante ya emitido |
POST | /reprint | Reimprimir un comprobante ya emitido |
POST | /existe-constancia | Verificar si una constancia existe localmente |
Ejemplos de invocación por endpoint
GET /health
Chequeo rápido para saber si el cliente local está corriendo:
curl http://127.0.0.1:18787/health
Respuesta esperable:
{
"ok": true,
"service": "modulo_local_daemon",
"rest_base_url": "http://127.0.0.1:18787",
"state": {
"running": true
}
}
GET /state
Sirve para diagnóstico y monitoreo del estado local:
curl http://127.0.0.1:18787/state
Respuesta esperable:
{
"ok": true,
"drafts": 1,
"outbox_pending": 2,
"leases": [
{
"tipo_cfe": 101,
"serie": "A",
"disponibles": 99
}
]
}
POST /validar-xml
Valida XML antes de consumir folios o intentar firmar:
curl -X POST http://127.0.0.1:18787/validar-xml \
-H 'Content-Type: application/json' \
-d '{
"tipo_cfe": 101,
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CFE xmlns=\"http://cfe.dgi.gub.uy\" version=\"1.0\"><eTck>...</eTck></CFE>",
"cod_comercio": "1",
"cod_terminal": "1"
}'
Respuesta esperable:
{
"ok": true,
"valido": true,
"issues": []
}
GET /proximo-serie-nro
Permite consultar el próximo folio local antes de emitir:
curl "http://127.0.0.1:18787/proximo-serie-nro?tipo_cfe=101&cod_comercio=1&cod_terminal=1"
Respuesta esperable:
{
"ok": true,
"tipo_cfe": 101,
"serie": "A",
"numero_siguiente": 301,
"numero_desde": 301,
"numero_hasta": 400,
"disponibles": 100,
"vencimiento_cae": "2028-01-01"
}
POST /sign-cfe
Numeración y firma con respuesta inmediata:
curl -X POST http://127.0.0.1:18787/sign-cfe \
-H 'Content-Type: application/json' \
-d '{
"tipo_cfe": 101,
"uuid": "venta-pos-000123",
"cod_comercio": "1",
"cod_terminal": "1",
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CFE xmlns=\"http://cfe.dgi.gub.uy\" version=\"1.0\"><eTck>...</eTck></CFE>",
"adenda": "Pago contado",
"emails": ["cliente@ejemplo.com"],
"send_now": false
}'
POST /enqueue
Acepta el trabajo en bandeja para procesamiento asíncrono:
curl -X POST http://127.0.0.1:18787/enqueue \
-H 'Content-Type: application/json' \
-d '{
"tipo_cfe": 101,
"uuid": "venta-pos-000124",
"cod_comercio": "1",
"cod_terminal": "1",
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CFE xmlns=\"http://cfe.dgi.gub.uy\" version=\"1.0\"><eTck>...</eTck></CFE>",
"send_now": true
}'
Respuesta esperable:
{
"ok": true,
"stored_path": "/ruta/local/inbox/20260416143000124-venta-pos-000124.json",
"uuid": "venta-pos-000124",
"send_now": true
}
POST /pdf
Genera el PDF de un comprobante ya emitido:
curl -X POST http://127.0.0.1:18787/pdf \
-H 'Content-Type: application/json' \
-d '{
"uuid": "venta-pos-000123",
"impresora": "pdf",
"variant": "personalizado",
"fallback_copies": 1,
"cod_comercio": "1",
"cod_terminal": "1"
}' \
--output comprobante.pdf
Respuesta esperable:
HTTP 200Content-Type: application/pdf- body binario
POST /reprint
Reimprime un comprobante ya emitido:
curl -X POST http://127.0.0.1:18787/reprint \
-H 'Content-Type: application/json' \
-d '{
"uuid": "venta-pos-000123",
"impresora": "CajaFiscal;FORMATO=personalizado;COPIAS=2",
"variant": "personalizado",
"fallback_copies": 2,
"cod_comercio": "1",
"cod_terminal": "1"
}'
Respuesta esperable:
{
"ok": true,
"queued": true
}
POST /existe-constancia
Verifica si el comprobante ya tiene constancia local:
curl -X POST http://127.0.0.1:18787/existe-constancia \
-H 'Content-Type: application/json' \
-d '{
"tipo_cfe": 101,
"serie": "A",
"numero": 301,
"cod_comercio": "1",
"cod_terminal": "1"
}'
Respuesta esperable:
{
"ok": true,
"existe": true,
"codigo_respuesta": "00",
"estado": "Accepted",
"uuid": "venta-pos-000123",
"tipo_cfe": 101,
"serie": "A",
"numero": 301
}
Cuándo usar sign-cfe y cuándo usar enqueue
Usar POST /sign-cfe cuando:
- el sistema necesita saber en la misma llamada si el CFE quedó firmado
- el integrador trabaja con respuesta inmediata
- la operación es interactiva y depende del resultado en pantalla
Usar POST /enqueue cuando:
- el integrador solo necesita dejar trabajo aceptado localmente
- el envío puede continuar por cola
- se quiere desacoplar la app de la firma y del envío inmediato
Ejemplo de validación previa
Request:
curl -X POST http://127.0.0.1:18787/validar-xml \
-H 'Content-Type: application/json' \
-d '{
"tipo_cfe": 101,
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CFE xmlns=\"http://cfe.dgi.gub.uy\" version=\"1.0\"><eTck>...</eTck></CFE>",
"cod_comercio": "1",
"cod_terminal": "1"
}'
Objetivo:
- detectar errores XSD o de validación lógica antes de firmar
- confirmar que el XML corresponde al emisor y al punto de emisión correctos
Ejemplo de emisión síncrona
Request:
curl -X POST http://127.0.0.1:18787/sign-cfe \
-H 'Content-Type: application/json' \
-d '{
"tipo_cfe": 101,
"uuid": "venta-pos-000123",
"cod_comercio": "1",
"cod_terminal": "1",
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CFE xmlns=\"http://cfe.dgi.gub.uy\" version=\"1.0\"><eTck>...</eTck></CFE>",
"adenda": "Pago contado",
"emails": ["cliente@ejemplo.com"],
"send_now": false
}'
Respuesta esperable de éxito:
{
"uuid": "venta-pos-000123",
"tipo_cfe": "101",
"serie": "A",
"numero": "301",
"codigo_respuesta": "00",
"mensaje_respuesta": "CFE firmado localmente",
"codigo_terminal": "1",
"codigo_comercio": "1",
"numero_inicial_cae": "301",
"numero_final_cae": "400",
"vencimiento_cae": "2028-01-01",
"cfe_firmado": "<CFE>...</CFE>",
"datos_codigo_qr": "https://...",
"codigo_seguridad": "ZmCpqT",
"fecha_firma_cfe": "2026-04-16T14:30:00Z",
"imagen_qr": null
}
Regla de interpretación:
- considerar éxito solo si HTTP
200 - y
codigo_respuesta = "00" - y
serie+numerovienen informados
Ejemplo de encolado asíncrono
Request:
curl -X POST http://127.0.0.1:18787/enqueue \
-H 'Content-Type: application/json' \
-d '{
"tipo_cfe": 101,
"uuid": "venta-pos-000124",
"cod_comercio": "1",
"cod_terminal": "1",
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CFE xmlns=\"http://cfe.dgi.gub.uy\" version=\"1.0\"><eTck>...</eTck></CFE>",
"send_now": true
}'
Qué significa esa respuesta:
- confirma recepción local del trabajo
- no garantiza que ya haya quedado emitido y aceptado
- el integrador debe tratarlo como aceptación de bandeja, no como éxito fiscal final
Buenas prácticas para el programador integrador
- usar un
uuidexterno estable por operación - validar XML antes de firmar
- registrar siempre
tipo_cfe,uuid,cod_comercioycod_terminal - separar el manejo de errores HTTP del manejo de
codigo_respuesta - no mezclar esta integración con la API REST pública del servidor
- probar primero con un tipo de CFE simple y un XML mínimo controlado
Qué leer después
Después de esta guía, el orden recomendado es: