Feat CarouselNacional y Fix Workers
This commit is contained in:
		| @@ -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