Cliente .NET (C#)
FacturoClient.cs es un cliente para aplicaciones .NET basado en HttpClient y System.Text.Json, en el espacio de nombres Facturo.Client.
Compatibilidad
- .NET 8.0 / .NET 9.0
- .NET 6.0 / .NET 7.0
- .NET Standard 2.0 / 2.1 (.NET Framework 4.6.2+, .NET Core 2.0+, Xamarin/MAUI)
En estos destinos, agrega el paquete NuGet System.Text.Json y habilita C# 8 o superior (<LangVersion>latest</LangVersion>), ya que el cliente usa tipos anulables y declaraciones using.
Opción A: ASP.NET Core con inyección de dependencias (recomendada)
Registra FacturoClient con IHttpClientFactory para evitar el agotamiento de sockets y gestionar el ciclo de vida de las conexiones:
// Program.cs
using Facturo.Client;
var builder = WebApplication.CreateBuilder(args);
var facturoOptions = new FacturoClientOptions
{
BaseUrl = builder.Configuration["Facturo:BaseUrl"] ?? "https://ecf.facturord.app/",
ApiKey = builder.Configuration["Facturo:ApiKey"] ?? string.Empty,
DefaultCompanyId = builder.Configuration["Facturo:CompanyId"],
Timeout = TimeSpan.FromSeconds(30),
};
builder.Services.AddSingleton(facturoOptions);
// HttpClient tipado (en este modo el timeout se configura en el HttpClient)
builder.Services.AddHttpClient<IFacturoClient, FacturoClient>(http =>
{
http.Timeout = facturoOptions.Timeout;
});
var app = builder.Build();
Configuración en appsettings.json (guarda la API Key en User Secrets, variables de entorno o un gestor de secretos, no en el repositorio):
{
"Facturo": {
"BaseUrl": "https://ecf.facturord.app/",
"CompanyId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}
Inyecta IFacturoClient en tus servicios o controladores:
[ApiController]
[Route("api/[controller]")]
public class InvoicesController : ControllerBase
{
private readonly IFacturoClient _facturoClient;
public InvoicesController(IFacturoClient facturoClient)
{
_facturoClient = facturoClient;
}
[HttpPost("void-encf")]
public async Task<IActionResult> VoidSequence([FromBody] VoidSequenceRequest req)
{
var response = await _facturoClient.CancelSingleSequenceAsync(
ecfType: req.EcfType,
encf: req.Encf
);
return Ok(response);
}
}
Opción B: Aplicación de consola, worker o escritorio
Para scripts, background workers o aplicaciones WPF/WinForms:
using System;
using System.Threading.Tasks;
using Facturo.Client;
class Program
{
static async Task Main(string[] args)
{
// URL base por defecto: "https://ecf.facturord.app/"
using var client = new FacturoClient(
apiKey: Environment.GetEnvironmentVariable("FACTURO_API_KEY")!,
defaultCompanyId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
);
// 1. Anular un e-NCF no utilizado
try
{
var cancelRes = await client.CancelSingleSequenceAsync(
ecfType: EcfTypes.FacturaCreditoFiscal,
encf: "E310000000001",
dgiiEnv: DgiiEnvironment.TestECF
);
Console.WriteLine($"Código: {cancelRes.Code}, Nombre: {cancelRes.Name}");
Console.WriteLine($"Total anuladas: {cancelRes.TotalCancelled}");
}
catch (FacturoApiException ex)
{
Console.WriteLine($"Error de la API ({ex.StatusCode}): {ex.Message}");
}
// 2. Anular un rango de secuencias
try
{
var rangeRes = await client.CancelSequenceRangeAsync(
ecfType: EcfTypes.FacturaCreditoFiscal,
fromEncf: "E310000000010",
toEncf: "E310000000020"
);
Console.WriteLine($"Se anularon {rangeRes.TotalCancelled} secuencias.");
}
catch (FacturoApiException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
// 3. Descargar el XML firmado
try
{
byte[] xmlBytes = await client.GetSignedXmlAsync("E310000000001");
await System.IO.File.WriteAllBytesAsync("E310000000001.xml", xmlBytes);
Console.WriteLine("XML firmado guardado.");
}
catch (FacturoApiException ex)
{
Console.WriteLine($"Error al descargar el XML: {ex.Message}");
}
// 4. Consultar el estado por Track ID
try
{
var tracking = await client.GetTrackingStatusAsync("trk_ejemplo_12345");
Console.WriteLine($"Estado DGII: {tracking.DgiiStatus}, Último estado: {tracking.LastStatus}");
}
catch (FacturoApiException ex)
{
Console.WriteLine($"Error en la consulta: {ex.Message}");
}
}
}
El constructor con apiKey crea y gestiona su propio HttpClient. Reutiliza la misma instancia durante la vida de la aplicación en lugar de crear una por petición. En ASP.NET Core usa la opción A.
Métodos de IFacturoClient
Todos los métodos aceptan un CancellationToken opcional.
| Método | Descripción | Endpoint |
|---|---|---|
CancelRangesAsync(details, companyId?, dgiiEnv?) | Anula rangos agrupados por tipo de e-CF | POST /emission/cancel-ranges |
CancelSingleSequenceAsync(ecfType, encf, companyId?, dgiiEnv?) | Anula un único e-NCF | POST /emission/cancel-ranges |
CancelSequenceRangeAsync(ecfType, fromEncf, toEncf, companyId?, dgiiEnv?) | Anula un rango continuo | POST /emission/cancel-ranges |
EmitRichECFAsync(request, companyId?) | Emite un e-CF completo | POST /emission/ecf |
GetTrackingStatusAsync(trackId, companyId?) | Estado en la DGII por Track ID | GET /emission/invoices/{track_id}/status |
GetSignedXmlAsync(documentIdOrEncf, companyId?, dgiiEnv?, download?) | XML firmado (byte[]) por UUID o e-NCF | GET /emission/documents/{id}/signed-xml |
RetryEmissionAsync(documentId, companyId?) | Reintenta un documento pendiente o fallido | POST /emission/documents/{id}/retry |
Errores
- Errores HTTP de la API:
FacturoApiException, conStatusCodeyRawPayload(cuerpo original). company_idausente:InvalidOperationException.- Argumentos vacíos (e-NCF, Track ID, etc.):
ArgumentException. - Timeout:
TaskCanceledException; fallo de red:HttpRequestException.
Reglas ANECF a recordar
- Solo secuencias no utilizadas: la DGII rechaza ANECF si el e-NCF ya fue aceptado (se debe emitir una Nota de Crédito E34).
- 13 caracteres exactos:
[Serie 1][Tipo 2][Secuencia 10], por ejemploE310000000001. - Rangos continuos: la parte numérica de
from_encfdebe ser<=que la deto_encf.
Más detalles en Anulación de secuencias (ANECF).