diff --git a/Elecciones-Web/src/Elecciones.Worker/Program.cs b/Elecciones-Web/src/Elecciones.Worker/Program.cs
index eb9ff1e..74111b5 100644
--- a/Elecciones-Web/src/Elecciones.Worker/Program.cs
+++ b/Elecciones-Web/src/Elecciones.Worker/Program.cs
@@ -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(() =>
diff --git a/Elecciones-Web/src/Elecciones.Worker/Worker.cs b/Elecciones-Web/src/Elecciones.Worker/Worker.cs
index be5a5b6..00267fc 100644
--- a/Elecciones-Web/src/Elecciones.Worker/Worker.cs
+++ b/Elecciones-Web/src/Elecciones.Worker/Worker.cs
@@ -498,54 +498,84 @@ public class Worker : BackgroundService
}
}
+ ///
+ /// 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.
+ ///
+ /// El token de autenticación válido para la sesión.
+ /// El token de cancelación para detener la operación.
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();
+ // 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.");
}
}