Skip to content

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.


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 (CaeException si 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.

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

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).


// Vencidos, agotados, por vencer (7 días por defecto) o con el rango usado al 80 % o más
foreach (var advertencia in await client.ObtenerAdvertenciasCaeAsync(diasAlertaVencimiento: 15))
Console.WriteLine(advertencia);
// Estado de cada CAE
foreach (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.
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.cs
builder.Services.AddHostedService<CaeMonitorService>();

  1. Solicitar el nuevo CAE en DGI en línea (Gestión CFE → Solicitud de CAE), indicando el tipo de CFE y la cantidad.

  2. 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 DGI
    RangoHasta = 2000,
    FechaVencimiento = new DateOnly(2028, 12, 31),
    });
  3. Verificar con ObtenerCaesAsync() u ObtenerAdvertenciasCaeAsync().