using Microsoft.EntityFrameworkCore; using GestorFacturas.API.Data; using GestorFacturas.API.Models; using GestorFacturas.API.Services.Interfaces; namespace GestorFacturas.API.Workers; /// /// Worker Service que ejecuta el procesamiento de facturas de forma programada /// public class CronogramaWorker : BackgroundService { private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; // Eliminamos el Timer antiguo public CronogramaWorker( IServiceProvider serviceProvider, ILogger 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(); 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(); // 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) }; } }