Files
GestorWebFacturas/Backend/GestorFacturas.API/Workers/CronogramaWorker.cs
2025-12-12 15:40:34 -03:00

155 lines
5.3 KiB
C#

using Microsoft.EntityFrameworkCore;
using GestorFacturas.API.Data;
using GestorFacturas.API.Models;
using GestorFacturas.API.Services.Interfaces;
namespace GestorFacturas.API.Workers;
/// <summary>
/// Worker Service que ejecuta el procesamiento de facturas de forma programada
/// </summary>
public class CronogramaWorker : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<CronogramaWorker> _logger;
// Eliminamos el Timer antiguo
public CronogramaWorker(
IServiceProvider serviceProvider,
ILogger<CronogramaWorker> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("CronogramaWorker iniciado correctamente.");
// PeriodicTimer espera a que termine la ejecución antes de contar el siguiente intervalo.
// Iniciamos con un tick de 1 minuto para chequear.
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
try
{
// Bucle infinito mientras el servicio esté activo
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await VerificarYEjecutar(stoppingToken);
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("CronogramaWorker detenido.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fatal en el ciclo del CronogramaWorker");
}
}
private async Task VerificarYEjecutar(CancellationToken stoppingToken)
{
try
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var config = await context.Configuraciones.FirstOrDefaultAsync(c => c.Id == 1, stoppingToken);
if (config == null)
{
_logger.LogWarning("No se encontró configuración del sistema");
return;
}
// Verificar si el servicio está activo
if (!config.EnEjecucion)
{
// Solo loguear en nivel Debug para no saturar los logs
_logger.LogDebug("Servicio en estado detenido");
return;
}
// Determinar si toca ejecutar según la periodicidad
if (!DebeEjecutar(config))
{
return;
}
_logger.LogInformation("¡Es momento de ejecutar el proceso de facturas!");
// Ejecutar el proceso
var procesador = scope.ServiceProvider.GetRequiredService<IProcesadorFacturasService>();
// Calcular fecha desde basándonos en la última ejecución o periodicidad
DateTime fechaDesde = CalcularFechaDesde(config);
await procesador.EjecutarProcesoAsync(fechaDesde);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error en CronogramaWorker al verificar y ejecutar");
}
}
private bool DebeEjecutar(Configuracion config)
{
if (config.UltimaEjecucion == null) return true;
var ahora = DateTime.Now;
var ultimaEjecucion = config.UltimaEjecucion.Value;
if (!TimeSpan.TryParse(config.HoraEjecucion, out TimeSpan horaConfigurada))
{
horaConfigurada = TimeSpan.Zero;
}
switch (config.Periodicidad.ToUpper())
{
case "MINUTOS":
var minutosTranscurridos = (ahora - ultimaEjecucion).TotalMinutes;
return minutosTranscurridos >= config.ValorPeriodicidad;
case "DIAS":
case "DÍAS":
var diasTranscurridos = (ahora.Date - ultimaEjecucion.Date).Days;
if (diasTranscurridos < config.ValorPeriodicidad) return false;
var horaActual = ahora.TimeOfDay;
var yaEjecutadoHoy = ultimaEjecucion.Date == ahora.Date;
// Ejecuta si pasó la hora configurada Y no se ha ejecutado hoy
return horaActual >= horaConfigurada && !yaEjecutadoHoy;
case "MESES":
var mesesTranscurridos = ((ahora.Year - ultimaEjecucion.Year) * 12) + ahora.Month - ultimaEjecucion.Month;
if (mesesTranscurridos < config.ValorPeriodicidad) return false;
return ahora.TimeOfDay >= horaConfigurada && !(ultimaEjecucion.Date == ahora.Date);
default:
return false;
}
}
private DateTime CalcularFechaDesde(Configuracion config)
{
// Buffer de seguridad de 10 días
int diasBuffer = 10;
if (config.UltimaEjecucion != null)
{
return config.UltimaEjecucion.Value.Date.AddDays(-diasBuffer);
}
// Si es la primera vez, usa la periodicidad + buffer
return config.Periodicidad.ToUpper() switch
{
"MINUTOS" => DateTime.Now.AddMinutes(-config.ValorPeriodicidad).AddDays(-diasBuffer),
"DIAS" or "DÍAS" => DateTime.Now.AddDays(-config.ValorPeriodicidad - diasBuffer),
"MESES" => DateTime.Now.AddMonths(-config.ValorPeriodicidad).AddDays(-diasBuffer),
_ => DateTime.Today.AddDays(-diasBuffer)
};
}
}