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:

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:

No cubre:

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:

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:

  1. construye el XML base del comprobante
  2. invoca la bandeja REST local
  3. obtiene una respuesta inmediata o deja trabajo en cola
  4. consume el XML firmado, el PDF o el estado operativo según la operación utilizada

En el flujo normal el cliente local resuelve:

Proceso recomendado para emisión

El proceso sugerido para una primera integración estable es el siguiente:

  1. levantar el cliente local y verificar GET /health
  2. asumir como prerrequisito de despliegue que el cliente local fue instalado con su runtime completo
  3. construir el XML base sin firma
  4. validar con POST /validar-xml
  5. emitir con POST /sign-cfe si se necesita respuesta inmediata
  6. o encolar con POST /enqueue si el flujo es asíncrono
  7. usar POST /pdf o POST /reprint para 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:

Seguridad y despliegue

La bandeja REST local está pensada para exponer su superficie en el propio equipo del emisor.

Buenas prácticas operativas:

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:

Ruteo por punto de emisión

La bandeja local puede operar de dos formas:

Regla:

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:

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:

En ejecución, el cliente local utiliza los XSD empaquetados en runtime:

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:

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:

Sin embargo, hay reglas comunes:

Campos frecuentes de request

CampoUsoDescripción
tipo_cfeemisión, validación, consultasTipo de comprobante a procesar
uuidemisión, reimpresión, PDFIdentificador externo estable del comprobante
xmlvalidación, firma, encoladoXML CFE base sin firma
cod_comercioruteoCódigo de comercio del punto de emisión
cod_terminalruteoCódigo de terminal del punto de emisión
adendafirma, encoladoTexto adicional asociado al comprobante
emailsfirma, encoladoLista de destinatarios para uso posterior
impresoraimpresión, reimpresiónDestino y parámetros de impresión
send_nowfirma, encoladoIndica si se intenta enviar o dejar persistido para cola
variantPDF, reimpresiónVariante de plantilla o formato
fallback_copiesPDF, reimpresiónCantidad de copias de respaldo
serieconsultas, reimpresiónSerie del comprobante cuando no se usa uuid
numeroconsultas, reimpresiónNúmero del comprobante cuando no se usa uuid

Campos frecuentes de response

CampoCuándo apareceDescripción
okhealth, state, validación, existencia, reprint, enqueueResultado técnico de la operación
codigo_respuestasign-cfe, existe-constanciaResultado funcional del procesamiento
mensaje_respuestasign-cfeMensaje descriptivo complementario
seriesign-cfe, consultasSerie asignada o encontrada
numerosign-cfe, consultasNúmero asignado o encontrado
cfe_firmadosign-cfeXML final firmado
datos_codigo_qrsign-cfeURL o datos del QR del CFE
codigo_seguridadsign-cfeCódigo de seguridad fiscal
fecha_firma_cfesign-cfeFecha y hora de firma
numero_inicial_caesign-cfeInicio del rango CAE usado
numero_final_caesign-cfeFin del rango CAE usado
vencimiento_caesign-cfe, proximo-serie-nroFecha de vencimiento del CAE
stored_pathenqueueRuta local donde quedó persistido el trabajo
queuedreprintIndica que la reimpresión fue aceptada
existeexiste-constanciaIndica si se encontró persistencia o constancia local

Códigos funcionales relevantes

En POST /sign-cfe el integrador debe revisar siempre codigo_respuesta.

CódigoSignificado
00Éxito
31Rechazo de validación o XML rechazado
96Error 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é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

Cuándo usar sign-cfe y cuándo usar enqueue

Usar POST /sign-cfe cuando:

Usar POST /enqueue cuando:

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:

  1. verificar que el servicio local responde
  2. construir un XML base simple
  3. validarlo localmente
  4. invocar POST /sign-cfe
  5. guardar uuid, serie, numero y cfe_firmado
  6. 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:

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:

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
}

Impresoras y variantes

Los endpoints que usan impresora aceptan una especificación textual.

Ejemplos:

Parámetros:

ParámetroDescripción
nombre baseNombre o destino de impresora
FORMATO=personalizadoPlantilla HTML/PDF
FORMATO=escposImpresión térmica ESC/POS RAW
FORMATO=roll, FORMATO=rollo, FORMATO=tmlPDF tipo ticket usando parser .tml
COPIAS=2Cantidad de copias

Errores frecuentes

Recomendaciones para producción

Qué leer después

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

  1. Proceso de integración para emisión
  2. Catálogo de campos de la bandeja REST
  3. Ejemplos de XML por tipo de CFE
  4. Operación sign-cfe
  5. Operación enqueue
  6. Operación pdf
  7. Operación reprint
  8. Integración por REST local