Feat CarouselNacional y Fix Workers
This commit is contained in:
@@ -60,9 +60,9 @@ public class CriticalDataWorker : BackgroundService
|
||||
if (settings.Prioridad == "Resultados" && settings.ResultadosActivado)
|
||||
{
|
||||
_logger.LogInformation("Ejecutando tareas de Resultados en alta prioridad.");
|
||||
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
||||
await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
|
||||
await SondearResumenProvincialAsync(authToken, stoppingToken);
|
||||
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
||||
}
|
||||
else if (settings.Prioridad == "Telegramas" && settings.BajasActivado)
|
||||
{
|
||||
@@ -588,92 +588,110 @@ public class CriticalDataWorker : BackgroundService
|
||||
{
|
||||
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);
|
||||
var provinciasASondear = await dbContext.AmbitosGeograficos
|
||||
.AsNoTracking()
|
||||
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
||||
.ToListAsync(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;
|
||||
}
|
||||
// Busca NivelId 1 (País) o 0 como fallback.
|
||||
var ambitoNacional = await dbContext.AmbitosGeograficos
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(a => a.NivelId == 1 || a.NivelId == 0, stoppingToken);
|
||||
|
||||
// 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())
|
||||
if (!provinciasASondear.Any() || !categoriasParaSondear.Any())
|
||||
{
|
||||
_logger.LogWarning("No hay categorías en la BD para sondear el estado general del recuento.");
|
||||
_logger.LogWarning("No se encontraron Provincias o Categorías para sondear estado general.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Iniciando sondeo de Estado Recuento General para {count} categorías...", categoriasParaSondear.Count);
|
||||
_logger.LogInformation("Iniciando sondeo de Estado Recuento General para {provCount} provincias, el total nacional y {catCount} categorías...", provinciasASondear.Count, categoriasParaSondear.Count);
|
||||
|
||||
// PASO 4: Iterar sobre cada categoría para obtener su estado de recuento individual.
|
||||
foreach (var categoria in categoriasParaSondear)
|
||||
// Sondeo a nivel provincial
|
||||
foreach (var provincia in provinciasASondear)
|
||||
{
|
||||
// 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)
|
||||
foreach (var categoria in categoriasParaSondear)
|
||||
{
|
||||
// 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
|
||||
);
|
||||
if (stoppingToken.IsCancellationRequested) break;
|
||||
|
||||
// Si no se encuentra (FindAsync devuelve null), es un registro nuevo.
|
||||
if (registroDb == null)
|
||||
var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id);
|
||||
if (estadoDto != null)
|
||||
{
|
||||
// Creamos una nueva instancia de la entidad.
|
||||
registroDb = new EstadoRecuentoGeneral
|
||||
var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { provincia.Id, categoria.Id }, stoppingToken);
|
||||
if (registroDb == null)
|
||||
{
|
||||
EleccionId = EleccionId,
|
||||
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);
|
||||
registroDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id };
|
||||
dbContext.EstadosRecuentosGenerales.Add(registroDb);
|
||||
}
|
||||
registroDb.FechaTotalizacion = DateTime.UtcNow;
|
||||
registroDb.MesasEsperadas = estadoDto.MesasEsperadas;
|
||||
registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas;
|
||||
registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje;
|
||||
registroDb.CantidadElectores = estadoDto.CantidadElectores;
|
||||
registroDb.CantidadVotantes = estadoDto.CantidadVotantes;
|
||||
registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje;
|
||||
}
|
||||
|
||||
// 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.");
|
||||
// Bloque para el sondeo a nivel nacional
|
||||
if (ambitoNacional != null && !stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogInformation("Sondeando totales a nivel Nacional (Ambito ID: {ambitoId})...", ambitoNacional.Id);
|
||||
foreach (var categoria in categoriasParaSondear)
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested) break;
|
||||
|
||||
var estadoNacionalDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, "", categoria.Id);
|
||||
|
||||
if (estadoNacionalDto != null)
|
||||
{
|
||||
var registroNacionalDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { ambitoNacional.Id, categoria.Id }, stoppingToken);
|
||||
if (registroNacionalDb == null)
|
||||
{
|
||||
registroNacionalDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = ambitoNacional.Id, CategoriaId = categoria.Id };
|
||||
dbContext.EstadosRecuentosGenerales.Add(registroNacionalDb);
|
||||
}
|
||||
registroNacionalDb.FechaTotalizacion = DateTime.UtcNow;
|
||||
registroNacionalDb.MesasEsperadas = estadoNacionalDto.MesasEsperadas;
|
||||
registroNacionalDb.MesasTotalizadas = estadoNacionalDto.MesasTotalizadas;
|
||||
registroNacionalDb.MesasTotalizadasPorcentaje = estadoNacionalDto.MesasTotalizadasPorcentaje;
|
||||
registroNacionalDb.CantidadElectores = estadoNacionalDto.CantidadElectores;
|
||||
registroNacionalDb.CantidadVotantes = estadoNacionalDto.CantidadVotantes;
|
||||
registroNacionalDb.ParticipacionPorcentaje = estadoNacionalDto.ParticipacionPorcentaje;
|
||||
_logger.LogInformation("Datos nacionales para categoría '{catNombre}' actualizados.", categoria.Nombre);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ambitoNacional == null)
|
||||
{
|
||||
_logger.LogWarning("No se encontró el ámbito geográfico para el Nivel Nacional (NivelId 1 o 0). No se pueden capturar los totales del país.");
|
||||
}
|
||||
|
||||
// Guardar todos los cambios
|
||||
if (dbContext.ChangeTracker.HasChanges())
|
||||
{
|
||||
await dbContext.SaveChangesAsync(stoppingToken);
|
||||
_logger.LogInformation("Sondeo de Estado Recuento General completado. Se han guardado los cambios en la base de datos.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Sondeo de Estado Recuento General completado. No se detectaron cambios.");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogInformation("Sondeo de Estado Recuento General cancelado.");
|
||||
}
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ public class LowPriorityDataWorker : BackgroundService
|
||||
if (settings.Prioridad == "Telegramas" && settings.ResultadosActivado)
|
||||
{
|
||||
_logger.LogInformation("Ejecutando tareas de Resultados en baja prioridad.");
|
||||
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
||||
await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
|
||||
await SondearResumenProvincialAsync(authToken, stoppingToken);
|
||||
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
||||
}
|
||||
else if (settings.Prioridad == "Resultados" && settings.BajasActivado)
|
||||
{
|
||||
@@ -320,92 +320,110 @@ public class LowPriorityDataWorker : BackgroundService
|
||||
{
|
||||
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);
|
||||
var provinciasASondear = await dbContext.AmbitosGeograficos
|
||||
.AsNoTracking()
|
||||
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
||||
.ToListAsync(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;
|
||||
}
|
||||
// Busca NivelId 1 (País) o 0 como fallback.
|
||||
var ambitoNacional = await dbContext.AmbitosGeograficos
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(a => a.NivelId == 1 || a.NivelId == 0, stoppingToken);
|
||||
|
||||
// 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())
|
||||
if (!provinciasASondear.Any() || !categoriasParaSondear.Any())
|
||||
{
|
||||
_logger.LogWarning("No hay categorías en la BD para sondear el estado general del recuento.");
|
||||
_logger.LogWarning("No se encontraron Provincias o Categorías para sondear estado general.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Iniciando sondeo de Estado Recuento General para {count} categorías...", categoriasParaSondear.Count);
|
||||
_logger.LogInformation("Iniciando sondeo de Estado Recuento General para {provCount} provincias, el total nacional y {catCount} categorías...", provinciasASondear.Count, categoriasParaSondear.Count);
|
||||
|
||||
// PASO 4: Iterar sobre cada categoría para obtener su estado de recuento individual.
|
||||
foreach (var categoria in categoriasParaSondear)
|
||||
// Sondeo a nivel provincial
|
||||
foreach (var provincia in provinciasASondear)
|
||||
{
|
||||
// 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)
|
||||
foreach (var categoria in categoriasParaSondear)
|
||||
{
|
||||
// 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
|
||||
);
|
||||
if (stoppingToken.IsCancellationRequested) break;
|
||||
|
||||
// Si no se encuentra (FindAsync devuelve null), es un registro nuevo.
|
||||
if (registroDb == null)
|
||||
var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id);
|
||||
if (estadoDto != null)
|
||||
{
|
||||
// Creamos una nueva instancia de la entidad.
|
||||
registroDb = new EstadoRecuentoGeneral
|
||||
var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { provincia.Id, categoria.Id }, stoppingToken);
|
||||
if (registroDb == null)
|
||||
{
|
||||
EleccionId = EleccionId,
|
||||
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);
|
||||
registroDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id };
|
||||
dbContext.EstadosRecuentosGenerales.Add(registroDb);
|
||||
}
|
||||
registroDb.FechaTotalizacion = DateTime.UtcNow;
|
||||
registroDb.MesasEsperadas = estadoDto.MesasEsperadas;
|
||||
registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas;
|
||||
registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje;
|
||||
registroDb.CantidadElectores = estadoDto.CantidadElectores;
|
||||
registroDb.CantidadVotantes = estadoDto.CantidadVotantes;
|
||||
registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje;
|
||||
}
|
||||
|
||||
// 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.");
|
||||
// Bloque para el sondeo a nivel nacional
|
||||
if (ambitoNacional != null && !stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogInformation("Sondeando totales a nivel Nacional (Ambito ID: {ambitoId})...", ambitoNacional.Id);
|
||||
foreach (var categoria in categoriasParaSondear)
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested) break;
|
||||
|
||||
var estadoNacionalDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, "", categoria.Id);
|
||||
|
||||
if (estadoNacionalDto != null)
|
||||
{
|
||||
var registroNacionalDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { ambitoNacional.Id, categoria.Id }, stoppingToken);
|
||||
if (registroNacionalDb == null)
|
||||
{
|
||||
registroNacionalDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = ambitoNacional.Id, CategoriaId = categoria.Id };
|
||||
dbContext.EstadosRecuentosGenerales.Add(registroNacionalDb);
|
||||
}
|
||||
registroNacionalDb.FechaTotalizacion = DateTime.UtcNow;
|
||||
registroNacionalDb.MesasEsperadas = estadoNacionalDto.MesasEsperadas;
|
||||
registroNacionalDb.MesasTotalizadas = estadoNacionalDto.MesasTotalizadas;
|
||||
registroNacionalDb.MesasTotalizadasPorcentaje = estadoNacionalDto.MesasTotalizadasPorcentaje;
|
||||
registroNacionalDb.CantidadElectores = estadoNacionalDto.CantidadElectores;
|
||||
registroNacionalDb.CantidadVotantes = estadoNacionalDto.CantidadVotantes;
|
||||
registroNacionalDb.ParticipacionPorcentaje = estadoNacionalDto.ParticipacionPorcentaje;
|
||||
_logger.LogInformation("Datos nacionales para categoría '{catNombre}' actualizados.", categoria.Nombre);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ambitoNacional == null)
|
||||
{
|
||||
_logger.LogWarning("No se encontró el ámbito geográfico para el Nivel Nacional (NivelId 1 o 0). No se pueden capturar los totales del país.");
|
||||
}
|
||||
|
||||
// Guardar todos los cambios
|
||||
if (dbContext.ChangeTracker.HasChanges())
|
||||
{
|
||||
await dbContext.SaveChangesAsync(stoppingToken);
|
||||
_logger.LogInformation("Sondeo de Estado Recuento General completado. Se han guardado los cambios en la base de datos.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Sondeo de Estado Recuento General completado. No se detectaron cambios.");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogInformation("Sondeo de Estado Recuento General cancelado.");
|
||||
}
|
||||
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