From defb74fcd23fcd9ee58ff5e7da11fe2677267bac Mon Sep 17 00:00:00 2001 From: dmolinari Date: Mon, 18 Aug 2025 17:47:11 -0300 Subject: [PATCH] Fix bancas y telegramas --- .../Services/ElectoralApiService.cs | 10 +- .../Services/IElectoralApiService.cs | 2 +- .../src/Elecciones.Worker/Worker.cs | 107 ++++++++---------- 3 files changed, 57 insertions(+), 62 deletions(-) diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs b/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs index 6b171f4..9d126b1 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs +++ b/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs @@ -83,13 +83,21 @@ public class ElectoralApiService : IElectoralApiService return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } - public async Task?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId) + public async Task?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId, int? categoriaId = null) { var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var requestUri = $"/api/resultados/getTelegramasTotalizados?distritoId={distritoId}&seccionId={seccionId}"; + + // Añadimos el parámetro categoriaId a la URL SÓLO si se proporciona un valor. + if (categoriaId.HasValue) + { + requestUri += $"&categoriaId={categoriaId.Value}"; + } + var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); var response = await client.SendAsync(request); + // Si la respuesta es 400, devolvemos null para que el worker sepa que falló. return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync>() : null; } diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs b/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs index eddc405..f0c3faf 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs +++ b/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs @@ -14,7 +14,7 @@ public interface IElectoralApiService Task?> GetAgrupacionesAsync(string authToken, string distritoId, int categoriaId); Task GetResultadosAsync(string authToken, string distritoId, string seccionId, string? municipioId, int categoriaId); Task GetBancasAsync(string authToken, string distritoId, string seccionId, int categoriaId); - Task?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId); + Task?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId, int? categoriaId = null); Task GetTelegramaFileAsync(string authToken, string mesaId); Task GetResumenAsync(string authToken, string distritoId); Task GetEstadoRecuentoGeneralAsync(string authToken, string distritoId, int categoriaId); diff --git a/Elecciones-Web/src/Elecciones.Worker/Worker.cs b/Elecciones-Web/src/Elecciones.Worker/Worker.cs index 5a8f059..483bc80 100644 --- a/Elecciones-Web/src/Elecciones.Worker/Worker.cs +++ b/Elecciones-Web/src/Elecciones.Worker/Worker.cs @@ -466,96 +466,83 @@ public class Worker : BackgroundService { try { - // PASO 1: Preparar el DbContext - // Creamos un scope para obtener una instancia fresca de DbContext para esta operación. using var scope = _serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); - // PASO 2: Obtener los Partidos/Municipios (NivelId = 30) - // --- CORRECCIÓN CLAVE --- - // La API requiere un 'seccionId' para listar telegramas, y el manual lo define como "Id del partido". - // La respuesta de getCatalogo nos confirmó que los registros de Partidos/Municipios usan NivelId = 30. + // Obtenemos todos los partidos (NivelId=30) y todas las categorías para iterar var partidos = await dbContext.AmbitosGeograficos - .AsNoTracking() // Optimización: Solo necesitamos leer estos datos. + .AsNoTracking() .Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null) .ToListAsync(stoppingToken); - // Si la sincronización inicial no cargó ningún partido, no podemos continuar. - if (!partidos.Any()) + var categorias = await dbContext.CategoriasElectorales + .AsNoTracking() + .ToListAsync(stoppingToken); + + if (!partidos.Any() || !categorias.Any()) { - _logger.LogWarning("No se encontraron Partidos (NivelId 30) en la BD para sondear telegramas."); + _logger.LogWarning("No se encontraron partidos o categorías en la BD para sondear telegramas."); return; } - _logger.LogInformation("Iniciando sondeo de Telegramas nuevos para {count} partidos...", partidos.Count); + _logger.LogInformation("Iniciando sondeo de Telegramas nuevos para {partidosCount} partidos y {categoriasCount} categorías...", partidos.Count, categorias.Count); - // PASO 3: Iterar sobre cada Partido para buscar telegramas. foreach (var partido in partidos) { - if (stoppingToken.IsCancellationRequested) break; // Salida limpia si la aplicación se detiene. + if (stoppingToken.IsCancellationRequested) break; - // 3.1 - OBTENER LA LISTA COMPLETA DE IDs DE LA API PARA EL PARTIDO ACTUAL - var listaTelegramasApi = await _apiService.GetTelegramasTotalizadosAsync(authToken, partido.DistritoId!, partido.SeccionId!); - - // Solo procesamos si la API devolvió una lista con al menos un telegrama. - if (listaTelegramasApi is { Count: > 0 }) + foreach (var categoria in categorias) { - // La API devuelve una lista de arrays de string (ej. [ ["id1"], ["id2"] ]). - // Usamos .Select(t => t[0]) para extraer solo el ID de cada sub-array. - var idsDeApi = listaTelegramasApi.Select(t => t[0]).Distinct().ToList(); + if (stoppingToken.IsCancellationRequested) break; - // 3.2 - COMPARAR CON LA BASE DE DATOS LOCAL PARA ENCONTRAR LOS NUEVOS - // Esta es una consulta muy eficiente: le pedimos a la BD que nos devuelva solo los IDs que - // ya existen de la lista que nos acaba de dar la API. - var idsYaEnDb = await dbContext.Telegramas - .Where(t => idsDeApi.Contains(t.Id)) - .Select(t => t.Id) - .ToListAsync(stoppingToken); + // Llamamos al servicio pasando la categoriaId. + // Si mañana la API ya no lo necesita, simplemente lo ignorará. + // Si lo vuelve a necesitar en el futuro, nuestro código ya está preparado. + var listaTelegramasApi = await _apiService.GetTelegramasTotalizadosAsync(authToken, partido.DistritoId!, partido.SeccionId!, categoria.Id); - // Comparamos las dos listas para obtener una tercera: la de los IDs que están en la API pero no en nuestra BD. - var nuevosTelegramasIds = idsDeApi.Except(idsYaEnDb).ToList(); - - // Si no hay telegramas nuevos para este partido, simplemente continuamos con el siguiente. - if (!nuevosTelegramasIds.Any()) + // El resto de la lógica es la misma: si la respuesta es válida y contiene datos... + if (listaTelegramasApi is { Count: > 0 }) { - continue; - } + var idsDeApi = listaTelegramasApi.Select(t => t[0]).Distinct().ToList(); + var idsYaEnDb = await dbContext.Telegramas + .Where(t => idsDeApi.Contains(t.Id)) + .Select(t => t.Id) + .ToListAsync(stoppingToken); - _logger.LogInformation("Se encontraron {count} telegramas nuevos en el partido '{nombrePartido}' ({seccionId}). Descargando...", nuevosTelegramasIds.Count, partido.Nombre, partido.SeccionId); + var nuevosTelegramasIds = idsDeApi.Except(idsYaEnDb).ToList(); - // 3.3 - DESCARGAR Y GUARDAR CADA NUEVO TELEGRAMA - foreach (var mesaId in nuevosTelegramasIds) - { - if (stoppingToken.IsCancellationRequested) break; - - // Descargamos el contenido completo del telegrama (imagen base64 y metadatos). - var telegramaFile = await _apiService.GetTelegramaFileAsync(authToken, mesaId); - if (telegramaFile != null) + if (!nuevosTelegramasIds.Any()) { - var nuevoTelegrama = new Telegrama - { - Id = telegramaFile.NombreArchivo, - AmbitoGeograficoId = partido.Id, // Lo asociamos al ID del partido. - ContenidoBase64 = telegramaFile.Imagen, - FechaEscaneo = DateTime.Parse(telegramaFile.FechaEscaneo).ToUniversalTime(), - FechaTotalizacion = DateTime.Parse(telegramaFile.FechaTotalizacion).ToUniversalTime() - }; - // Añadimos la nueva entidad a la sesión de EF Core para su inserción. - await dbContext.Telegramas.AddAsync(nuevoTelegrama, stoppingToken); + continue; } - } - // Guardamos los cambios en la base de datos después de procesar todos los telegramas nuevos de UN partido. - // Esto es más eficiente que guardar en cada iteración del bucle interno. - await dbContext.SaveChangesAsync(stoppingToken); + _logger.LogInformation("Se encontraron {count} telegramas nuevos en el partido '{nombre}' para la categoría '{cat}'. Descargando...", nuevosTelegramasIds.Count, partido.Nombre, categoria.Nombre); + + foreach (var mesaId in nuevosTelegramasIds) + { + if (stoppingToken.IsCancellationRequested) break; + var telegramaFile = await _apiService.GetTelegramaFileAsync(authToken, mesaId); + if (telegramaFile != null) + { + var nuevoTelegrama = new Telegrama + { + Id = telegramaFile.NombreArchivo, + AmbitoGeograficoId = partido.Id, + ContenidoBase64 = telegramaFile.Imagen, + FechaEscaneo = DateTime.Parse(telegramaFile.FechaEscaneo).ToUniversalTime(), + FechaTotalizacion = DateTime.Parse(telegramaFile.FechaTotalizacion).ToUniversalTime() + }; + await dbContext.Telegramas.AddAsync(nuevoTelegrama, stoppingToken); + } + } + await dbContext.SaveChangesAsync(stoppingToken); + } } } - _logger.LogInformation("Sondeo de Telegramas completado."); } catch (Exception ex) { - // Capturamos cualquier error para que no detenga el worker y lo registramos para depuración. _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Telegramas."); } }