diff --git a/src/Mercados.Worker/DataFetchingService.cs b/src/Mercados.Worker/DataFetchingService.cs index 2cac2d2..4d56408 100644 --- a/src/Mercados.Worker/DataFetchingService.cs +++ b/src/Mercados.Worker/DataFetchingService.cs @@ -1,4 +1,5 @@ using Mercados.Infrastructure.DataFetchers; +using Cronos; namespace Mercados.Worker { @@ -11,15 +12,17 @@ namespace Mercados.Worker private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; private readonly TimeZoneInfo _argentinaTimeZone; + private readonly IConfiguration _configuration; // Diccionario para rastrear la última vez que se ejecutó una tarea diaria // y evitar que se ejecute múltiples veces si el servicio se reinicia. private readonly Dictionary _lastDailyRun = new(); - public DataFetchingService(ILogger logger, IServiceProvider serviceProvider) + public DataFetchingService(ILogger logger, IServiceProvider serviceProvider,IConfiguration configuration) { _logger = logger; _serviceProvider = serviceProvider; + _configuration = configuration; // Se define explícitamente la zona horaria de Argentina. // Esto asegura que los cálculos de tiempo sean correctos, sin importar @@ -63,33 +66,78 @@ namespace Mercados.Worker /// private async Task RunScheduledTasksAsync(CancellationToken stoppingToken) { - // Se obtiene la hora actual convertida a la zona horaria de Argentina. - var nowInArgentina = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, _argentinaTimeZone); + var utcNow = DateTime.UtcNow; - // --- Tarea 1: Mercado Agroganadero (L-V a las 11:00 AM) --- - if (IsWeekDay(nowInArgentina) && nowInArgentina.Hour == 11 && nowInArgentina.Minute == 0 && HasNotRunToday("MercadoAgroganadero")) + // Obtenemos las expresiones Cron desde la configuración + string? agroSchedule = _configuration["Schedules:MercadoAgroganadero"]; + string? bcrSchedule = _configuration["Schedules:BCR"]; + string? bolsasSchedule = _configuration["Schedules:Bolsas"]; + + // Comprobamos cada una antes de usarla + if (!string.IsNullOrEmpty(agroSchedule)) { - await RunFetcherByNameAsync("MercadoAgroganadero", stoppingToken); - _lastDailyRun["MercadoAgroganadero"] = nowInArgentina.Date; + await TryRunDailyTaskAsync("MercadoAgroganadero", agroSchedule, utcNow, stoppingToken); + } + else + { + _logger.LogWarning("No se encontró la configuración de horario para 'MercadoAgroganadero' en appsettings.json."); } - // --- Tarea 2: Granos BCR (L-V a las 11:30 AM) --- - if (IsWeekDay(nowInArgentina) && nowInArgentina.Hour == 11 && nowInArgentina.Minute == 30 && HasNotRunToday("BCR")) + if (!string.IsNullOrEmpty(bcrSchedule)) { - await RunFetcherByNameAsync("BCR", stoppingToken); - _lastDailyRun["BCR"] = nowInArgentina.Date; + await TryRunDailyTaskAsync("BCR", bcrSchedule, utcNow, stoppingToken); + } + else + { + _logger.LogWarning("No se encontró la configuración de horario para 'BCR' en appsettings.json."); } - // --- Tarea 3 y 4: Mercados de Bolsa (L-V, durante horario de mercado, una vez por hora) --- - // Se ejecutan si el mercado está abierto y si el minuto actual es exactamente 10. - // Esto replica la lógica de "cada hora a las y 10". - if (IsArgentineMarketOpen(nowInArgentina) && nowInArgentina.Minute == 10) + if (!string.IsNullOrEmpty(bolsasSchedule)) { - _logger.LogInformation("Hora de actualización de mercados de bolsa. Ejecutando fetchers..."); - - await RunFetcherByNameAsync("YahooFinance", stoppingToken); - // Si Finnhub está desactivado en Program.cs, este simplemente no se encontrará y se omitirá. - await RunFetcherByNameAsync("Finnhub", stoppingToken); + await TryRunRecurringTaskAsync(new[] { "YahooFinance", "Finnhub" }, bolsasSchedule, utcNow, stoppingToken); + } + else + { + _logger.LogWarning("No se encontró la configuración de horario para 'Bolsas' en appsettings.json."); + } + // --- ^ FIN DE LA CORRECCIÓN DE NULABILIDAD ^ --- + } + + /// + /// Comprueba y ejecuta una tarea que debe correr solo una vez al día. + /// + private async Task TryRunDailyTaskAsync(string taskName, string cronExpression, DateTime utcNow, CancellationToken stoppingToken) + { + var cron = CronExpression.Parse(cronExpression); + var nextOccurrence = cron.GetNextOccurrence(utcNow.AddMinutes(-1)); + + if (nextOccurrence.HasValue && nextOccurrence.Value <= utcNow) + { + if (HasNotRunToday(taskName)) + { + await RunFetcherByNameAsync(taskName, stoppingToken); + _lastDailyRun[taskName] = TimeZoneInfo.ConvertTimeFromUtc(utcNow, _argentinaTimeZone).Date; + } + } + } + + /// + /// Comprueba y ejecuta una tarea que puede correr múltiples veces al día. + /// + private async Task TryRunRecurringTaskAsync(string[] taskNames, string cronExpression, DateTime utcNow, CancellationToken stoppingToken) + { + // Añadimos 'IncludeSeconds' para que la comparación sea precisa y no se ejecute dos veces en el mismo minuto. + var cron = CronExpression.Parse(cronExpression, CronFormat.IncludeSeconds); + // Comprobamos si hubo una ocurrencia en el último minuto. + var nextOccurrence = cron.GetNextOccurrence(utcNow.AddMinutes(-1)); + + if (nextOccurrence.HasValue && nextOccurrence.Value <= utcNow) + { + _logger.LogInformation("Ventana de ejecución recurrente detectada para: {Tasks}", string.Join(", ", taskNames)); + foreach (var taskName in taskNames) + { + await RunFetcherByNameAsync(taskName, stoppingToken); + } } } @@ -145,26 +193,9 @@ namespace Mercados.Worker private bool HasNotRunToday(string taskName) { - // Comprueba si la tarea ya se ejecutó en la fecha actual (en zona horaria de Argentina). return !_lastDailyRun.ContainsKey(taskName) || _lastDailyRun[taskName].Date < TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, _argentinaTimeZone).Date; } - private bool IsWeekDay(DateTime now) - { - return now.DayOfWeek >= DayOfWeek.Monday && now.DayOfWeek <= DayOfWeek.Friday; - } - - private bool IsArgentineMarketOpen(DateTime now) - { - if (!IsWeekDay(now)) return false; - - // Rango de 11:00 a 17:15, para asegurar la captura del cierre a las 17:10. - var marketOpen = new TimeSpan(11, 0, 0); - var marketClose = new TimeSpan(17, 15, 0); - - return now.TimeOfDay >= marketOpen && now.TimeOfDay <= marketClose; - } - #endregion } } \ No newline at end of file diff --git a/src/Mercados.Worker/Mercados.Worker.csproj b/src/Mercados.Worker/Mercados.Worker.csproj index 25c5b8d..a9b5af5 100644 --- a/src/Mercados.Worker/Mercados.Worker.csproj +++ b/src/Mercados.Worker/Mercados.Worker.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Mercados.Worker/appsettings.json b/src/Mercados.Worker/appsettings.json index 6c56bd7..baf189e 100644 --- a/src/Mercados.Worker/appsettings.json +++ b/src/Mercados.Worker/appsettings.json @@ -9,6 +9,11 @@ "ConnectionStrings": { "DefaultConnection": "" }, + "Schedules": { + "MercadoAgroganadero": "0 11 * * 1-5", + "BCR": "30 11 * * 1-5", + "Bolsas": "10 11-17 * * 1-5" + }, "ApiKeys": { "Finnhub": "", "Bcr": {