diff --git a/Elecciones-Web/src/Elecciones.Worker/LowPriorityDataWorker.cs b/Elecciones-Web/src/Elecciones.Worker/LowPriorityDataWorker.cs index 7f706ce..d45a722 100644 --- a/Elecciones-Web/src/Elecciones.Worker/LowPriorityDataWorker.cs +++ b/Elecciones-Web/src/Elecciones.Worker/LowPriorityDataWorker.cs @@ -248,51 +248,85 @@ public class LowPriorityDataWorker : BackgroundService /// Sondea la proyección de bancas a nivel Provincial y por Sección Electoral. /// Esta versión recolecta todos los datos disponibles y los guarda en una única transacción. /// + /// + /// Sondea la proyección de bancas a nivel Provincial y por Sección Electoral. + /// Esta versión incluye logging detallado para diagnosticar problemas de deserialización + /// o respuestas inesperadas de la API, y guarda los datos en una transacción atómica. + /// + /// El token de autenticación válido para la sesión. + /// El token de cancelación para detener la operación. private async Task SondearProyeccionBancasAsync(string authToken, CancellationToken stoppingToken) { try { + // Usamos un scope de DbContext para esta operación, asegurando una conexión limpia. using var scope = _serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); + // 1. OBTENER DATOS DE CONFIGURACIÓN DE NUESTRA BD + + // Buscamos las categorías que reparten bancas (Senadores y Diputados). var categoriasDeBancas = await dbContext.CategoriasElectorales .AsNoTracking() .Where(c => c.Nombre.Contains("SENADORES") || c.Nombre.Contains("DIPUTADOS")) .ToListAsync(stoppingToken); + // Obtenemos el registro de la Provincia para hacer la consulta a nivel provincial. var provincia = await dbContext.AmbitosGeograficos .AsNoTracking() .FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken); + // Obtenemos todas las Secciones Electorales para hacer las consultas por sección. var seccionesElectorales = await dbContext.AmbitosGeograficos .AsNoTracking() .Where(a => a.NivelId == 20 && a.DistritoId != null && a.SeccionProvincialId != null) .ToListAsync(stoppingToken); + // Si falta alguna configuración base, no podemos continuar. if (!categoriasDeBancas.Any() || provincia == null) { - _logger.LogWarning("No se encontraron categorías de bancas o el ámbito provincial en la BD. Omitiendo sondeo."); + _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); - // --- LÓGICA CORREGIDA --- - bool hasReceivedAnyNewData = false; - var todasLasProyecciones = new List(); + // 2. RECOLECTAR DATOS DE LA API - // 1. Bucle Provincial + // Una lista para acumular todas las proyecciones que encontremos. + var todasLasProyecciones = new List(); + // Una bandera para saber si hemos recibido al menos una banca con datos. + bool hasReceivedAnyNewData = false; + + // Bucle para el nivel Provincial foreach (var categoria in categoriasDeBancas) { if (stoppingToken.IsCancellationRequested) break; + var repartoBancas = await _apiService.GetBancasAsync(authToken, provincia.DistritoId!, null, categoria.Id); - // Comprobamos si la respuesta no es nula y si la lista de bancas TIENE ELEMENTOS. - if (repartoBancas?.RepartoBancas is { Count: > 0 } bancas) + // --- Lógica de Verificación Explícita --- + if (repartoBancas == null) { - // Si encontramos datos, activamos la bandera. - hasReceivedAnyNewData = true; - foreach (var banca in bancas) + // Esto ocurriría si GetBancasAsync devuelve null (ej. por un error 500 o un JsonException). + _logger.LogWarning("La respuesta para bancas provinciales (cat: {catId}) fue nula o inválida.", categoria.Id); + continue; + } + + if (repartoBancas.RepartoBancas == null) + { + // Esto es por si la API devuelve un JSON válido pero sin la propiedad "repartoBancas". + _logger.LogWarning("La lista 'RepartoBancas' en la respuesta provincial (cat: {catId}) es nula.", categoria.Id); + continue; + } + + if (repartoBancas.RepartoBancas.Count > 0) + { + // ¡Éxito! Hemos encontrado datos. + _logger.LogInformation("Se encontraron {count} registros de bancas a nivel provincial para la categoría {catId}.", repartoBancas.RepartoBancas.Count, categoria.Id); + hasReceivedAnyNewData = true; // Activamos la bandera. + + foreach (var banca in repartoBancas.RepartoBancas) { todasLasProyecciones.Add(new ProyeccionBanca { @@ -304,7 +338,7 @@ public class LowPriorityDataWorker : BackgroundService } } - // 2. Bucle por Sección + // Bucle para el nivel de Sección Electoral foreach (var seccion in seccionesElectorales) { if (stoppingToken.IsCancellationRequested) break; @@ -313,10 +347,10 @@ public class LowPriorityDataWorker : BackgroundService if (stoppingToken.IsCancellationRequested) break; var repartoBancas = await _apiService.GetBancasAsync(authToken, seccion.DistritoId!, seccion.SeccionProvincialId!, categoria.Id); - if (repartoBancas?.RepartoBancas is { Count: > 0 } bancas) + if (repartoBancas?.RepartoBancas?.Count > 0) { hasReceivedAnyNewData = true; - foreach (var banca in bancas) + foreach (var banca in repartoBancas.RepartoBancas) { todasLasProyecciones.Add(new ProyeccionBanca { @@ -329,27 +363,31 @@ public class LowPriorityDataWorker : BackgroundService } } - // 3. Guardado Final - // La lógica de guardado ahora depende de la bandera. + // 3. GUARDAR DATOS EN LA BASE DE DATOS + + // Solo procedemos a escribir en la BD si la bandera se activó. if (hasReceivedAnyNewData) { - _logger.LogInformation("Se recibieron {count} registros de proyección de bancas. Actualizando la tabla...", todasLasProyecciones.Count); + _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); await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ProyeccionesBancas", stoppingToken); - // Guardamos la lista completa, incluso los que tienen 0 bancas. await dbContext.ProyeccionesBancas.AddRangeAsync(todasLasProyecciones, stoppingToken); await dbContext.SaveChangesAsync(stoppingToken); await transaction.CommitAsync(stoppingToken); - _logger.LogInformation("Sondeo de Bancas completado. La tabla de proyecciones ha sido actualizada."); + _logger.LogInformation("La tabla de proyecciones ha sido actualizada con {count} registros.", todasLasProyecciones.Count); } else { _logger.LogInformation("Sondeo de Bancas completado. No se encontraron datos nuevos de proyección, 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.");