Retry Fix 504 Timeout
This commit is contained in:
@@ -38,14 +38,16 @@ builder.Services.AddHttpClient("ElectoralApiClient", client =>
|
||||
client.BaseAddress = new Uri(baseUrl);
|
||||
}
|
||||
|
||||
// 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.
|
||||
// --- 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);
|
||||
|
||||
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