Files
Elecciones-2025/Elecciones-Web/src/Elecciones.Worker/CriticalDataWorker.cs

681 lines
30 KiB
C#
Raw Normal View History

//Elecciones.Worker/CriticalDataWorker.cs
2025-08-20 16:58:18 -03:00
using Elecciones.Database;
using Elecciones.Database.Entities;
using Elecciones.Infrastructure.Services;
using Microsoft.EntityFrameworkCore;
using System.Collections.Concurrent;
namespace Elecciones.Worker;
public class CriticalDataWorker : BackgroundService
{
2025-10-16 15:46:44 -03:00
private const int EleccionId = 2;
2025-08-20 16:58:18 -03:00
private readonly ILogger<CriticalDataWorker> _logger;
private readonly SharedTokenService _tokenService;
private readonly IServiceProvider _serviceProvider;
2025-08-23 11:01:54 -03:00
private readonly IElectoralApiService _apiService;
private readonly WorkerConfigService _configService;
2025-08-20 16:58:18 -03:00
public CriticalDataWorker(
ILogger<CriticalDataWorker> logger,
SharedTokenService tokenService,
IServiceProvider serviceProvider,
IElectoralApiService apiService,
WorkerConfigService configService)
2025-08-20 16:58:18 -03:00
{
_logger = logger;
_tokenService = tokenService;
_serviceProvider = serviceProvider;
2025-08-23 11:01:54 -03:00
_apiService = apiService;
_configService = configService;
2025-08-20 16:58:18 -03:00
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Worker de Datos Críticos iniciado.");
try
{
await Task.Delay(TimeSpan.FromMinutes(2), stoppingToken);
}
2025-08-23 11:01:54 -03:00
catch (TaskCanceledException) { return; }
2025-08-20 16:58:18 -03:00
int cicloContador = 0;
while (!stoppingToken.IsCancellationRequested)
{
var cicloInicio = DateTime.UtcNow;
cicloContador++;
_logger.LogInformation("--- Iniciando Ciclo de Datos Críticos #{ciclo} ---", cicloContador);
var authToken = await _tokenService.GetValidAuthTokenAsync(stoppingToken);
if (string.IsNullOrEmpty(authToken))
{
_logger.LogError("Ciclo Crítico: No se pudo obtener token. Reintentando en 30 segundos.");
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
continue;
}
var settings = await _configService.GetSettingsAsync();
if (settings.Prioridad == "Resultados" && settings.ResultadosActivado)
{
_logger.LogInformation("Ejecutando tareas de Resultados en alta prioridad.");
await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
await SondearResumenProvincialAsync(authToken, stoppingToken);
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
}
else if (settings.Prioridad == "Telegramas" && settings.BajasActivado)
{
_logger.LogInformation("Ejecutando tareas de Baja Prioridad en alta prioridad.");
await SondearProyeccionBancasAsync(authToken, stoppingToken);
2025-10-17 10:12:12 -03:00
//await SondearNuevosTelegramasAsync(authToken, stoppingToken);
}
else
{
_logger.LogInformation("Worker de alta prioridad inactivo según la configuración.");
}
2025-08-20 16:58:18 -03:00
var cicloFin = DateTime.UtcNow;
var duracionCiclo = cicloFin - cicloInicio;
_logger.LogInformation("--- Ciclo de Datos Críticos #{ciclo} completado en {duration:N2} segundos. ---", cicloContador, duracionCiclo.TotalSeconds);
var tiempoDeEspera = TimeSpan.FromSeconds(30) - duracionCiclo;
if (tiempoDeEspera < TimeSpan.Zero) tiempoDeEspera = TimeSpan.Zero;
try
{
await Task.Delay(tiempoDeEspera, stoppingToken);
}
2025-08-23 11:01:54 -03:00
catch (TaskCanceledException) { break; }
2025-08-20 16:58:18 -03:00
}
}
/// <summary>
/// Sondea la proyección de bancas a nivel Provincial y por Sección Electoral.
/// Esta versión es completamente robusta: maneja respuestas de API vacías o con fechas mal formadas,
/// guarda la CategoriaId y usa una transacción atómica para la escritura en base de datos.
/// </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 SondearProyeccionBancasAsync(string authToken, CancellationToken stoppingToken)
{
try
{
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
var categoriasDeBancas = await dbContext.CategoriasElectorales
.AsNoTracking()
.Where(c => c.Nombre.Contains("SENADORES") || c.Nombre.Contains("DIPUTADOS"))
.ToListAsync(stoppingToken);
2025-10-17 12:13:45 -03:00
// --- MODIFICACIÓN 1: Obtener todos los ámbitos en una sola consulta ---
var ambitosASondear = await dbContext.AmbitosGeograficos
.AsNoTracking()
2025-10-17 12:13:45 -03:00
.Where(a => (a.NivelId == 10 || a.NivelId == 20) && a.DistritoId != null)
.ToListAsync(stoppingToken);
2025-10-17 12:13:45 -03:00
var provincia = ambitosASondear.FirstOrDefault(a => a.NivelId == 10);
var seccionesElectorales = ambitosASondear.Where(a => a.NivelId == 20).ToList();
if (!categoriasDeBancas.Any() || provincia == null)
{
_logger.LogWarning("No se encontraron categorías de bancas o el ámbito provincial en la BD. Omitiendo sondeo de bancas.");
return;
}
_logger.LogInformation("Iniciando sondeo de Bancas a nivel Provincial y para {count} Secciones Electorales...", seccionesElectorales.Count);
var todasLasProyecciones = new List<ProyeccionBanca>();
bool hasReceivedAnyNewData = false;
2025-10-17 12:13:45 -03:00
// --- MODIFICACIÓN 2: Usar un diccionario para no buscar repetidamente en la BD ---
var agrupacionesEnDb = await dbContext.AgrupacionesPoliticas.ToDictionaryAsync(a => a.Id, a => a, stoppingToken);
2025-10-17 12:13:45 -03:00
// Bucle combinado para todos los ámbitos
foreach (var ambito in ambitosASondear)
{
if (stoppingToken.IsCancellationRequested) break;
2025-10-17 12:13:45 -03:00
foreach (var categoria in categoriasDeBancas)
{
if (stoppingToken.IsCancellationRequested) break;
2025-10-17 12:13:45 -03:00
// Llamada a la API (lógica adaptada para ambos niveles)
var repartoBancasDto = await _apiService.GetBancasAsync(authToken, ambito.DistritoId!, ambito.SeccionProvincialId, categoria.Id);
if (repartoBancasDto?.RepartoBancas is { Count: > 0 } bancas)
{
hasReceivedAnyNewData = true;
DateTime fechaTotalizacion;
if (!DateTime.TryParse(repartoBancasDto.FechaTotalizacion, out var parsedDate))
{
2025-10-17 12:13:45 -03:00
_logger.LogWarning("No se pudo parsear FechaTotalizacion ('{dateString}') para bancas. Usando la hora actual.", repartoBancasDto.FechaTotalizacion);
fechaTotalizacion = DateTime.UtcNow;
}
else
{
fechaTotalizacion = parsedDate.ToUniversalTime();
}
foreach (var banca in bancas)
{
2025-10-17 12:13:45 -03:00
// --- MODIFICACIÓN 3: Lógica de "Upsert" para Agrupaciones ---
if (!agrupacionesEnDb.ContainsKey(banca.IdAgrupacion))
{
_logger.LogWarning("Agrupación con ID {AgrupacionId} ('{Nombre}') no encontrada. Creándola desde los datos de bancas.", banca.IdAgrupacion, banca.NombreAgrupacion);
var nuevaAgrupacion = new AgrupacionPolitica
{
Id = banca.IdAgrupacion,
Nombre = banca.NombreAgrupacion,
IdTelegrama = banca.IdAgrupacionTelegrama ?? string.Empty
};
await dbContext.AgrupacionesPoliticas.AddAsync(nuevaAgrupacion, stoppingToken);
agrupacionesEnDb.Add(nuevaAgrupacion.Id, nuevaAgrupacion); // Añadir al diccionario para no volver a crearla
}
todasLasProyecciones.Add(new ProyeccionBanca
{
2025-10-16 15:46:44 -03:00
EleccionId = EleccionId,
2025-10-17 12:13:45 -03:00
AmbitoGeograficoId = ambito.Id,
AgrupacionPoliticaId = banca.IdAgrupacion,
NroBancas = banca.NroBancas,
CategoriaId = categoria.Id,
FechaTotalizacion = fechaTotalizacion
});
}
}
}
}
if (hasReceivedAnyNewData)
{
_logger.LogInformation("Se recibieron datos válidos de bancas. Procediendo a actualizar la base de datos...");
await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
2025-10-17 12:13:45 -03:00
// Si se crearon nuevas agrupaciones, se guardarán aquí primero.
await dbContext.SaveChangesAsync(stoppingToken);
// Luego, procedemos con las proyecciones.
await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ProyeccionesBancas", stoppingToken);
await dbContext.ProyeccionesBancas.AddRangeAsync(todasLasProyecciones, stoppingToken);
await dbContext.SaveChangesAsync(stoppingToken);
await transaction.CommitAsync(stoppingToken);
_logger.LogInformation("La tabla de proyecciones ha sido actualizada con {count} registros.", todasLasProyecciones.Count);
}
else
{
2025-10-17 12:13:45 -03:00
_logger.LogInformation("Sondeo de Bancas completado. No se encontraron datos nuevos, la tabla no fue modificada.");
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Sondeo de bancas cancelado.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Bancas.");
}
}
/// <summary>
/// Busca y descarga nuevos telegramas de forma masiva y concurrente.
/// Este método crea una lista de todas las combinaciones de Partido/Categoría,
/// las consulta a la API con un grado de paralelismo controlado, y cada tarea concurrente
/// maneja su propia lógica de descarga y guardado en la base de datos.
/// </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 SondearNuevosTelegramasAsync(string authToken, CancellationToken stoppingToken)
{
try
{
_logger.LogInformation("--- Iniciando sondeo de Nuevos Telegramas (modo de bajo perfil) ---");
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
2025-09-08 14:25:19 -03:00
// La obtención de partidos y categorías no cambia
var partidos = await dbContext.AmbitosGeograficos.AsNoTracking()
.Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null)
.ToListAsync(stoppingToken);
2025-09-08 14:25:19 -03:00
var categorias = await dbContext.CategoriasElectorales.AsNoTracking().ToListAsync(stoppingToken);
if (!partidos.Any() || !categorias.Any()) return;
foreach (var partido in partidos)
{
foreach (var categoria in categorias)
{
if (stoppingToken.IsCancellationRequested) return;
var listaTelegramasApi = await _apiService.GetTelegramasTotalizadosAsync(authToken, partido.DistritoId!, partido.SeccionId!, categoria.Id);
if (listaTelegramasApi is { Count: > 0 })
{
2025-09-08 14:25:19 -03:00
// Creamos el DbContext para la operación de guardado
using var innerScope = _serviceProvider.CreateScope();
var innerDbContext = innerScope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
var idsYaEnDb = await innerDbContext.Telegramas
.Where(t => listaTelegramasApi.Contains(t.Id))
2025-09-08 14:25:19 -03:00
.Select(t => t.Id).ToListAsync(stoppingToken);
var nuevosTelegramasIds = listaTelegramasApi.Except(idsYaEnDb).ToList();
if (nuevosTelegramasIds.Any())
{
_logger.LogInformation("Se encontraron {count} telegramas nuevos en '{partido}' para '{cat}'. Descargando...", nuevosTelegramasIds.Count, partido.Nombre, categoria.Nombre);
2025-09-08 14:25:19 -03:00
var originalTimeout = innerDbContext.Database.GetCommandTimeout();
try
{
2025-09-08 14:25:19 -03:00
innerDbContext.Database.SetCommandTimeout(180);
_logger.LogDebug("Timeout de BD aumentado a 180s para descarga de telegramas.");
int contadorLote = 0;
const int tamanoLote = 100;
2025-09-08 14:25:19 -03:00
foreach (var mesaId in nuevosTelegramasIds)
{
2025-09-08 14:25:19 -03:00
if (stoppingToken.IsCancellationRequested) return;
2025-09-08 14:25:19 -03:00
var telegramaFile = await _apiService.GetTelegramaFileAsync(authToken, mesaId);
if (telegramaFile != null)
{
2025-09-08 14:25:19 -03:00
var ambitoMesa = await innerDbContext.AmbitosGeograficos.AsNoTracking()
.FirstOrDefaultAsync(a => a.MesaId == mesaId, stoppingToken);
if (ambitoMesa != null)
{
2025-09-08 14:25:19 -03:00
var nuevoTelegrama = new Telegrama
{
2025-10-16 15:46:44 -03:00
EleccionId = EleccionId,
2025-09-08 14:25:19 -03:00
Id = telegramaFile.NombreArchivo,
AmbitoGeograficoId = ambitoMesa.Id,
ContenidoBase64 = telegramaFile.Imagen,
FechaEscaneo = DateTime.Parse(telegramaFile.FechaEscaneo).ToUniversalTime(),
FechaTotalizacion = DateTime.Parse(telegramaFile.FechaTotalizacion).ToUniversalTime()
};
await innerDbContext.Telegramas.AddAsync(nuevoTelegrama, stoppingToken);
contadorLote++;
}
else
{
_logger.LogWarning("No se encontró un ámbito geográfico para la mesa con MesaId {MesaId}. El telegrama no será guardado.", mesaId);
}
}
2025-09-08 14:25:19 -03:00
await Task.Delay(250, stoppingToken);
if (contadorLote >= tamanoLote)
{
2025-09-08 14:25:19 -03:00
await innerDbContext.SaveChangesAsync(stoppingToken);
_logger.LogInformation("Guardado un lote de {count} telegramas.", contadorLote);
contadorLote = 0;
}
}
2025-09-08 10:22:51 -03:00
2025-09-08 14:25:19 -03:00
if (contadorLote > 0)
2025-09-08 10:22:51 -03:00
{
await innerDbContext.SaveChangesAsync(stoppingToken);
2025-09-08 14:25:19 -03:00
_logger.LogInformation("Guardado el último lote de {count} telegramas.", contadorLote);
2025-09-08 10:22:51 -03:00
}
}
2025-09-08 14:25:19 -03:00
finally
2025-09-08 10:22:51 -03:00
{
2025-09-08 14:25:19 -03:00
innerDbContext.Database.SetCommandTimeout(originalTimeout);
_logger.LogDebug("Timeout de BD restaurado a su valor original ({timeout}s).", originalTimeout);
}
2025-09-08 14:25:19 -03:00
} // Fin del if (nuevosTelegramasIds.Any())
// Movemos el delay aquí para que solo se ejecute si hubo telegramas en la respuesta de la API
await Task.Delay(100, stoppingToken);
} // Fin del if (listaTelegramasApi is not null)
}
}
_logger.LogInformation("Sondeo de Telegramas completado.");
}
catch (OperationCanceledException)
{
_logger.LogInformation("Sondeo de telegramas cancelado.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Telegramas.");
}
}
2025-08-20 16:58:18 -03:00
private async Task SondearResultadosMunicipalesAsync(string authToken, CancellationToken stoppingToken)
{
try
{
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
var municipiosASondear = await dbContext.AmbitosGeograficos
.AsNoTracking()
.Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null)
.ToListAsync(stoppingToken);
2025-08-23 11:01:54 -03:00
var todasLasCategorias = await dbContext.CategoriasElectorales
2025-08-20 16:58:18 -03:00
.AsNoTracking()
2025-08-23 11:01:54 -03:00
.ToListAsync(stoppingToken);
2025-08-20 16:58:18 -03:00
2025-08-23 11:01:54 -03:00
if (!municipiosASondear.Any() || !todasLasCategorias.Any())
2025-08-20 16:58:18 -03:00
{
2025-08-23 11:01:54 -03:00
_logger.LogWarning("No se encontraron Partidos (NivelId 30) o Categorías para sondear resultados.");
2025-08-20 16:58:18 -03:00
return;
}
2025-08-23 11:01:54 -03:00
_logger.LogInformation("Iniciando sondeo de resultados para {m} municipios y {c} categorías...", municipiosASondear.Count, todasLasCategorias.Count);
2025-08-20 16:58:18 -03:00
2025-08-23 11:01:54 -03:00
foreach (var municipio in municipiosASondear)
2025-08-20 16:58:18 -03:00
{
2025-08-23 11:01:54 -03:00
if (stoppingToken.IsCancellationRequested) break;
var tareasCategoria = todasLasCategorias.Select(async categoria =>
2025-08-20 16:58:18 -03:00
{
2025-08-23 11:01:54 -03:00
var resultados = await _apiService.GetResultadosAsync(authToken, municipio.DistritoId!, municipio.SeccionId!, null, categoria.Id);
2025-08-20 16:58:18 -03:00
if (resultados != null)
{
2025-08-23 11:01:54 -03:00
using var innerScope = _serviceProvider.CreateScope();
var innerDbContext = innerScope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
2025-08-20 16:58:18 -03:00
2025-08-23 11:01:54 -03:00
// --- LLAMADA CORRECTA ---
await GuardarResultadosDeAmbitoAsync(innerDbContext, municipio.Id, categoria.Id, resultados, stoppingToken);
}
});
2025-08-20 16:58:18 -03:00
2025-08-23 11:01:54 -03:00
await Task.WhenAll(tareasCategoria);
2025-08-20 16:58:18 -03:00
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ocurrió un error inesperado durante el sondeo de resultados municipales.");
}
}
2025-08-23 11:01:54 -03:00
private async Task GuardarResultadosDeAmbitoAsync(
EleccionesDbContext dbContext, int ambitoId, int categoriaId,
Elecciones.Core.DTOs.ResultadosDto resultadosDto, CancellationToken stoppingToken)
2025-08-20 16:58:18 -03:00
{
2025-08-23 11:01:54 -03:00
var estadoRecuento = await dbContext.EstadosRecuentos.FindAsync(new object[] { ambitoId, categoriaId }, stoppingToken);
2025-08-20 16:58:18 -03:00
2025-08-23 11:01:54 -03:00
if (estadoRecuento == null)
2025-08-20 16:58:18 -03:00
{
2025-10-16 15:46:44 -03:00
estadoRecuento = new EstadoRecuento { EleccionId = EleccionId, AmbitoGeograficoId = ambitoId, CategoriaId = categoriaId };
2025-08-23 11:01:54 -03:00
dbContext.EstadosRecuentos.Add(estadoRecuento);
}
2025-08-20 16:58:18 -03:00
2025-08-23 11:01:54 -03:00
estadoRecuento.FechaTotalizacion = DateTime.Parse(resultadosDto.FechaTotalizacion).ToUniversalTime();
estadoRecuento.MesasEsperadas = resultadosDto.EstadoRecuento.MesasEsperadas;
estadoRecuento.MesasTotalizadas = resultadosDto.EstadoRecuento.MesasTotalizadas;
estadoRecuento.MesasTotalizadasPorcentaje = resultadosDto.EstadoRecuento.MesasTotalizadasPorcentaje;
estadoRecuento.CantidadElectores = resultadosDto.EstadoRecuento.CantidadElectores;
estadoRecuento.CantidadVotantes = resultadosDto.EstadoRecuento.CantidadVotantes;
estadoRecuento.ParticipacionPorcentaje = resultadosDto.EstadoRecuento.ParticipacionPorcentaje;
2025-08-20 16:58:18 -03:00
2025-08-23 11:01:54 -03:00
if (resultadosDto.ValoresTotalizadosOtros != null)
{
estadoRecuento.VotosEnBlanco = resultadosDto.ValoresTotalizadosOtros.VotosEnBlanco;
estadoRecuento.VotosEnBlancoPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosEnBlancoPorcentaje;
estadoRecuento.VotosNulos = resultadosDto.ValoresTotalizadosOtros.VotosNulos;
estadoRecuento.VotosNulosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosNulosPorcentaje;
estadoRecuento.VotosRecurridos = resultadosDto.ValoresTotalizadosOtros.VotosRecurridos;
estadoRecuento.VotosRecurridosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosRecurridosPorcentaje;
2025-10-15 11:44:22 -03:00
estadoRecuento.VotosComando = resultadosDto.ValoresTotalizadosOtros.VotosComando;
estadoRecuento.VotosComandoPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosComandoPorcentaje;
estadoRecuento.VotosImpugnados = resultadosDto.ValoresTotalizadosOtros.VotosImpugnados;
estadoRecuento.VotosImpugnadosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosImpugnadosPorcentaje;
2025-08-23 11:01:54 -03:00
}
2025-08-20 16:58:18 -03:00
2025-08-23 11:01:54 -03:00
foreach (var votoPositivoDto in resultadosDto.ValoresTotalizadosPositivos)
{
2025-10-16 15:34:12 -03:00
// PASO 1: VERIFICAR SI LA AGRUPACIÓN YA EXISTE EN NUESTRA BD
var agrupacion = await dbContext.AgrupacionesPoliticas.FindAsync(votoPositivoDto.IdAgrupacion);
// PASO 2: SI NO EXISTE, LA CREAMOS "SOBRE LA MARCHA"
if (agrupacion == null)
{
_logger.LogWarning("Agrupación con ID {AgrupacionId} ('{Nombre}') no encontrada en el catálogo local. Creándola desde los datos de resultados.",
votoPositivoDto.IdAgrupacion, votoPositivoDto.NombreAgrupacion);
agrupacion = new AgrupacionPolitica
{
Id = votoPositivoDto.IdAgrupacion,
Nombre = votoPositivoDto.NombreAgrupacion,
// El IdTelegrama puede ser nulo, usamos el operador '??' para asignar un string vacío si es así.
IdTelegrama = votoPositivoDto.IdAgrupacionTelegrama ?? string.Empty
};
await dbContext.AgrupacionesPoliticas.AddAsync(agrupacion, stoppingToken);
// No es necesario llamar a SaveChangesAsync aquí, se hará al final.
}
// PASO 3: CONTINUAR CON LA LÓGICA DE GUARDADO DEL VOTO
2025-08-23 11:01:54 -03:00
var resultadoVoto = await dbContext.ResultadosVotos.FirstOrDefaultAsync(
rv => rv.AmbitoGeograficoId == ambitoId &&
rv.CategoriaId == categoriaId &&
rv.AgrupacionPoliticaId == votoPositivoDto.IdAgrupacion,
stoppingToken
);
if (resultadoVoto == null)
2025-08-20 16:58:18 -03:00
{
2025-08-23 11:01:54 -03:00
resultadoVoto = new ResultadoVoto
2025-08-20 16:58:18 -03:00
{
2025-10-16 15:46:44 -03:00
EleccionId = EleccionId,
2025-08-23 11:01:54 -03:00
AmbitoGeograficoId = ambitoId,
CategoriaId = categoriaId,
AgrupacionPoliticaId = votoPositivoDto.IdAgrupacion
};
dbContext.ResultadosVotos.Add(resultadoVoto);
2025-08-20 16:58:18 -03:00
}
2025-08-23 11:01:54 -03:00
resultadoVoto.CantidadVotos = votoPositivoDto.Votos;
resultadoVoto.PorcentajeVotos = votoPositivoDto.VotosPorcentaje;
2025-08-20 16:58:18 -03:00
}
2025-08-23 11:01:54 -03:00
try
{
await dbContext.SaveChangesAsync(stoppingToken);
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, "DbUpdateException al guardar resultados para AmbitoId {ambitoId} y CategoriaId {categoriaId}", ambitoId, categoriaId);
}
2025-08-20 16:58:18 -03:00
}
/// <summary>
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
/// Obtiene y actualiza el resumen de votos y el estado del recuento a nivel provincial para CADA categoría.
/// Este método itera sobre todas las provincias y categorías, obteniendo sus resultados consolidados
/// y guardándolos en las tablas 'ResumenesVotos' y 'EstadosRecuentosGenerales'.
2025-08-20 16:58:18 -03:00
/// </summary>
private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken)
{
try
{
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
_logger.LogInformation("Iniciando sondeo de Resúmenes Provinciales...");
2025-08-20 16:58:18 -03:00
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
var provinciasASondear = await dbContext.AmbitosGeograficos
2025-08-23 13:19:35 -03:00
.AsNoTracking()
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
.Where(a => a.NivelId == 10 && a.DistritoId != null)
.ToListAsync(stoppingToken);
2025-08-23 13:19:35 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
var todasLasCategorias = await dbContext.CategoriasElectorales
.AsNoTracking()
.ToListAsync(stoppingToken);
if (!provinciasASondear.Any() || !todasLasCategorias.Any())
2025-08-23 13:19:35 -03:00
{
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
_logger.LogWarning("No se encontraron Provincias o Categorías para sondear resúmenes.");
2025-08-23 13:19:35 -03:00
return;
}
2025-08-20 16:58:18 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
foreach (var provincia in provinciasASondear)
2025-08-20 16:58:18 -03:00
{
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
if (stoppingToken.IsCancellationRequested) break;
2025-08-23 13:19:35 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
foreach (var categoria in todasLasCategorias)
2025-08-23 13:19:35 -03:00
{
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
if (stoppingToken.IsCancellationRequested) break;
2025-08-23 13:19:35 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
// Usamos GetResultados sin seccionId/municipioId para obtener el resumen del distrito.
var resultadosDto = await _apiService.GetResultadosAsync(authToken, provincia.DistritoId!, null, null, categoria.Id);
2025-08-23 13:19:35 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
if (resultadosDto?.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos)
{
// Usamos una transacción para asegurar que el borrado y la inserción sean atómicos.
await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
2025-08-20 16:58:18 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
// A. Borrar los resúmenes viejos SOLO para esta provincia y categoría.
await dbContext.ResumenesVotos
.Where(rv => rv.AmbitoGeograficoId == provincia.Id && rv.CategoriaId == categoria.Id)
.ExecuteDeleteAsync(stoppingToken);
2025-08-23 13:19:35 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
// B. Añadir los nuevos resúmenes.
foreach (var voto in nuevosVotos)
{
dbContext.ResumenesVotos.Add(new ResumenVoto
{
2025-10-16 15:46:44 -03:00
EleccionId = EleccionId,
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
AmbitoGeograficoId = provincia.Id,
CategoriaId = categoria.Id,
AgrupacionPoliticaId = voto.IdAgrupacion,
Votos = voto.Votos,
VotosPorcentaje = voto.VotosPorcentaje
});
}
2025-08-23 13:19:35 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
// C. Guardar los cambios en la tabla ResumenesVotos.
await dbContext.SaveChangesAsync(stoppingToken);
2025-08-23 13:19:35 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
// No es necesario actualizar EstadosRecuentosGenerales aquí,
// ya que el método SondearEstadoRecuentoGeneralAsync se encarga de eso
// de forma más específica y eficiente.
2025-08-20 16:58:18 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
await transaction.CommitAsync(stoppingToken);
}
} // Fin bucle categorías
} // Fin bucle provincias
2025-08-20 16:58:18 -03:00
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
_logger.LogInformation("Sondeo de Resúmenes Provinciales completado.");
2025-08-20 16:58:18 -03:00
}
2025-08-23 13:19:35 -03:00
catch (OperationCanceledException)
{
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
_logger.LogInformation("Sondeo de resúmenes provinciales cancelado.");
2025-08-23 13:19:35 -03:00
}
2025-08-20 16:58:18 -03:00
catch (Exception ex)
{
feat(Worker): Adaptación integral para la API de Elecciones Nacionales Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
2025-10-14 16:00:55 -03:00
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Resúmenes Provinciales.");
2025-08-20 16:58:18 -03:00
}
}
/// <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() // 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
{
2025-10-16 15:46:44 -03:00
EleccionId = EleccionId,
2025-08-20 16:58:18 -03:00
AmbitoGeograficoId = provincia.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;
registroDb.CantidadElectores = estadoDto.CantidadElectores;
registroDb.CantidadVotantes = estadoDto.CantidadVotantes;
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.");
}
}
}