Bandeja REST del cliente local
Referencia técnica para bandeja rest del cliente local dentro del cliente local.
Objetivo
Esta guía documenta la integración técnica con la bandeja REST del cliente local Tauri.
Está pensada para el programador que necesita:
- construir el XML del CFE
- enviarlo al cliente local para validación, numeración y firma
- operar impresión, PDF y reimpresión
- resolver reintentos, estados y consultas técnicas sin depender de la API REST pública
La intención es que esta página funcione como manual base del integrador del módulo local.
Alcance
Esta documentación cubre la integración por HTTP local con el servicio empaquetado del cliente local.
Cubre:
- proceso recomendado de integración
- reglas de armado del XML
- diferencias con la API REST pública
- URL base y despliegue esperado
- ruteo por punto de emisión
- catálogo de campos más usados
- operaciones disponibles
- ejemplos de invocación y de respuesta
No cubre:
- contratos internos de FFI
- detalles de persistencia SQLite
- implementación interna del daemon
- especificación completa de todos los tipos CFE de DGI
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 en PHP.
Diferencias importantes:
- la API REST pública corre en servidor y trabaja sobre internet
- la bandeja REST local corre en el equipo del emisor y responde por loopback
- la API REST pública expone endpoints como
/api/v1/comprobante/emitir - la bandeja local expone endpoints como
/sign-cfe,/enqueue,/validar-xml,/pdf - 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 API pública tiene autenticación y permisos propios del servidor
- la bandeja local depende de la seguridad operativa del equipo donde fue instalada
Consecuencia práctica:
si un integrador ya consume POST /api/v1/comprobante/emitir, no debe reutilizar ese mismo request contra el cliente local. Son superficies distintas y resuelven problemas distintos.
Modelo general de integración
El cliente local actúa como un motor de emisión cerca del punto de emisión.
El sistema integrador:
- construye el XML base del comprobante
- invoca la bandeja REST local
- obtiene una respuesta inmediata o deja trabajo en cola
- consume el XML firmado, el PDF o el estado operativo según la operación utilizada
En el flujo normal el cliente local resuelve:
- validación del XML
- control del emisor y del punto de emisión
- obtención y uso del rango CAE local
- asignación de
SerieyNro - armado de
CAEData - firma del CFE
- persistencia local
- cola y reintentos cuando aplica
Proceso recomendado para emisión
El proceso sugerido para una primera integración estable es el siguiente:
- levantar el cliente local y verificar
GET /health - asumir como prerrequisito de despliegue que el cliente local fue instalado con su runtime completo
- construir el XML base sin firma
- validar con
POST /validar-xml - emitir con
POST /sign-cfesi se necesita respuesta inmediata - o encolar con
POST /enqueuesi el flujo es asíncrono - usar
POST /pdfoPOST /reprintpara representación impresa posterior
Ese runtime es responsabilidad del despliegue del cliente local, no del integrador funcional que consume la bandeja REST.
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
Seguridad y despliegue
La bandeja REST local está pensada para exponer su superficie en el propio equipo del emisor.
Buenas prácticas operativas:
- mantener la escucha en
127.0.0.1 - no publicar el puerto hacia la red salvo que exista una capa de control adicional
- no dejar REST activo si el entorno no controla quién puede consumir loopback o la red local
- registrar
uuid,tipo_cfe,cod_comercioycod_terminalen cada llamada de emisión
La documentación actual del cliente local no define una autenticación propia equivalente a la API pública. Por eso la seguridad esperada es de despliegue y segmentación:
- loopback
- firewall
- permisos del sistema operativo
- control del perfil activo
Ruteo por punto de emisión
La bandeja local puede operar de dos formas:
- usando el perfil activo del daemon
- enviando
cod_comercioycod_terminalpara rutear explícitamente la operación
Regla:
- si se envía uno de esos campos, se deben enviar ambos
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
- para tipos normales no conviene depender de una numeración definitiva armada por el integrador
- 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
En ejecución, el cliente local utiliza los XSD empaquetados en runtime:
runtime/resources/xsds_dgi/CFEDGI.xsdruntime/resources/xsds_dgi_sin_firma/CFEDGI.xsd
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-29</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>[email protected]</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
Contrato general de la bandeja
A diferencia de otros sistemas de integración que concentran todo en una operación única tipo Invoke, la bandeja REST local expone endpoints separados.
Eso implica:
- no existe un único envelope para todos los requests
- no existe un único envelope para todas las respuestas
- cada endpoint define sus propios campos de entrada y salida
Sin embargo, hay reglas comunes:
- la mayoría de las operaciones de emisión utilizan
tipo_cfe,uuid,xml,cod_comercioycod_terminal POST /sign-cfeyPOST /enqueueson las operaciones centrales de emisión- en las respuestas funcionales de firma importa tanto el HTTP como
codigo_respuesta
Campos frecuentes de request
| Campo | Uso | Descripción |
|---|---|---|
tipo_cfe | emisión, validación, consultas | Tipo de comprobante a procesar |
uuid | emisión, reimpresión, PDF | Identificador externo estable del comprobante |
xml | validación, firma, encolado | XML CFE base sin firma |
cod_comercio | ruteo | Código de comercio del punto de emisión |
cod_terminal | ruteo | Código de terminal del punto de emisión |
adenda | firma, encolado | Texto adicional asociado al comprobante |
emails | firma, encolado | Lista de destinatarios para uso posterior |
impresora | impresión, reimpresión | Destino y parámetros de impresión |
send_now | firma, encolado | Indica si se intenta enviar o dejar persistido para cola |
variant | PDF, reimpresión | Variante de plantilla o formato |
fallback_copies | PDF, reimpresión | Cantidad de copias de respaldo |
serie | consultas, reimpresión | Serie del comprobante cuando no se usa uuid |
numero | consultas, reimpresión | Número del comprobante cuando no se usa uuid |
Campos frecuentes de response
| Campo | Cuándo aparece | Descripción |
|---|---|---|
ok | health, state, validación, existencia, reprint, enqueue | Resultado técnico de la operación |
codigo_respuesta | sign-cfe, existe-constancia | Resultado funcional del procesamiento |
mensaje_respuesta | sign-cfe | Mensaje descriptivo complementario |
serie | sign-cfe, consultas | Serie asignada o encontrada |
numero | sign-cfe, consultas | Número asignado o encontrado |
cfe_firmado | sign-cfe | XML final firmado |
datos_codigo_qr | sign-cfe | URL o datos del QR del CFE |
codigo_seguridad | sign-cfe | Código de seguridad fiscal |
fecha_firma_cfe | sign-cfe | Fecha y hora de firma |
numero_inicial_cae | sign-cfe | Inicio del rango CAE usado |
numero_final_cae | sign-cfe | Fin del rango CAE usado |
vencimiento_cae | sign-cfe, proximo-serie-nro | Fecha de vencimiento del CAE |
stored_path | enqueue | Ruta local donde quedó persistido el trabajo |
queued | reprint | Indica que la reimpresión fue aceptada |
existe | existe-constancia | Indica si se encontró persistencia o constancia local |
Códigos funcionales relevantes
En POST /sign-cfe el integrador debe revisar siempre codigo_respuesta.
| Código | Significado |
|---|---|
00 | Éxito |
31 | Rechazo de validación o XML rechazado |
96 | Error interno, de firma, permisos, rango, persistencia o SOAP |
Regla recomendada:
considerar emitido solamente si HTTP 200, codigo_respuesta = "00" y serie + numero tienen valor válido.
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 |
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 cómo empezar a integrar
Un punto de partida razonable para una primera integración es implementar un único flujo de eTicket contado con emisión síncrona.
La secuencia mínima sería:
- verificar que el servicio local responde
- construir un XML base simple
- validarlo localmente
- invocar
POST /sign-cfe - guardar
uuid,serie,numeroycfe_firmado - opcionalmente pedir el PDF
Paso 1. Chequeo de disponibilidad
curl http://127.0.0.1:18787/health
Paso 2. XML base de arranque
<?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-29</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>[email protected]</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>
Paso 3. Validación local
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"
}'
Paso 4. Emisión síncrona
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>",
"send_now": false
}'
Paso 5. Qué guardar del response
Del response conviene persistir como mínimo:
uuidtipo_cfeserienumerocodigo_respuestacfe_firmado
Paso 6. PDF opcional
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
Con este flujo ya se puede cerrar una primera integración funcional. Después de eso conviene recién incorporar cola asíncrona, reimpresión, más tipos de CFE y casos fiscales especiales.
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": ["[email protected]"],
"send_now": false
}'
Respuesta 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-29T13:45:00Z",
"imagen_qr": null
}
Respuesta de error:
{
"uuid": "venta-pos-000123",
"tipo_cfe": "101",
"serie": null,
"numero": null,
"codigo_respuesta": "31",
"mensaje_respuesta": "validation error: ...",
"codigo_terminal": "1",
"codigo_comercio": "1",
"numero_inicial_cae": null,
"numero_final_cae": null,
"vencimiento_cae": null,
"cfe_firmado": null,
"datos_codigo_qr": null,
"codigo_seguridad": null,
"fecha_firma_cfe": null,
"imagen_qr": null
}
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/20260429134500124-venta-pos-000124.json",
"uuid": "venta-pos-000124",
"send_now": true
}
Interpretación:
- confirma recepción local del trabajo
- no garantiza por sí sola que el comprobante ya quedó aceptado fiscalmente
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
}
Impresoras y variantes
Los endpoints que usan impresora aceptan una especificación textual.
Ejemplos:
CajaFiscalCajaFiscal;COPIAS=2CajaFiscal;FORMATO=personalizado;COPIAS=2CajaFiscal;FORMATO=escpos;COPIAS=2CajaFiscal;FORMATO=rollo;COPIAS=2
Parámetros:
| Parámetro | Descripción |
|---|---|
| nombre base | Nombre o destino de impresora |
FORMATO=personalizado | Plantilla HTML/PDF |
FORMATO=escpos | Impresión térmica ESC/POS RAW |
FORMATO=roll, FORMATO=rollo, FORMATO=tml | PDF tipo ticket usando parser .tml |
COPIAS=2 | Cantidad de copias |
Errores frecuentes
uuid es obligatorioxml es obligatoriotipo_cfe debe ser mayor a cerocod_comercio es obligatorio para enrutar por punto de emisióncod_terminal es obligatorio para enrutar por punto de emisiónNo se encontró perfil para cod_comercio=X cod_terminal=Y- errores de firma
- errores de rango
- errores SOAP
Recomendaciones para producción
- usar
uuidexternos estables y únicos - validar XML antes de firmar
- no considerar éxito solo por HTTP
200 - en
/sign-cfe, revisar siemprecodigo_respuesta - registrar
uuid,tipo_cfe,serie,numero,codigo_respuestaymensaje_respuesta - probar primero con un tipo de CFE simple y un XML mínimo controlado
- separar la lógica de negocio del manejo técnico de impresión y reintentos
Qué leer después
Después de esta guía, el orden recomendado es: