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:

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:

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:

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:

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:

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:

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:

Secuencia recomendada para empezar a integrar

Orden recomendado para una primera integración estable:

  1. confirmar que el cliente local responde en GET /health
  2. validar el XML con POST /validar-xml
  3. consultar folio local con GET /proximo-serie-nro si la UI necesita previsualizar serie o número
  4. emitir síncrono con POST /sign-cfe o asíncrono con POST /enqueue
  5. usar POST /pdf o POST /reprint según necesidad operativa

Operaciones disponibles en la bandeja REST local

MétodoEndpointCuándo usarlo
GET/healthVerificación rápida de que el servicio está vivo
GET/stateDiagnóstico del estado local, drafts y cola
POST/validar-xmlValidar XML antes de consumir folios o firmar
GET/proximo-serie-nroObtener el próximo folio local esperado
POST/sign-cfeNumerar y firmar con respuesta inmediata
POST/enqueueAceptar trabajo en forma asíncrona
POST/pdfGenerar PDF de un comprobante ya emitido
POST/reprintReimprimir un comprobante ya emitido
POST/existe-constanciaVerificar 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:

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:

Usar POST /enqueue cuando:

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:

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:

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:

Buenas prácticas para el programador integrador

Qué leer después

Después de esta guía, el orden recomendado es:

  1. Integración por REST local
  2. Configuración de runtime
  3. Ciclo operativo
  4. Cola local por REST y archivos