// backend/src/Titulares.Api/Workers/ProcesoScrapingWorker.cs using Microsoft.AspNetCore.SignalR; using Titulares.Api.Data; using Titulares.Api.Hubs; using Titulares.Api.Services; using Microsoft.Extensions.Options; using Titulares.Api.Models; namespace Titulares.Api.Workers; public class ProcesoScrapingWorker : BackgroundService, IDisposable { private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; private readonly IOptionsMonitor _configuracion; private readonly IDisposable? _optionsReloadToken; private CancellationTokenSource? _delayCts; public ProcesoScrapingWorker(ILogger logger, IServiceProvider serviceProvider, IOptionsMonitor configuracion) { _logger = logger; _serviceProvider = serviceProvider; _configuracion = configuracion; _optionsReloadToken = _configuracion.OnChange(OnConfigurationChanged); } private void OnConfigurationChanged(ConfiguracionApp newConfig) { _logger.LogInformation("La configuración ha cambiado. Interrumpiendo la espera actual para aplicar los nuevos ajustes."); _delayCts?.Cancel(); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { var configActual = _configuracion.CurrentValue; _logger.LogInformation("Iniciando ciclo de scraping con cantidad: {cantidad}", configActual.CantidadTitularesAScrapear); try { using (var scope = _serviceProvider.CreateScope()) { var repositorio = scope.ServiceProvider.GetRequiredService(); var scrapingService = scope.ServiceProvider.GetRequiredService(); var hubContext = scope.ServiceProvider.GetRequiredService>(); var csvService = scope.ServiceProvider.GetRequiredService(); int cantidadTitulares = configActual.CantidadTitularesAScrapear; var articulosScrapeados = await scrapingService.ObtenerUltimosTitulares(cantidadTitulares); await repositorio.SincronizarDesdeScraping(articulosScrapeados, cantidadTitulares); _logger.LogInformation("Sincronización con la base de datos completada."); var titularesActualizados = await repositorio.ObtenerTodosAsync(); await hubContext.Clients.All.SendAsync("TitularesActualizados", titularesActualizados, stoppingToken); _logger.LogInformation("Notificación enviada a los clientes."); await csvService.GenerarCsvAsync(titularesActualizados); } } catch (Exception ex) { _logger.LogError(ex, "Ocurrió un error durante el proceso de scraping."); } var intervaloEnMinutos = configActual.IntervaloMinutos; _logger.LogInformation("Proceso en espera por {minutos} minutos.", intervaloEnMinutos); try { _delayCts = new CancellationTokenSource(); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, _delayCts.Token); await Task.Delay(TimeSpan.FromMinutes(intervaloEnMinutos), linkedCts.Token); } catch (TaskCanceledException) { _logger.LogInformation("La espera fue interrumpida. Reiniciando el ciclo."); } finally { _delayCts?.Dispose(); _delayCts = null; } } } public override void Dispose() { _optionsReloadToken?.Dispose(); _delayCts?.Dispose(); base.Dispose(); } }