Documentación

Todo lo que necesitas para pasar de dotnet add package a tu primer e-CF aceptado en TesteCF.

01

Instalación

Requiere .NET 8 o .NET 10. Es un solo paquete: emisor, receptor, firma y herramientas vienen juntos.

terminal
dotnet add package DgiiEcf
02

Configuración

Agrega la sección DgiiEcf a tu appsettings.json:

appsettings.json
{
  "DgiiEcf": {
    "Environment": "Test",
    "StatusApiKey": null,
    "Certificate": {
      "CertificatePath": "certs/empresa.p12",
      "CertificatePassword": "<desde un secret store>"
    }
  }
}
EnvironmentAmbiente DGII
TestTesteCF
CertificationCerteCF
ProductioneCF

El certificado también se puede entregar en base64 con CertificateBase64, algo útil en contenedores y secret managers. Nunca guardes la contraseña en el repositorio.

03

Registro

Con el contenedor de dependencias de ASP.NET Core o de un Worker:

Program.cs
builder.Services.AddDgiiEcf(builder.Configuration);

// o por código
services.AddDgiiEcf(options =>
{
    options.Environment = DgiiEnvironment.Certification;
    options.CertificateBase64 = secrets["dgii-p12"];
    options.CertificatePassword = secrets["dgii-p12-password"];
});

// o partiendo de un X509Certificate2 que ya tengas abierto
services.AddDgiiEcf(certificate, options => options.Environment = DgiiEnvironment.Production);

Si no usas un contenedor de DI:

Program.cs
using var dgii = DgiiEcfClient.Create(options => { /* ... */ });
var client   = dgii.Client;   // IDgiiEcfClient
var receiver = dgii.Receiver; // IEcfReceiver
04

Resultados y errores

Todas las operaciones devuelven Result<T>. Un error esperado nunca lanza excepción: revisa IsSuccess o Error.

  • Cuando falla la DGII, Error es un DgiiApiError con Status, Messages (los mensajes de la DGII) y RawBody.
  • Error.Message trae el texto de la DGII, por ejemplo "e-NCF duplicado".
05

Emisor

Inyecta IDgiiEcfClient, firma y envía. El token se obtiene y se renueva solo, y si la DGII responde 401 se reintenta una vez con un token nuevo. Por defecto el archivo se nombra {RNCEmisor}{eNCF}.xml.

FacturacionService.cs
public sealed class FacturacionService(IDgiiEcfClient dgii)
{
    public async Task<string?> EnviarAsync(Ecf factura, CancellationToken ct)
    {
        var firmado = dgii.SignDocument(factura);   // o dgii.Sign(xml, "ECF")
        if (firmado.IsFailure) return null;

        var envio = await dgii.SendElectronicDocumentAsync(firmado.Value, cancellationToken: ct);
        if (envio.IsFailure) return null;

        var estado = await dgii.GetTrackStatusAsync(envio.Value.TrackId!, ct);
        return estado.IsSuccess ? estado.Value.Estado : null;
    }
}

Otras operaciones del emisor: SendCommercialApprovalAsync, VoidEncfAsync, InquiryStatusAsync, GetTrackIdsAsync, GetCustomerDirectoryAsync y GetSummaryInvoiceInquiryAsync (solo producción). Las de estatus de servicios (GetServicesStatusAsync, GetMaintenanceWindowsAsync, VerifyServiceStatusAsync) requieren StatusApiKey.

06

Factura de consumo

Para un e-CF 32 menor de RD$250,000 se envía el resumen (RFCE). Guarda el e-CF 32 firmado: es el documento completo.

Consumo.cs
var ecf32 = dgii.SignDocument(facturaConsumo).Value;
var rfce  = dgii.CreateSignedRfce(ecf32).Value;       // resumen firmado + código de seguridad
var respuesta = await dgii.SendSummaryAsync(rfce.SignedXml);
var qr = EcfTools.FcQrCodeUrl(rnc, encf, montoTotal, rfce.SecurityCode, DgiiEnvironment.Test);
07

Receptor

Para el estándar emisor-receptor expones tres endpoints y IEcfReceiver hace el trabajo pesado:

EndpointMétodo
GET /fe/autenticacion/api/semillaGenerateSeed()
POST /fe/autenticacion/api/validacioncertificadoValidateSignedSeed() → JWT
POST /fe/recepcion/api/ecfValidateToken() + BuildSignedReceiptAcknowledgement()
Program.cs
app.MapGet("/fe/autenticacion/api/semilla", (IEcfReceiver r) =>
    Results.Content(r.GenerateSeed(), "application/xml"));

app.MapPost("/fe/autenticacion/api/validacioncertificado", async (HttpRequest req, IEcfReceiver r) =>
{
    var body = await new StreamReader(req.Body).ReadToEndAsync();
    var archivo = r.ParseReceivedDocument(body, req.ContentType!);
    var token = archivo.IsSuccess ? r.ValidateSignedSeed(archivo.Value.XmlContent) : null;
    return token is { IsSuccess: true } ? Results.Ok(token.Value) : Results.Unauthorized();
});

El acuse de recibo rechaza por su cuenta los tipos 32, 41, 43, 45, 46 y 47 (código 1) y los documentos cuyo RNC comprador no es el tuyo (código 4).

08

Herramientas

EcfTools es una clase estática: no necesita certificado ni conexión HTTP.

MétodoQué hace
GetSecurityCode(signedXml)Primeros 6 caracteres del SignatureValue
ConvertEcf32ToRfce(signedEcf32)Genera el RFCE sin firmar
EcfQrCodeUrl(...) / FcQrCodeUrl(...)URL del QR con el encoding de encodeURIComponent
Serialize(doc) / Deserialize<T>(xml)Documentos tipados ⇄ XML
JsonToXml / XmlToJson / NormalizeJsonCasingCompatibilidad con los payloads JSON del paquete Node
SetXmlValue, ExtractXmlFromBody, CurrentFormattedDateTimeUtilidades de XML y fecha

Para verificar la firma de un documento recibido, usa IValidateDocumentSignatureHandler desde el contenedor de DI.

09

Diferencias con Node

Es un port de dgii-ecf v1.8.5, con estas mejoras:

  • Token por audiencia (la DGII o cada receptor), renovado un minuto antes de expirar. Puedes reemplazar IAccessTokenStore por un store distribuido.
  • Semilla del receptor verificada criptográficamente, no solo por digest.
  • Firmas aceptadas aunque el documento se haya guardado indentado. No valida la vigencia ni la cadena del certificado.
  • RFCE funciona con un solo ImpuestoAdicional y rechaza documentos que no son tipo 32 o que llegan a RD$250,000.
  • Certificado .p12: se usa el que tiene la clave privada, no el primero de la bolsa.
  • JSON ⇄ XML respeta los números tal como vienen (637.20 sigue siendo 637.20).

Proyecto independiente desarrollado y mantenido por fcastro. No está afiliado, respaldado ni mantenido por la Dirección General de Impuestos Internos (DGII).