Egeek.Sifen.WebApi permite usar el core open source de Egeek Sifen como un servicio HTTP stateless y monoemisor. En este tutorial vamos a levantar la versión estable 0.3.0, configurar el perfil fiscal sin exponer secretos, verificar la salud del host, firmar uno o varios documentos electrónicos y transmitirlos mediante el proceso por lotes de SIFEN.

El objetivo no es simular datos fiscales reales, sino mostrar el recorrido técnico completo y los puntos donde cada integrador debe conectar sus credenciales y datos habilitados.

Qué vamos a implementar

Al terminar tendrás una Web API local con estos componentes:

  • un perfil fiscal cerrado para un único emisor;
  • un certificado PKCS#12 utilizado tanto para XMLDSig como para mTLS;
  • timbrados y CSC seleccionables únicamente entre opciones autorizadas por el host;
  • Swagger, health checks, firma, envío, consultas y lotes;
  • contratos HTTP que nunca reciben certificados, contraseñas ni el secreto CSC.

La versión 0.3.0 soporta Factura Electrónica (FE), Nota de Crédito Electrónica (NCE), Nota de Débito Electrónica (NDE), Nota de Remisión Electrónica (NRE) y Autofactura Electrónica (AFE).

Requisitos previos

  • .NET SDK 10;
  • Git;
  • un certificado PKCS#12 y su clave;
  • CSC, timbrado y datos fiscales habilitados para el ambiente SIFEN que vas a utilizar;
  • un entorno seguro para almacenar secretos.

Empieza siempre en test. Este proyecto es independiente y no oficial de la DNIT; usarlo no reemplaza las validaciones fiscales, jurídicas y operativas que correspondan a tu implementación.

Paso 1: clonar y fijar la versión estable

git clone https://gitlab.egeek.cloud/egeek/sifen.git
cd sifen
git switch --detach 0.3.0
dotnet restore Egeek.Sifen.sln
dotnet build Egeek.Sifen.sln -c Release

Fijar el tag evita que una actualización futura de la rama principal cambie el comportamiento durante la integración. Para una instalación productiva conviene aplicar la misma regla a imágenes de contenedor, paquetes y pipelines.

Paso 2: entender el modelo monoemisor

Cada instancia de Egeek.Sifen.WebApi representa un solo emisor fiscal. El host controla cuatro grupos de información:

  1. PerfilFiscalMonoemisor: RUC, razón social, actividades, establecimientos y puntos de expedición.
  2. CredencialMonoemisor: certificado, clave y ambiente.
  3. Timbrados: combinaciones autorizadas por tipo de documento, establecimiento y punto de expedición.
  4. Csc: credenciales habilitadas para construir el QR.

El cliente HTTP solo envía selectores como idTimbradoRequerido e idCscRequerido. No puede introducir un timbrado arbitrario ni enviar secretos en el request.

Paso 3: configurar el host sin versionar secretos

El proyecto ya tiene soporte para ASP.NET Core User Secrets. En Visual Studio puedes abrir Manage User Secrets sobre Egeek.Sifen.WebApi. En otros entornos utiliza variables de entorno o el gestor de secretos de tu plataforma.

La estructura mínima es la siguiente. Todos los valores son marcadores y deben reemplazarse con información autorizada:

{
  "Sifen": {
    "PerfilFiscalMonoemisor": {
      "Ruc": "REEMPLAZAR",
      "DigitoVerificador": "REEMPLAZAR",
      "RazonSocial": "REEMPLAZAR",
      "NombreFantasia": null,
      "CodigoTipoContribuyente": 1,
      "CodigoTipoRegimen": 1,
      "ActividadesEconomicas": [
        { "Codigo": "REEMPLAZAR", "Descripcion": "REEMPLAZAR" }
      ],
      "Establecimientos": [
        {
          "Codigo": "001",
          "Denominacion": "REEMPLAZAR",
          "Direccion": {
            "Direccion": "REEMPLAZAR",
            "NumeroCasa": "0",
            "CodigoDepartamento": 1,
            "CodigoDistrito": 1,
            "CodigoCiudad": 1
          },
          "Telefono": "REEMPLAZAR",
          "CorreoElectronico": "REEMPLAZAR",
          "PuntosExpedicion": [ { "Codigo": "001" } ]
        }
      ]
    },
    "CredencialMonoemisor": {
      "IdCredencial": "principal-test",
      "Ambiente": "test",
      "Ruta": "REEMPLAZAR_CON_RUTA_PKCS12",
      "Clave": "REEMPLAZAR_DESDE_GESTOR_SEGURO"
    },
    "Timbrados": {
      "Timbrados": [
        {
          "IdTimbrado": "fe-test",
          "Ambiente": "test",
          "NumeroTimbrado": "REEMPLAZAR",
          "VigenciaDesde": "2026-01-01",
          "VigenciaHasta": "2026-12-31",
          "CodigoTipoDocumento": "FE",
          "CodigoEstablecimiento": "001",
          "CodigoPuntoExpedicion": "001",
          "Habilitado": true,
          "Activo": true
        }
      ]
    },
    "Csc": {
      "Credenciales": [
        {
          "Ambiente": "test",
          "IdCsc": "0001",
          "CodigoSecretoCsc": "REEMPLAZAR_DESDE_GESTOR_SEGURO",
          "Habilitada": true,
          "Activa": true
        }
      ]
    }
  }
}

Si prefieres variables de entorno, ASP.NET Core representa cada nivel con doble guion bajo:

$env:Sifen__CredencialMonoemisor__IdCredencial = 'principal-test'
$env:Sifen__CredencialMonoemisor__Ambiente = 'test'
$env:Sifen__CredencialMonoemisor__Ruta = 'C:\certificados\sifen.p12'
$env:Sifen__CredencialMonoemisor__Clave = '<desde-gestor-seguro>'
$env:Sifen__PerfilFiscalMonoemisor__Ruc = '<ruc-sin-dv>'
$env:Sifen__PerfilFiscalMonoemisor__DigitoVerificador = '<dv>'
$env:Sifen__Csc__Credenciales__0__CodigoSecretoCsc = '<desde-gestor-seguro>'

No guardes el archivo .pfx/.p12, su clave, el secreto CSC, XML fiscales ni datos reales en Git, imágenes de contenedor o logs.

Paso 4: arrancar la Web API y comprobar su salud

dotnet run --project src/Egeek.Sifen.WebApi

En el perfil de desarrollo incluido, Swagger queda disponible en http://localhost:5027/swagger. Comprueba primero los health checks:

curl http://localhost:5027/health/live
curl http://localhost:5027/health
  • /health/live confirma que el proceso está ejecutándose.
  • /health comprueba readiness, incluida la credencial monoemisor.

El host valida perfil, timbrados, CSC y certificado durante el arranque. Si la configuración está incompleta, falla antes de aceptar solicitudes: corrige ese problema antes de continuar.

Paso 5: firmar una Factura Electrónica

El repositorio incluye un request FE sanitizado en src/Egeek.Sifen.WebApi/Egeek.Sifen.WebApi.http. Sustituye RUC, receptor, timbrado, numeración, fechas y demás marcadores antes de usarlo.

La operación de firma es:

POST http://localhost:5027/api/v1/documentos-electronicos/firmar
Content-Type: application/json

Este es el payload FE completo incluido en la versión 0.3.0. El request selecciona únicamente recursos autorizados por el host; sustituye todos los marcadores por datos válidos y coherentes con el perfil, el timbrado y el ambiente configurados:

{
  "correlationId": "emision-local-001",
  "ambiente": "test",
  "rucEmisorEsperado": "REEMPLAZAR_RUC_CONFIGURADO",
  "idTimbradoRequerido": "fe-test",
  "idCscRequerido": "0001",
  "documento": {
    "tipoDocumento": "FE",
    "cabecera": {
      "codigoEstablecimiento": "001",
      "codigoPuntoExpedicion": "001",
      "numeroDocumento": "1",
      "fechaEmision": "2026-08-12T10:00:00-03:00",
      "codigoSeguridadCdc": "123456789",
      "codigoTipoEmision": 1,
      "sistemaFacturacion": 1
    },
    "operacion": {
      "codigoTipoTransaccion": 1,
      "codigoTipoImpuesto": 1,
      "codigoMoneda": "PYG",
      "obligacionesAfectadas": []
    },
    "receptor": {
      "codigoNaturaleza": 1,
      "codigoTipoOperacion": 1,
      "codigoPais": "PRY",
      "codigoTipoContribuyente": 1,
      "ruc": "80000000",
      "digitoVerificadorRuc": "0",
      "nombreRazonSocial": "REEMPLAZAR CON RECEPTOR VALIDO",
      "direccion": {
        "direccion": "CALLE DE PRUEBA",
        "numeroCasa": "123",
        "codigoDepartamento": 11,
        "codigoDistrito": 1,
        "codigoCiudad": 1
      }
    },
    "items": [
      {
        "codigoInterno": "SERV-001",
        "descripcion": "SERVICIO DE PRUEBA",
        "codigoUnidadMedida": 77,
        "cantidad": 1,
        "precioUnitario": 100000,
        "ajustes": {
          "descuentoItem": 0,
          "anticipoPrecioUnitario": 0,
          "anticipoGlobalPrecioUnitario": 0
        },
        "iva": {
          "codigoAfectacionIva": 1,
          "proporcionIva": 100,
          "tasaIva": 10
        }
      }
    ],
    "ajustesGlobales": {
      "porcentajeDescuentoTotal": 0,
      "redondeo": 0
    },
    "facturaElectronica": {
      "codigoIndicadorPresencia": 1,
      "condicionOperacion": {
        "codigoCondicionOperacion": 1,
        "pagosContado": [
          {
            "codigoTipoPago": 1,
            "monto": 100000,
            "codigoMoneda": "PYG"
          }
        ]
      }
    },
    "referencias": []
  }
}

El ejemplo representa una operación al contado en guaraníes con un ítem gravado al 10 %. Los códigos, importes y datos fiscales deben corresponder a la operación real. El archivo .http del repositorio conserva la misma referencia para ejecutarla desde un cliente HTTP compatible, y Swagger permite inspeccionar el contrato publicado por la API.

Una firma correcta devuelve, entre otros datos, cdc, xmlFirmado, urlQr e idCscUtilizado. Tu sistema debe conservarlos antes de intentar una transmisión.

Paso 6: firmar varios documentos en una sola solicitud

La Web API también permite firmar de 1 a 50 documentos mediante:

POST http://localhost:5027/api/v1/documentos-electronicos/firmar-multiples
Content-Type: application/json

{
  "correlationId": "firma-multiple-001",
  "ambiente": "test",
  "rucEmisorEsperado": "REEMPLAZAR_RUC_CONFIGURADO",
  "idCscRequerido": "0001",
  "documentos": [
    {
      "correlationId": "documento-001",
      "idTimbradoRequerido": "fe-test",
      "documento": {
        "tipoDocumento": "FE"
      }
    }
  ]
}

En cada elemento, documento utiliza el mismo contrato FE completo mostrado en el paso anterior. El repositorio contiene un ejemplo completo con dos facturas en samples/Egeek.Sifen.Stateless.Sample/firma-multiple.swagger.ejemplo.json.

La firma múltiple produce un resultado independiente por documento. Conserva el cdc, xmlFirmado y urlQr de cada elemento exitoso; un fallo individual no debe ocultar los demás resultados. Firma múltiple y lote SIFEN no son lo mismo: primero se generan los XML rDE firmados y después se agrupan para transmitirlos.

Paso 7: enviar los documentos por lote

Importante: el envío individual síncrono mediante /api/v1/documentos-electronicos/enviar se utiliza únicamente en ambiente TEST. En Producción los documentos deben transmitirse mediante el proceso por lotes.

Los requests siguientes mantienen "ambiente": "test" porque el tutorial debe validarse primero en SIFEN Test. Al desplegar en Producción, utiliza "production" junto con certificado, timbrados, CSC y perfil fiscal habilitados específicamente para ese ambiente.

No vuelvas a generar ni firmar los documentos antes de enviarlos. Usa exactamente los xmlFirmado que persististe después de la firma individual o múltiple:

POST http://localhost:5027/api/v1/lotes/enviar
Content-Type: application/json

{
  "correlationId": "lote-local-001",
  "ambiente": "test",
  "idControl": "lote-local-001",
  "rucEmisorEsperado": "REEMPLAZAR_RUC_CONFIGURADO",
  "documentos": [
    {
      "correlationId": "documento-001",
      "xmlFirmado": "<rDE>REEMPLAZAR_CON_XML_FIRMADO_CONSERVADO</rDE>"
    },
    {
      "correlationId": "documento-002",
      "xmlFirmado": "<rDE>REEMPLAZAR_CON_OTRO_XML_FIRMADO</rDE>"
    }
  ]
}

El lote admite de 1 a 50 documentos firmados. Los documentos deben cumplir las reglas de homogeneidad del tipo documental y corresponder al emisor y ambiente configurados. Conserva especialmente numeroLote, cdcs, cantidadDocumentos y proximaConsultaSugeridaEnSegundos.

La recepción del lote no significa que cada documento esté aprobado. Consulta posteriormente el resultado usando el número retornado:

POST http://localhost:5027/api/v1/lotes/consultar-resultado
Content-Type: application/json

{
  "correlationId": "consulta-lote-001",
  "ambiente": "test",
  "idControl": "consulta-lote-001",
  "numeroLote": "REEMPLAZAR_CON_NUMERO_LOTE",
  "rucEmisorEsperado": "REEMPLAZAR_RUC_CONFIGURADO"
}

Un lote puede estar EnProcesamiento, Concluido, Rechazado, NoEncontrado o Expirado. Cuando esté concluido, revisa el estado y los códigos nativos de cada documento; no basta con mirar únicamente el código HTTP de la consulta.

Paso 8: manejar timeouts y rechazos correctamente

  • Un Timeout o TransportError es ambiguo. No autoriza a crear otro documento ni a reenviar a ciegas.
  • No reutilices un CDC para otro comprobante.
  • No reintentes automáticamente un rechazo funcional: registra los códigos nativos, corrige los datos y aplica la política operativa correspondiente.
  • Respeta proximaConsultaSugeridaEnSegundos y define el polling en tu aplicación; el core no incorpora un scheduler automático.
  • En Producción utiliza el proceso por lotes y confirma que el RUC esté habilitado para los servicios correspondientes.

Otros endpoints disponibles

POST /api/v1/documentos-electronicos/enviar  (solo TEST)
POST /api/v1/consultas/documento-electronico
POST /api/v1/consultas/ruc
GET  /health/live
GET  /health

La Web API no implementa persistencia, scheduler, polling ni reintentos automáticos: esas decisiones pertenecen a la aplicación que integra el core.

Checklist antes de pasar a producción

  • Fijar la versión del core y reproducir build y pruebas en CI.
  • Usar un gestor de secretos y restringir el acceso al certificado.
  • Proteger la Web API con autenticación, autorización, TLS y controles de red adecuados a tu plataforma.
  • Persistir XML firmado, CDC, correlación y respuestas sin exponerlos en logs.
  • Monitorear el readiness y la vigencia del certificado.
  • Completar una aceptación controlada en SIFEN Test antes de operar en Producción.
  • Implementar en Producción el envío y seguimiento por lotes, sin depender del endpoint síncrono.

Recursos

Con esta base ya puedes integrar el flujo HTTP sin convertir la Web API en propietaria de tus datos fiscales. La aplicación consumidora sigue siendo responsable de persistencia, autorización, auditoría y política operativa; Egeek.Sifen se concentra en construir, validar, firmar y transportar documentos conforme al flujo técnico de SIFEN.

Por Miguel

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *