Retry Fix 504 Timeout
This commit is contained in:
		| @@ -37,15 +37,17 @@ builder.Services.AddHttpClient("ElectoralApiClient", client => | ||||
|     { | ||||
|         client.BaseAddress = new Uri(baseUrl); | ||||
|     } | ||||
|      | ||||
|     // --- TIMEOUT MÁS LARGO --- | ||||
|     // Aumentamos el tiempo de espera a 90 segundos. | ||||
|     // Esto le dará a las peticiones lentas de la API tiempo suficiente para responder. | ||||
|     client.Timeout = TimeSpan.FromSeconds(90); | ||||
|  | ||||
|     // Limpiamos los headers por defecto y añadimos uno que simula ser un navegador. | ||||
|     // Esto es crucial para pasar a través de Firewalls de Aplicaciones Web (WAFs) | ||||
|     // que bloquean clientes automatizados no reconocidos. | ||||
|     client.DefaultRequestHeaders.Clear(); | ||||
|     client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"); | ||||
|     client.DefaultRequestHeaders.Add("Accept", "*/*"); // Opcional, pero ayuda a parecerse más a Postman/navegador | ||||
|     client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br"); // Opcional | ||||
|     client.DefaultRequestHeaders.Add("Connection", "keep-alive"); // Opcional | ||||
|     client.DefaultRequestHeaders.Add("Accept", "*/*"); | ||||
|     client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br"); | ||||
|     client.DefaultRequestHeaders.Add("Connection", "keep-alive"); | ||||
|  | ||||
| }) | ||||
| .ConfigurePrimaryHttpMessageHandler(() => | ||||
|   | ||||
| @@ -498,54 +498,84 @@ public class Worker : BackgroundService | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Obtiene y actualiza el estado general del recuento a nivel provincial para CADA categoría electoral. | ||||
|     /// Esta versión es robusta: consulta dinámicamente las categorías, usa la clave primaria compuesta | ||||
|     /// de la base de datos y guarda todos los cambios en una única transacción al final. | ||||
|     /// </summary> | ||||
|     /// <param name="authToken">El token de autenticación válido para la sesión.</param> | ||||
|     /// <param name="stoppingToken">El token de cancelación para detener la operación.</param> | ||||
|     private async Task SondearEstadoRecuentoGeneralAsync(string authToken, CancellationToken stoppingToken) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             // PASO 1: Crear un "scope" para obtener una instancia fresca de DbContext. | ||||
|             // Esto es una práctica recomendada para servicios de larga duración para evitar problemas de concurrencia. | ||||
|             using var scope = _serviceProvider.CreateScope(); | ||||
|             var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>(); | ||||
|  | ||||
|             // PASO 2: Obtener el ámbito geográfico de la Provincia. | ||||
|             // Necesitamos este objeto para obtener su 'DistritoId' ("02"), que es requerido por la API. | ||||
|             var provincia = await dbContext.AmbitosGeograficos | ||||
|                 .AsNoTracking() | ||||
|                 .AsNoTracking() // Optimización: Solo necesitamos leer datos, no modificarlos. | ||||
|                 .FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken); | ||||
|  | ||||
|             // Comprobación de seguridad: Si la sincronización inicial falló y no tenemos el registro de la provincia, | ||||
|             // no podemos continuar. Registramos una advertencia y salimos del método. | ||||
|             if (provincia == null) | ||||
|             { | ||||
|                 _logger.LogWarning("No se encontró el ámbito 'Provincia' (NivelId 10) en la BD. Omitiendo sondeo de estado general."); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // PASO 3: Obtener todas las categorías electorales disponibles desde nuestra base de datos. | ||||
|             // Esto hace que el método sea dinámico y no dependa de IDs fijos en el código. | ||||
|             var categoriasParaSondear = await dbContext.CategoriasElectorales | ||||
|                 .AsNoTracking() | ||||
|                 .ToListAsync(stoppingToken); | ||||
|  | ||||
|             if (!categoriasParaSondear.Any()) | ||||
|             { | ||||
|                 _logger.LogWarning("No hay categorías en la BD para sondear el estado general del recuento."); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             _logger.LogInformation("Iniciando sondeo de Estado Recuento General para {count} categorías...", categoriasParaSondear.Count); | ||||
|  | ||||
|             // PASO 4: Iterar sobre cada categoría para obtener su estado de recuento individual. | ||||
|             foreach (var categoria in categoriasParaSondear) | ||||
|             { | ||||
|                 // Salimos limpiamente del bucle si la aplicación se está deteniendo. | ||||
|                 if (stoppingToken.IsCancellationRequested) break; | ||||
|  | ||||
|                 // Llamamos a la API con el distrito y la CATEGORÍA ACTUAL del bucle. | ||||
|                 var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id); | ||||
|  | ||||
|                 // Solo procedemos si la API devolvió datos válidos. | ||||
|                 if (estadoDto != null) | ||||
|                 { | ||||
|                     // Lógica "Upsert" (Update or Insert): | ||||
|                     // Buscamos un registro existente usando la CLAVE PRIMARIA COMPUESTA. | ||||
|                     var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync( | ||||
|                         new object[] { provincia.Id, categoria.Id }, | ||||
|                         cancellationToken: stoppingToken | ||||
|                     ); | ||||
|  | ||||
|                     // Si no se encuentra (FindAsync devuelve null), es un registro nuevo. | ||||
|                     if (registroDb == null) | ||||
|                     { | ||||
|                         // Creamos una nueva instancia de la entidad. | ||||
|                         registroDb = new EstadoRecuentoGeneral | ||||
|                         { | ||||
|                             AmbitoGeograficoId = provincia.Id, | ||||
|                             CategoriaId = categoria.Id | ||||
|                             CategoriaId = categoria.Id // Asignamos ambas partes de la clave primaria. | ||||
|                         }; | ||||
|                         // Y la añadimos al ChangeTracker de EF para que la inserte en la BD. | ||||
|                         dbContext.EstadosRecuentosGenerales.Add(registroDb); | ||||
|                     } | ||||
|  | ||||
|                     // Mapeamos los datos del DTO de la API a nuestra entidad de base de datos. | ||||
|                     // Esto se hace tanto para registros nuevos como para los existentes que se van a actualizar. | ||||
|                     registroDb.MesasEsperadas = estadoDto.MesasEsperadas; | ||||
|                     registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas; | ||||
|                     registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje; | ||||
| @@ -554,11 +584,16 @@ public class Worker : BackgroundService | ||||
|                     registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // PASO 5: Guardar todos los cambios en la base de datos. | ||||
|             // Al llamar a SaveChangesAsync UNA SOLA VEZ fuera del bucle, EF Core agrupa | ||||
|             // todas las inserciones y actualizaciones en una única transacción eficiente. | ||||
|             await dbContext.SaveChangesAsync(stoppingToken); | ||||
|             _logger.LogInformation("Sondeo de Estado Recuento General completado para todas las categorías."); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             // Capturamos cualquier excepción inesperada para que no detenga el worker y la registramos. | ||||
|             _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General."); | ||||
|         } | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user