diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs b/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs index 1f93187..7784059 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs +++ b/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs @@ -42,10 +42,11 @@ public class ElectoralApiService : IElectoralApiService : null; } - public async Task?> GetCatalogoCompletoAsync(string authToken) + public async Task?> GetCatalogoAmbitosAsync(string authToken, int categoriaId) { var client = _httpClientFactory.CreateClient("ElectoralApiClient"); - var request = new HttpRequestMessage(HttpMethod.Get, "/api/catalogo/getCatalogo?categoriald=5"); + // La URL ahora es dinámica y usa el categoriaId + var request = new HttpRequestMessage(HttpMethod.Get, $"/api/catalogo/getCatalogo?categoriald={categoriaId}"); request.Headers.Add("Authorization", $"Bearer {authToken}"); var response = await client.SendAsync(request); diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/Services/FakeElectoralApiService.cs b/Elecciones-Web/src/Elecciones.Infrastructure/Services/FakeElectoralApiService.cs deleted file mode 100644 index fe243cc..0000000 --- a/Elecciones-Web/src/Elecciones.Infrastructure/Services/FakeElectoralApiService.cs +++ /dev/null @@ -1,145 +0,0 @@ -using Elecciones.Core.DTOs; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using static Elecciones.Core.DTOs.BancaDto; - -namespace Elecciones.Infrastructure.Services; - -public class FakeElectoralApiService : IElectoralApiService -{ - private readonly ILogger _logger; - private List? _fakeAmbitosCache = null; - private readonly Random _random = new Random(); - - public FakeElectoralApiService(ILogger logger) - { - _logger = logger; - } - - private void GenerateFakeAmbitosFromGeoJson() - { - if (_fakeAmbitosCache != null) return; - _logger.LogWarning("--- USANDO SERVICIO FALSO (FAKE) ---"); - _logger.LogInformation("Generando datos de prueba de ámbitos desde el archivo GeoJSON..."); - - var geoJsonPath = Path.Combine(AppContext.BaseDirectory, "buenos-aires-municipios.geojson"); - if (!File.Exists(geoJsonPath)) - { - _logger.LogError("No se encontró el archivo buenos-aires-municipios.geojson."); - _fakeAmbitosCache = new List(); - return; - } - - var geoJsonString = File.ReadAllText(geoJsonPath); - using var document = JsonDocument.Parse(geoJsonString); - var features = document.RootElement.GetProperty("features").EnumerateArray().ToList(); - - var ambitos = new List(); - ambitos.Add(new AmbitoDto { NivelId = 10, Nombre = "BUENOS AIRES", CodigoAmbitos = new CodigoAmbitoDto { DistritoId = "02" } }); - var secciones = new List { - new() { NivelId = 4, Nombre = "PRIMERA SECCION ELECTORAL", CodigoAmbitos = new CodigoAmbitoDto { DistritoId = "02", SeccionId = "0001" } }, - new() { NivelId = 4, Nombre = "SEGUNDA SECCION ELECTORAL", CodigoAmbitos = new CodigoAmbitoDto { DistritoId = "02", SeccionId = "0002" } }, - new() { NivelId = 4, Nombre = "TERCERA SECCION ELECTORAL", CodigoAmbitos = new CodigoAmbitoDto { DistritoId = "02", SeccionId = "0003" } } - }; - ambitos.AddRange(secciones); - - for (int i = 0; i < features.Count; i++) - { - var feature = features[i]; - var properties = feature.GetProperty("properties"); - var seccionAsignada = secciones[i % secciones.Count]; - ambitos.Add(new AmbitoDto { NivelId = 5, Nombre = properties.GetProperty("nam").GetString() ?? "Sin Nombre", CodigoAmbitos = new CodigoAmbitoDto { MunicipioId = properties.GetProperty("cca").GetString(), DistritoId = "02", SeccionId = seccionAsignada.CodigoAmbitos.SeccionId } }); - } - _fakeAmbitosCache = ambitos; - _logger.LogInformation("Se generaron {count} ámbitos de prueba (Provincia, Secciones y Municipios).", _fakeAmbitosCache.Count); - } - - public Task GetResumenAsync(string authToken, string distritoId) - { - _logger.LogInformation("Simulando obtención de Resumen para distrito {DistritoId}...", distritoId); - var resumen = new ResumenDto - { - ValoresTotalizadosPositivos = new List - { - new() { IdAgrupacion = "025", Votos = 2500000 + _random.Next(1000), VotosPorcentaje = 45.12m }, - new() { IdAgrupacion = "018", Votos = 2100000 + _random.Next(1000), VotosPorcentaje = 38.78m }, - new() { IdAgrupacion = "031", Votos = 800000 + _random.Next(1000), VotosPorcentaje = 14.10m } - } - }; - return Task.FromResult(resumen); - } - - public Task GetEstadoRecuentoGeneralAsync(string authToken, string distritoId) - { - _logger.LogInformation("Simulando obtención de Estado de Recuento General para distrito {DistritoId}...", distritoId); - var estado = new EstadoRecuentoGeneralDto - { - MesasEsperadas = 38000, - MesasTotalizadas = _random.Next(28000, 37000), - MesasTotalizadasPorcentaje = 95.5m, - CantidadElectores = 12500000, - CantidadVotantes = 9375000, - ParticipacionPorcentaje = 75.0m - }; - return Task.FromResult(estado); - } - - public Task GetAuthTokenAsync() - { - _logger.LogInformation("Simulando obtención de token..."); - return Task.FromResult("FAKE_TOKEN_FOR_DEVELOPMENT"); - } - - public Task?> GetCatalogoCompletoAsync(string authToken) - { - GenerateFakeAmbitosFromGeoJson(); - var catalogo = new List { new() { Version = 1, CategoriaId = 5, Ambitos = _fakeAmbitosCache ?? new List(), Niveles = new List { new() { NivelId = 10, Nombre = "Provincia" }, new() { NivelId = 4, Nombre = "Seccion" }, new() { NivelId = 5, Nombre = "Municipio" } } } }; - return Task.FromResult?>(catalogo); - } - - public Task?> GetAgrupacionesAsync(string authToken, string distritoId, int categoriaId) - { - var agrupaciones = new List { new() { IdAgrupacion = "018", IdAgrupacionTelegrama = "131", NombreAgrupacion = "FRENTE DE AVANZADA" }, new() { IdAgrupacion = "025", IdAgrupacionTelegrama = "132", NombreAgrupacion = "ALIANZA POR EL FUTURO" }, new() { IdAgrupacion = "031", IdAgrupacionTelegrama = "133", NombreAgrupacion = "UNION POPULAR" }, new() { IdAgrupacion = "045", IdAgrupacionTelegrama = "134", NombreAgrupacion = "PARTIDO VECINALISTA" } }; - return Task.FromResult?>(agrupaciones); - } - - public Task GetResultadosAsync(string authToken, string distritoId, string seccionId, string municipioId) - { - var resultados = new ResultadosDto { FechaTotalizacion = DateTime.Now.ToString("o"), EstadoRecuento = new EstadoRecuentoDto { MesasEsperadas = _random.Next(100, 2000), MesasTotalizadas = _random.Next(50, 100), CantidadElectores = _random.Next(50000, 600000), ParticipacionPorcentaje = _random.Next(60, 85) + (decimal)_random.NextDouble() }, ValoresTotalizadosPositivos = new List { new() { IdAgrupacion = "018", Votos = _random.Next(10000, 20000) }, new() { IdAgrupacion = "025", Votos = _random.Next(15000, 25000) }, new() { IdAgrupacion = "031", Votos = _random.Next(5000, 10000) }, new() { IdAgrupacion = "045", Votos = _random.Next(2000, 5000) } }, ValoresTotalizadosOtros = new VotosOtrosDto { VotosEnBlanco = _random.Next(1000, 2000), VotosNulos = _random.Next(500, 1000), VotosRecurridos = _random.Next(20, 50) } }; - return Task.FromResult(resultados); - } - - public Task GetBancasAsync(string authToken, string distritoId, string seccionId) - { - var reparto = new RepartoBancasDto { RepartoBancas = new List { new() { IdAgrupacion = "025", NroBancas = _random.Next(5, 9) }, new() { IdAgrupacion = "018", NroBancas = _random.Next(3, 7) } } }; - return Task.FromResult(reparto); - } - - public Task?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId) - { - var lista = new List { new[] { $"02{seccionId}0001M" }, new[] { $"02{seccionId}0002M" } }; - return Task.FromResult?>(lista); - } - - public Task GetTelegramaFileAsync(string authToken, string mesaId) - { - var file = new TelegramaFileDto { NombreArchivo = mesaId, Imagen = "FAKE_BASE64_PDF_CONTENT", FechaEscaneo = DateTime.UtcNow.AddMinutes(-10).ToString("o"), FechaTotalizacion = DateTime.UtcNow.ToString("o") }; - return Task.FromResult(file); - } - - public Task?> GetCategoriasAsync(string authToken) - { - _logger.LogInformation("Simulando obtención de Categorías Electorales..."); - var categorias = new List - { - new() { CategoriaId = 5, Nombre = "DIPUTADOS NACIONALES", Orden = 1 }, - new() { CategoriaId = 6, Nombre = "SENADORES NACIONALES", Orden = 2 } - }; - return Task.FromResult?>(categorias); - } -} \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs b/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs index e256f9e..ea06127 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs +++ b/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs @@ -10,7 +10,7 @@ public interface IElectoralApiService Task GetAuthTokenAsync(); // Métodos para catálogos - Task?> GetCatalogoCompletoAsync(string authToken); + Task?> GetCatalogoAmbitosAsync(string authToken, int categoriaId); Task?> GetAgrupacionesAsync(string authToken, string distritoId, int categoriaId); // Métodos para resultados y datos dinámicos diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs index 7785234..55f9a1d 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+39b1e9707275ed59ac4a7d32e26b951186a346bb")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+527839dd6deeb2314e8edc9f7f83edc264a0ea61")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Elecciones-Web/src/Elecciones.Worker/Program.cs b/Elecciones-Web/src/Elecciones.Worker/Program.cs index f555ac0..eb9ff1e 100644 --- a/Elecciones-Web/src/Elecciones.Worker/Program.cs +++ b/Elecciones-Web/src/Elecciones.Worker/Program.cs @@ -30,9 +30,6 @@ var connectionString = builder.Configuration.GetConnectionString("DefaultConnect builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); -#if DEBUG -builder.Services.AddSingleton(); -#else builder.Services.AddHttpClient("ElectoralApiClient", client => { var baseUrl = builder.Configuration["ElectoralApi:BaseUrl"]; @@ -80,8 +77,6 @@ builder.Services.AddHttpClient("ElectoralApiClient", client => builder.Services.AddSingleton(); -#endif - builder.Services.AddHostedService(); var host = builder.Build(); diff --git a/Elecciones-Web/src/Elecciones.Worker/Worker.cs b/Elecciones-Web/src/Elecciones.Worker/Worker.cs index 9bd5bf1..6d402da 100644 --- a/Elecciones-Web/src/Elecciones.Worker/Worker.cs +++ b/Elecciones-Web/src/Elecciones.Worker/Worker.cs @@ -170,126 +170,81 @@ public class Worker : BackgroundService using var scope = _serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); - // --- 1. SINCRONIZACIÓN DE CATEGORÍAS (NUEVO) --- + // --- 1. OBTENEMOS LA LISTA COMPLETA DE CATEGORÍAS DISPONIBLES --- var categoriasApi = await _apiService.GetCategoriasAsync(_authToken); - if (categoriasApi is { Count: > 0 }) + if (categoriasApi is null || !categoriasApi.Any()) { - // Primero, nos aseguramos de procesar solo IDs de categoría únicos desde la API - // para evitar conflictos de seguimiento en Entity Framework. - var distinctCategoriasApi = categoriasApi - .GroupBy(c => c.CategoriaId) - .Select(g => g.First()) // Tomamos la primera aparición de cada ID único - .ToList(); + _logger.LogWarning("No se recibieron datos del catálogo de Categorías. No se puede continuar con la sincronización."); + return; + } - var categoriasEnDb = await dbContext.CategoriasElectorales.ToDictionaryAsync(c => c.Id, c => c, stoppingToken); + // Depuramos la lista para evitar duplicados + var distinctCategorias = categoriasApi + .GroupBy(c => c.CategoriaId) + .Select(g => g.First()) + .OrderBy(c => c.Orden) // Procesamos en el orden definido + .ToList(); - // Ahora iteramos sobre la lista depurada - foreach (var categoriaDto in distinctCategoriasApi) + _logger.LogInformation("Se procesarán {count} categorías electorales.", distinctCategorias.Count); + + // --- 2. ITERAMOS SOBRE CADA CATEGORÍA PARA DESCARGAR SUS CATÁLOGOS --- + foreach (var categoria in distinctCategorias) + { + if (stoppingToken.IsCancellationRequested) break; + _logger.LogInformation("--- Sincronizando catálogos para la categoría: {NombreCategoria} (ID: {CategoriaId}) ---", categoria.Nombre, categoria.CategoriaId); + + // --- SINCRONIZACIÓN DE ÁMBITOS GEOGRÁFICOS --- + var catalogoAmbitosApi = await _apiService.GetCatalogoAmbitosAsync(_authToken, categoria.CategoriaId); + if (catalogoAmbitosApi is { Count: > 0 }) { - if (categoriasEnDb.TryGetValue(categoriaDto.CategoriaId, out var categoriaExistente)) + var ambitosEnDb = await dbContext.AmbitosGeograficos.ToDictionaryAsync(a => (a.DistritoId, a.SeccionId, a.MunicipioId), a => a, stoppingToken); + foreach (var ambitoDto in catalogoAmbitosApi.SelectMany(c => c.Ambitos)) { - // La categoría ya existe, actualizamos sus datos - categoriaExistente.Nombre = categoriaDto.Nombre; - categoriaExistente.Orden = categoriaDto.Orden; - } - else - { - // La categoría es nueva, la añadimos - await dbContext.CategoriasElectorales.AddAsync(new CategoriaElectoral + var claveUnica = (ambitoDto.CodigoAmbitos.DistritoId, ambitoDto.CodigoAmbitos.SeccionId, ambitoDto.CodigoAmbitos.MunicipioId); + if (!ambitosEnDb.TryGetValue(claveUnica, out var ambitoExistente)) { - Id = categoriaDto.CategoriaId, - Nombre = categoriaDto.Nombre, - Orden = categoriaDto.Orden - }, stoppingToken); + await dbContext.AmbitosGeograficos.AddAsync(new AmbitoGeografico + { + Nombre = ambitoDto.Nombre, + NivelId = ambitoDto.NivelId, + DistritoId = ambitoDto.CodigoAmbitos.DistritoId, + SeccionId = ambitoDto.CodigoAmbitos.SeccionId, + MunicipioId = ambitoDto.CodigoAmbitos.MunicipioId, + SeccionProvincialId = ambitoDto.CodigoAmbitos.SeccionProvincialId + }, stoppingToken); + } + } + _logger.LogInformation("Sincronización de Ámbitos Geográficos completada para la categoría actual."); + } + + // --- SINCRONIZACIÓN DE AGRUPACIONES POLÍTICAS --- + var provincia = catalogoAmbitosApi?.SelectMany(c => c.Ambitos).FirstOrDefault(a => a.NivelId == 10); + if (provincia != null) + { + var agrupacionesApi = await _apiService.GetAgrupacionesAsync(_authToken, provincia.CodigoAmbitos.DistritoId, categoria.CategoriaId); + if (agrupacionesApi is { Count: > 0 }) + { + var agrupacionesEnDb = await dbContext.AgrupacionesPoliticas.ToDictionaryAsync(a => a.Id, a => a, stoppingToken); + foreach (var agrupacionDto in agrupacionesApi) + { + if (!agrupacionesEnDb.TryGetValue(agrupacionDto.IdAgrupacion, out var agrupacionExistente)) + { + await dbContext.AgrupacionesPoliticas.AddAsync(new AgrupacionPolitica + { + Id = agrupacionDto.IdAgrupacion, + IdTelegrama = agrupacionDto.IdAgrupacionTelegrama, + Nombre = agrupacionDto.NombreAgrupacion + }, stoppingToken); + } + } + _logger.LogInformation("Sincronización de Agrupaciones Políticas completada para la categoría actual."); } } - _logger.LogInformation("Sincronización de Categorías Electorales completada."); - } - else - { - _logger.LogWarning("No se recibieron datos del catálogo de Categorías."); - } + } // Fin del bucle foreach - // --- 2. SINCRONIZACIÓN DE ÁMBITOS GEOGRÁFICOS --- - var catalogoAmbitosApi = await _apiService.GetCatalogoCompletoAsync(_authToken); - if (catalogoAmbitosApi is { Count: > 0 }) - { - // Cargamos los ámbitos existentes de la BD en un diccionario para búsqueda rápida - var ambitosEnDb = await dbContext.AmbitosGeograficos - .ToDictionaryAsync(a => a.MunicipioId ?? a.SeccionId ?? a.DistritoId ?? a.Nombre, a => a, stoppingToken); - - foreach (var ambitoDto in catalogoAmbitosApi.SelectMany(c => c.Ambitos)) - { - // Usamos una clave única para identificar el ámbito (ej. ID de municipio) - var claveUnica = ambitoDto.CodigoAmbitos.MunicipioId ?? ambitoDto.CodigoAmbitos.SeccionId ?? ambitoDto.CodigoAmbitos.DistritoId ?? ambitoDto.Nombre; - - if (ambitosEnDb.TryGetValue(claveUnica, out var ambitoExistente)) - { - // El ámbito ya existe, actualizamos sus datos descriptivos. - // No actualizamos los IDs, ya que forman parte de la identidad del ámbito. - ambitoExistente.Nombre = ambitoDto.Nombre; - ambitoExistente.NivelId = ambitoDto.NivelId; - } - else - { - // El ámbito es nuevo, lo añadimos (el código de inserción que ya tenemos es correcto) - var nuevoAmbito = new AmbitoGeografico - { - Nombre = ambitoDto.Nombre, - NivelId = ambitoDto.NivelId, - DistritoId = ambitoDto.CodigoAmbitos.DistritoId, - SeccionId = ambitoDto.CodigoAmbitos.SeccionId, - MunicipioId = ambitoDto.CodigoAmbitos.MunicipioId, - SeccionProvincialId = ambitoDto.CodigoAmbitos.SeccionProvincialId - }; - await dbContext.AmbitosGeograficos.AddAsync(nuevoAmbito, stoppingToken); - } - } - _logger.LogInformation("Sincronización de Ámbitos Geográficos completada."); - } - else - { - _logger.LogWarning("No se recibieron datos del catálogo de Ámbitos. Los datos existentes no serán modificados."); - } - - // --- 3. SINCRONIZACIÓN DE AGRUPACIONES POLÍTICAS --- - var agrupacionesApi = await _apiService.GetAgrupacionesAsync(_authToken, "02", 5); - if (agrupacionesApi is { Count: > 0 }) - { - var agrupacionesEnDb = await dbContext.AgrupacionesPoliticas - .ToDictionaryAsync(a => a.Id, a => a, stoppingToken); - - foreach (var agrupacionDto in agrupacionesApi) - { - if (agrupacionesEnDb.TryGetValue(agrupacionDto.IdAgrupacion, out var agrupacionExistente)) - { - // La agrupación ya existe, actualizamos el nombre por si cambia - agrupacionExistente.Nombre = agrupacionDto.NombreAgrupacion; - agrupacionExistente.IdTelegrama = agrupacionDto.IdAgrupacionTelegrama; - } - else - { - // La agrupación es nueva, la añadimos - var nuevaAgrupacion = new AgrupacionPolitica - { - Id = agrupacionDto.IdAgrupacion, - IdTelegrama = agrupacionDto.IdAgrupacionTelegrama, - Nombre = agrupacionDto.NombreAgrupacion - }; - await dbContext.AgrupacionesPoliticas.AddAsync(nuevaAgrupacion, stoppingToken); - } - } - _logger.LogInformation("Sincronización de Agrupaciones Políticas completada."); - } - else - { - _logger.LogWarning("No se recibieron datos del catálogo de Agrupaciones. Los datos existentes no serán modificados."); - } - - // --- 4. GUARDADO FINAL --- + // --- 3. GUARDADO FINAL --- int cambiosGuardados = await dbContext.SaveChangesAsync(stoppingToken); - _logger.LogInformation("{count} cambios en los catálogos han sido guardados en la base de datos.", cambiosGuardados); - + _logger.LogInformation("{count} cambios totales en los catálogos han sido guardados en la base de datos.", cambiosGuardados); _logger.LogInformation("Sincronización de catálogos maestros finalizada."); } catch (Exception ex)