Gestión de CAE
El CAE (Constancia de Autorización de Emisión) es el rango de números que la DGI autoriza para un tipo de CFE y una serie. Sin un CAE vigente no se puede emitir.
Registrar un CAE
Section titled “Registrar un CAE”await client.RegistrarCaeAsync(new Cae{ NumeroAutorizacion = "90110001945", // CAE_ID entregado por la DGI TipoCfe = TipoCfe.ETicket, // tipo de CFE que autoriza Serie = "A", // "A", "AB" o "1A" RangoDesde = 1, // primer número autorizado RangoHasta = 1000, // último número autorizado FechaVencimiento = new DateOnly(2027, 12, 31),});- Un CAE se identifica por tipo, serie y comienzo de rango: registrar el mismo otra vez actualiza sus datos sin hacer retroceder su numeración.
- Los datos se validan al registrar (
CaeExceptionsi algo está mal), así que un CAE incorrecto no llega a un comprobante. Tampoco se admite un CAE con números en común con otro del mismo tipo y serie (por ejemplo, por un error de tipeo en el rango): se emitirían números repetidos. - Un CAE registrado por error que todavía no numeró ningún CFE se quita con
client.EliminarCaeAsync(cae). - Para varios tipos de CFE, un CAE por tipo. Con más de un CAE vigente para un tipo, se numera con el de vencimiento más lejano y, a igual vencimiento, con el que tenga más números libres; cuando uno se agota se pasa al siguiente.
Dónde vive la numeración
Section titled “Dónde vive la numeración”La numeración vive en el almacenamiento del cliente (IUruFacturaStore), que es la única fuente de verdad. EmitirAsync toma el próximo número y lo registra junto con el CFE, en una sola operación atómica: si algo falla (la firma, la base de datos, una cancelación), no se consume ningún número, así que no quedan huecos sin comprobante.
| Almacenamiento | Para qué |
|---|---|
InMemoryStore (por defecto en Homologación) |
Pruebas y desarrollo. Se pierde al reiniciar: la aplicación volvería a usar números ya emitidos |
EfUruFacturaStore<TContext> (UruFactura.EntityFrameworkCore) |
Producción. Durable y seguro con varias instancias |
Producción y varias instancias: EF Core
Section titled “Producción y varias instancias: EF Core”EfUruFacturaStore avanza el CAE con un UPDATE condicional sobre UruFacturaCaes (solo si nadie lo cambió desde que se leyó) en la misma transacción que el INSERT del CFE en UruFacturaCfes. Si otra instancia numeró antes, no se guarda nada y el cliente vuelve a numerar: dos instancias (réplicas, blue/green, API + worker) nunca obtienen el mismo número, y el reporte diario incluye los CFE de todas.
builder.Services.AddDbContextFactory<UruFacturaDbContext>(o => o.UseSqlServer(connectionString));
builder.Services.AddSingleton<IUruFacturaClient>(sp => new UruFacturaClient( config, store: new EfUruFacturaStore<UruFacturaDbContext>(sp.GetRequiredService<IDbContextFactory<UruFacturaDbContext>>())));Si tu app ya tiene un DbContext, llamá a modelBuilder.ApplyUruFacturaModel() en OnModelCreating, usá EfUruFacturaStore<TuContexto> y generá la migración con tu proveedor. Para otro almacenamiento, implementá IUruFacturaStore: RegistrarEmisionAsync debe avanzar el CAE y guardar el CFE de forma atómica (las dos cosas o ninguna).
Monitorear
Section titled “Monitorear”// Vencidos, agotados, por vencer (7 días por defecto) o con el rango usado al 80 % o másforeach (var advertencia in await client.ObtenerAdvertenciasCaeAsync(diasAlertaVencimiento: 15)) Console.WriteLine(advertencia);
// Estado de cada CAEforeach (var cae in await client.ObtenerCaesAsync()) Console.WriteLine($"{cae.TipoCfe} serie {cae.Serie}: {cae.Disponibles} libres ({cae.PorcentajeUso}% usado), vence {cae.FechaVencimiento:dd/MM/yyyy}");Ejemplo de advertencia:
⚠️ CAE 90110001945 (ETicket, serie A) usó el 89.00% de su rango.En ASP.NET Core con un Hosted Service
Section titled “En ASP.NET Core con un Hosted Service”public class CaeMonitorService(IUruFacturaClient client, ILogger<CaeMonitorService> logger) : BackgroundService{ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { foreach (var advertencia in await client.ObtenerAdvertenciasCaeAsync(cancellationToken: stoppingToken)) logger.LogWarning("CAE: {Advertencia}", advertencia); // o alertar por email/Slack
await Task.Delay(TimeSpan.FromHours(1), stoppingToken); } }}
// Program.csbuilder.Services.AddHostedService<CaeMonitorService>();Renovar un CAE
Section titled “Renovar un CAE”-
Solicitar el nuevo CAE en DGI en línea (Gestión CFE → Solicitud de CAE), indicando el tipo de CFE y la cantidad.
-
Registrarlo — puede convivir con el anterior; se usa el que vence más tarde:
await client.RegistrarCaeAsync(new Cae{NumeroAutorizacion = "90110002001",TipoCfe = TipoCfe.ETicket,Serie = "A",RangoDesde = 1001, // el rango que autorizó la DGIRangoHasta = 2000,FechaVencimiento = new DateOnly(2028, 12, 31),}); -
Verificar con
ObtenerCaesAsync()uObtenerAdvertenciasCaeAsync().