Preparación Legislativas Nacionales 2025
This commit is contained in:
		| @@ -1,4 +1,5 @@ | ||||
| // src/Elecciones.Api/Controllers/ResultadosController.cs | ||||
| using Elecciones.Core.DTOs; | ||||
| using Elecciones.Core.DTOs.ApiResponses; | ||||
| using Elecciones.Database; | ||||
| using Elecciones.Database.Entities; | ||||
| @@ -8,7 +9,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| namespace Elecciones.Api.Controllers; | ||||
|  | ||||
| [ApiController] | ||||
| [Route("api/[controller]")] | ||||
| [Route("api/elecciones/{eleccionId}")] | ||||
| public class ResultadosController : ControllerBase | ||||
| { | ||||
|     private readonly EleccionesDbContext _dbContext; | ||||
| @@ -46,7 +47,7 @@ public class ResultadosController : ControllerBase | ||||
|     } | ||||
|  | ||||
|     [HttpGet("partido/{municipioId}")] | ||||
|     public async Task<IActionResult> GetResultadosPorPartido(string municipioId, [FromQuery] int categoriaId) | ||||
|     public async Task<IActionResult> GetResultadosPorPartido([FromRoute] int eleccionId, string municipioId, [FromQuery] int categoriaId) | ||||
|     { | ||||
|         var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking() | ||||
|             .FirstOrDefaultAsync(a => a.SeccionId == municipioId && a.NivelId == 30); | ||||
| @@ -57,7 +58,7 @@ public class ResultadosController : ControllerBase | ||||
|         } | ||||
|  | ||||
|         var estadoRecuento = await _dbContext.EstadosRecuentos.AsNoTracking() | ||||
|         .FirstOrDefaultAsync(e => e.AmbitoGeograficoId == ambito.Id && e.CategoriaId == categoriaId); | ||||
|             .FirstOrDefaultAsync(e => e.EleccionId == eleccionId && e.AmbitoGeograficoId == ambito.Id && e.CategoriaId == categoriaId); | ||||
|  | ||||
|         var agrupacionIds = await _dbContext.ResultadosVotos | ||||
|             .Where(rv => rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId) | ||||
| @@ -71,7 +72,7 @@ public class ResultadosController : ControllerBase | ||||
|  | ||||
|         var resultadosVotos = await _dbContext.ResultadosVotos.AsNoTracking() | ||||
|             .Include(rv => rv.AgrupacionPolitica) | ||||
|             .Where(rv => rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId) | ||||
|             .Where(rv => rv.EleccionId == eleccionId && rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         var candidatosRelevantes = await _dbContext.CandidatosOverrides.AsNoTracking() | ||||
| @@ -119,7 +120,7 @@ public class ResultadosController : ControllerBase | ||||
|     } | ||||
|  | ||||
|     [HttpGet("provincia/{distritoId}")] | ||||
|     public async Task<IActionResult> GetResultadosProvinciales(string distritoId) | ||||
|     public async Task<IActionResult> GetResultadosProvinciales([FromRoute] int eleccionId, string distritoId) | ||||
|     { | ||||
|         var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking() | ||||
|             .FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10); | ||||
| @@ -131,13 +132,13 @@ public class ResultadosController : ControllerBase | ||||
|  | ||||
|         var estadosPorCategoria = await _dbContext.EstadosRecuentosGenerales.AsNoTracking() | ||||
|             .Include(e => e.CategoriaElectoral) | ||||
|             .Where(e => e.AmbitoGeograficoId == provincia.Id) | ||||
|             .Where(e => e.EleccionId == eleccionId && e.AmbitoGeograficoId == provincia.Id) | ||||
|             .ToDictionaryAsync(e => e.CategoriaId); | ||||
|  | ||||
|         var resultadosPorMunicipio = await _dbContext.ResultadosVotos | ||||
|             .AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .Where(r => r.AmbitoGeografico.NivelId == 30) // Nivel 30 = Municipio | ||||
|             .Where(r => r.EleccionId == eleccionId && r.AmbitoGeografico.NivelId == 30) // Nivel 30 = Municipio | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         // Obtenemos TODOS los logos relevantes en una sola consulta | ||||
| @@ -194,8 +195,8 @@ public class ResultadosController : ControllerBase | ||||
|     } | ||||
|  | ||||
|  | ||||
|     [HttpGet("bancas-por-seccion/{seccionId}/{camara}")] // <-- CAMBIO 1: Modificar la ruta | ||||
|     public async Task<IActionResult> GetBancasPorSeccion(string seccionId, string camara) // <-- CAMBIO 2: Añadir el nuevo parámetro | ||||
|     [HttpGet("bancas-por-seccion/{seccionId}/{camara}")] | ||||
|     public async Task<IActionResult> GetBancasPorSeccion([FromRoute] int eleccionId, string seccionId, string camara) | ||||
|     { | ||||
|         // Convertimos el string de la cámara a un enum o un valor numérico para la base de datos | ||||
|         // 0 = Diputados, 1 = Senadores. Esto debe coincidir con cómo lo guardas en la DB. | ||||
| @@ -228,7 +229,7 @@ public class ResultadosController : ControllerBase | ||||
|         var proyecciones = await _dbContext.ProyeccionesBancas | ||||
|             .AsNoTracking() | ||||
|             .Include(p => p.AgrupacionPolitica) | ||||
|             .Where(p => p.AmbitoGeograficoId == seccion.Id && p.CategoriaId == CategoriaId) // <-- AÑADIDO EL FILTRO | ||||
|             .Where(p => p.EleccionId == eleccionId && p.AmbitoGeograficoId == seccion.Id && p.CategoriaId == CategoriaId) | ||||
|             .Select(p => new | ||||
|             { | ||||
|                 AgrupacionId = p.AgrupacionPolitica.Id, // Añadir para el 'key' en React | ||||
| @@ -347,7 +348,7 @@ public class ResultadosController : ControllerBase | ||||
|     } | ||||
|  | ||||
|     [HttpGet("composicion-congreso")] | ||||
|     public async Task<IActionResult> GetComposicionCongreso() | ||||
|     public async Task<IActionResult> GetComposicionCongreso([FromRoute] int eleccionId) | ||||
|     { | ||||
|         var config = await _dbContext.Configuraciones | ||||
|             .AsNoTracking() | ||||
| @@ -360,23 +361,24 @@ public class ResultadosController : ControllerBase | ||||
|         if (usarDatosOficiales) | ||||
|         { | ||||
|             // Si el interruptor está en 'true', llama a este método | ||||
|             return await GetComposicionDesdeBancadasOficiales(config); | ||||
|             return await GetComposicionDesdeBancadasOficiales(config, eleccionId); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Si está en 'false' o no existe, llama a este otro | ||||
|             return await GetComposicionDesdeProyecciones(config); | ||||
|             return await GetComposicionDesdeProyecciones(config, eleccionId); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // En ResultadosController.cs | ||||
|     private async Task<IActionResult> GetComposicionDesdeBancadasOficiales(Dictionary<string, string> config) | ||||
|     private async Task<IActionResult> GetComposicionDesdeBancadasOficiales(Dictionary<string, string> config, int eleccionId) | ||||
|     { | ||||
|         config.TryGetValue("MostrarOcupantes", out var mostrarOcupantesValue); | ||||
|         bool mostrarOcupantes = mostrarOcupantesValue == "true"; | ||||
|  | ||||
|         IQueryable<Bancada> bancadasQuery = _dbContext.Bancadas.AsNoTracking() | ||||
|                                              .Include(b => b.AgrupacionPolitica); | ||||
|                                       .Where(b => b.EleccionId == eleccionId) | ||||
|                                       .Include(b => b.AgrupacionPolitica); | ||||
|         if (mostrarOcupantes) | ||||
|         { | ||||
|             bancadasQuery = bancadasQuery.Include(b => b.Ocupante); | ||||
| @@ -459,7 +461,7 @@ public class ResultadosController : ControllerBase | ||||
|         return Ok(new { Diputados = diputados, Senadores = senadores }); | ||||
|     } | ||||
|  | ||||
|     private async Task<IActionResult> GetComposicionDesdeProyecciones(Dictionary<string, string> config) | ||||
|     private async Task<IActionResult> GetComposicionDesdeProyecciones(Dictionary<string, string> config, int eleccionId) | ||||
|     { | ||||
|         // --- INICIO DE LA CORRECCIÓN --- | ||||
|         // 1. Obtenemos el ID del ámbito provincial para usarlo en el filtro. | ||||
| @@ -480,8 +482,7 @@ public class ResultadosController : ControllerBase | ||||
|  | ||||
|         var bancasPorAgrupacion = await _dbContext.ProyeccionesBancas | ||||
|             .AsNoTracking() | ||||
|             // --- CAMBIO CLAVE: Añadimos el filtro por AmbitoGeograficoId --- | ||||
|             .Where(p => p.AmbitoGeograficoId == provincia.Id) | ||||
|             .Where(p => p.EleccionId == eleccionId && p.AmbitoGeograficoId == provincia.Id) | ||||
|             .GroupBy(p => new { p.AgrupacionPoliticaId, p.CategoriaId }) | ||||
|             .Select(g => new | ||||
|             { | ||||
| @@ -551,7 +552,7 @@ public class ResultadosController : ControllerBase | ||||
|     } | ||||
|  | ||||
|     [HttpGet("bancadas-detalle")] | ||||
|     public async Task<IActionResult> GetBancadasConOcupantes() | ||||
|     public async Task<IActionResult> GetBancadasConOcupantes([FromRoute] int eleccionId) | ||||
|     { | ||||
|         var config = await _dbContext.Configuraciones.AsNoTracking().ToDictionaryAsync(c => c.Clave, c => c.Valor); | ||||
|  | ||||
| @@ -565,6 +566,7 @@ public class ResultadosController : ControllerBase | ||||
|         // Si el modo oficial SÍ está activo, devolvemos los detalles. | ||||
|         var bancadasConOcupantes = await _dbContext.Bancadas | ||||
|             .AsNoTracking() | ||||
|             .Where(b => b.EleccionId == eleccionId) | ||||
|             .Include(b => b.Ocupante) | ||||
|             .Select(b => new | ||||
|             { | ||||
| @@ -1012,4 +1014,342 @@ public class ResultadosController : ControllerBase | ||||
|             Resultados = resultadosPorMunicipio | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("panel/{ambitoId?}")] | ||||
|     public async Task<IActionResult> GetPanelElectoral(int eleccionId, string? ambitoId, [FromQuery] int categoriaId) | ||||
|     { | ||||
|         if (string.IsNullOrEmpty(ambitoId)) | ||||
|         { | ||||
|             // CASO 1: No hay ID -> Vista Nacional | ||||
|             return await GetPanelNacional(eleccionId, categoriaId); | ||||
|         } | ||||
|  | ||||
|         // CASO 2: El ID es un número (y no un string corto como "02") -> Vista Municipal | ||||
|         // La condición clave es que los IDs de distrito son cortos. Los IDs de BD son más largos. | ||||
|         // O simplemente, un ID de distrito nunca será un ID de municipio. | ||||
|         if (int.TryParse(ambitoId, out int idNumerico) && ambitoId.Length > 2) | ||||
|         { | ||||
|             return await GetPanelMunicipal(eleccionId, idNumerico, categoriaId); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // CASO 3: El ID es un string corto como "02" o "06" -> Vista Provincial | ||||
|             return await GetPanelProvincial(eleccionId, ambitoId, categoriaId); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private async Task<IActionResult> GetPanelMunicipal(int eleccionId, int ambitoId, int categoriaId) | ||||
|     { | ||||
|         // 1. Validar y obtener la entidad del municipio | ||||
|         var municipio = await _dbContext.AmbitosGeograficos.AsNoTracking() | ||||
|             .FirstOrDefaultAsync(a => a.Id == ambitoId && a.NivelId == 30); | ||||
|  | ||||
|         if (municipio == null) return NotFound($"No se encontró el municipio con ID {ambitoId}."); | ||||
|  | ||||
|         // 2. Obtener los votos solo para ESE municipio | ||||
|         var resultadosCrudos = await _dbContext.ResultadosVotos.AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .Where(r => r.EleccionId == eleccionId && | ||||
|                         r.CategoriaId == categoriaId && | ||||
|                         r.AmbitoGeograficoId == ambitoId) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         if (!resultadosCrudos.Any()) | ||||
|         { | ||||
|             // Devolver un DTO vacío pero válido si no hay resultados | ||||
|             return Ok(new PanelElectoralDto | ||||
|             { | ||||
|                 AmbitoNombre = municipio.Nombre, | ||||
|                 MapaData = new List<ResultadoMapaDto>(), // El mapa estará vacío en la vista de un solo municipio | ||||
|                 ResultadosPanel = new List<AgrupacionResultadoDto>(), | ||||
|                 EstadoRecuento = new EstadoRecuentoDto() | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // 3. Calcular los resultados para el panel lateral (son los mismos datos crudos) | ||||
|         var totalVotosMunicipio = (decimal)resultadosCrudos.Sum(r => r.CantidadVotos); | ||||
|         var resultadosPanel = resultadosCrudos | ||||
|             .Select(g => new AgrupacionResultadoDto | ||||
|             { | ||||
|                 Id = g.AgrupacionPolitica.Id, | ||||
|                 Nombre = g.AgrupacionPolitica.Nombre, | ||||
|                 NombreCorto = g.AgrupacionPolitica.NombreCorto, | ||||
|                 Color = g.AgrupacionPolitica.Color, | ||||
|                 Votos = g.CantidadVotos, | ||||
|                 Porcentaje = totalVotosMunicipio > 0 ? (g.CantidadVotos / totalVotosMunicipio) * 100 : 0 | ||||
|             }) | ||||
|             .OrderByDescending(r => r.Votos) | ||||
|             .ToList(); | ||||
|  | ||||
|         var estadoRecuento = await _dbContext.EstadosRecuentos | ||||
|             .AsNoTracking() | ||||
|             .FirstOrDefaultAsync(e => e.EleccionId == eleccionId && e.AmbitoGeograficoId == ambitoId && e.CategoriaId == categoriaId); | ||||
|  | ||||
|         var respuesta = new PanelElectoralDto | ||||
|         { | ||||
|             AmbitoNombre = municipio.Nombre, | ||||
|             MapaData = new List<ResultadoMapaDto>(), // El mapa no muestra sub-geografías aquí | ||||
|             ResultadosPanel = resultadosPanel, | ||||
|             EstadoRecuento = new EstadoRecuentoDto | ||||
|             { | ||||
|                 ParticipacionPorcentaje = estadoRecuento?.ParticipacionPorcentaje ?? 0, | ||||
|                 MesasTotalizadasPorcentaje = estadoRecuento?.MesasTotalizadasPorcentaje ?? 0 | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return Ok(respuesta); | ||||
|     } | ||||
|  | ||||
|     // Este método se ejecutará cuando la URL sea, por ejemplo, /api/elecciones/2/panel/02?categoriaId=2 | ||||
|     private async Task<IActionResult> GetPanelProvincial(int eleccionId, string distritoId, int categoriaId) | ||||
|     { | ||||
|         // 1. Validar y obtener la entidad de la provincia | ||||
|         var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking() | ||||
|         .FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10); | ||||
|  | ||||
|         if (provincia == null) | ||||
|         { | ||||
|             _logger.LogWarning("Panel Provincial: La provincia {DistritoId} no fue encontrada en AmbitosGeograficos.", distritoId); | ||||
|             return NotFound($"No se encontró la provincia con distritoId {distritoId}"); | ||||
|         } | ||||
|  | ||||
|         _logger.LogInformation("Panel Provincial: Provincia {Nombre} ({Id}) encontrada. Buscando municipios...", provincia.Nombre, provincia.Id); | ||||
|  | ||||
|         // 2. Obtener los votos de TODOS los municipios de ESA provincia. | ||||
|         var resultadosCrudos = await _dbContext.ResultadosVotos.AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .Include(r => r.AmbitoGeografico) | ||||
|             .Where(r => r.EleccionId == eleccionId && | ||||
|                         r.CategoriaId == categoriaId && | ||||
|                         r.AmbitoGeografico.DistritoId == distritoId && | ||||
|                         r.AmbitoGeografico.NivelId == 30) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         if (!resultadosCrudos.Any()) | ||||
|         { | ||||
|             return Ok(new PanelElectoralDto | ||||
|             { | ||||
|                 AmbitoNombre = provincia.Nombre, | ||||
|                 MapaData = new List<ResultadoMapaDto>(), | ||||
|                 ResultadosPanel = new List<AgrupacionResultadoDto>(), | ||||
|                 EstadoRecuento = new EstadoRecuentoDto() | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // 3. Calcular los resultados para el PANEL LATERAL (agregado provincial) | ||||
|         var totalVotosProvincia = (decimal)resultadosCrudos.Sum(r => r.CantidadVotos); | ||||
|         var resultadosPanel = resultadosCrudos | ||||
|             .GroupBy(r => r.AgrupacionPolitica.Id) | ||||
|             .Select(g => new AgrupacionResultadoDto | ||||
|             { | ||||
|                 Id = g.Key, | ||||
|                 Nombre = g.First().AgrupacionPolitica.Nombre, | ||||
|                 NombreCorto = g.First().AgrupacionPolitica.NombreCorto, | ||||
|                 Color = g.First().AgrupacionPolitica.Color, | ||||
|                 Votos = g.Sum(r => r.CantidadVotos), | ||||
|                 Porcentaje = totalVotosProvincia > 0 ? (g.Sum(r => r.CantidadVotos) / totalVotosProvincia) * 100 : 0 | ||||
|             }) | ||||
|             .OrderByDescending(r => r.Votos) | ||||
|             .ToList(); | ||||
|  | ||||
|         // --- CORRECCIÓN DEFINITIVA DE LA LÓGICA DEL MAPA --- | ||||
|         var mapaData = resultadosCrudos | ||||
|             .GroupBy(r => r.AmbitoGeografico) // 1. Agrupar por la entidad del municipio | ||||
|             .Select(g => | ||||
|             { | ||||
|                 // 2. DENTRO de cada grupo (municipio), encontrar la fila con más votos | ||||
|                 var ganador = g.OrderByDescending(r => r.CantidadVotos).First(); | ||||
|  | ||||
|                 // 3. Crear UN SOLO objeto ResultadoMapaDto para este municipio | ||||
|                 return new ResultadoMapaDto | ||||
|                 { | ||||
|                     AmbitoId = g.Key.SeccionId!, | ||||
|                     AmbitoNombre = g.Key.Nombre, | ||||
|                     AgrupacionGanadoraId = ganador.AgrupacionPolitica.Id, | ||||
|                     ColorGanador = ganador.AgrupacionPolitica.Color ?? "#808080" | ||||
|                 }; | ||||
|             }) | ||||
|             .ToList(); | ||||
|  | ||||
|         var estadoRecuento = await _dbContext.EstadosRecuentosGenerales.AsNoTracking() | ||||
|             .FirstOrDefaultAsync(e => e.EleccionId == eleccionId && e.AmbitoGeograficoId == provincia.Id && e.CategoriaId == categoriaId); | ||||
|  | ||||
|         var respuesta = new PanelElectoralDto | ||||
|         { | ||||
|             AmbitoNombre = provincia.Nombre, | ||||
|             MapaData = mapaData, | ||||
|             ResultadosPanel = resultadosPanel, | ||||
|             EstadoRecuento = new EstadoRecuentoDto() // Simplificado para el ejemplo | ||||
|         }; | ||||
|  | ||||
|         return Ok(respuesta); | ||||
|     } | ||||
|  | ||||
|     private async Task<IActionResult> GetPanelNacional(int eleccionId, int categoriaId) | ||||
|     { | ||||
|         var resultadosCrudos = await _dbContext.ResultadosVotos.AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .Include(r => r.AmbitoGeografico) | ||||
|             .Where(r => r.EleccionId == eleccionId && r.CategoriaId == categoriaId) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         if (!resultadosCrudos.Any()) | ||||
|         { | ||||
|             return Ok(new PanelElectoralDto | ||||
|             { | ||||
|                 AmbitoNombre = "Argentina", | ||||
|                 MapaData = new List<ResultadoMapaDto>(), | ||||
|                 ResultadosPanel = new List<AgrupacionResultadoDto>(), | ||||
|                 EstadoRecuento = new EstadoRecuentoDto() | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // 2. Calcular los resultados para el panel | ||||
|         var resultadosPanel = resultadosCrudos | ||||
|         .GroupBy(r => r.AgrupacionPolitica.Id) | ||||
|         .Select(g => new AgrupacionResultadoDto | ||||
|         { | ||||
|             // g.Key ahora es el ID de la agrupación | ||||
|             Id = g.Key, | ||||
|             // Tomamos los datos de la agrupación del primer elemento del grupo | ||||
|             Nombre = g.First().AgrupacionPolitica.Nombre, | ||||
|             NombreCorto = g.First().AgrupacionPolitica.NombreCorto, | ||||
|             Color = g.First().AgrupacionPolitica.Color, | ||||
|             Votos = g.Sum(r => r.CantidadVotos), | ||||
|             Porcentaje = (decimal)g.Sum(r => r.CantidadVotos) * 100 / resultadosCrudos.Sum(r => r.CantidadVotos) | ||||
|         }) | ||||
|             .OrderByDescending(r => r.Votos) | ||||
|             .ToList(); | ||||
|  | ||||
|         // 3. Calcular los datos para el mapa (ganador por provincia/distrito) | ||||
|         var mapaData = resultadosCrudos | ||||
|         .Where(r => !string.IsNullOrEmpty(r.AmbitoGeografico.DistritoId)) | ||||
|         .GroupBy(r => r.AmbitoGeografico.DistritoId) | ||||
|         .Select(g => | ||||
|         { | ||||
|             var ganador = g.GroupBy(r => r.AgrupacionPoliticaId) | ||||
|                 .Select(pg => new { Votos = pg.Sum(r => r.CantidadVotos), Agrupacion = pg.First().AgrupacionPolitica }) | ||||
|                 .OrderByDescending(x => x.Votos) | ||||
|                 .First(); | ||||
|  | ||||
|             var provinciaAmbito = _dbContext.AmbitosGeograficos.AsNoTracking() | ||||
|                 .FirstOrDefault(a => a.DistritoId == g.Key && a.NivelId == 10); | ||||
|  | ||||
|             return new ResultadoMapaDto | ||||
|             { | ||||
|                 AmbitoId = g.Key!, | ||||
|                 AmbitoNombre = provinciaAmbito?.Nombre ?? "Desconocido", // <-- ENVIAMOS EL NOMBRE DE LA PROVINCIA | ||||
|                 AgrupacionGanadoraId = ganador.Agrupacion.Id, | ||||
|                 ColorGanador = ganador.Agrupacion.Color ?? "#808080" | ||||
|             }; | ||||
|         }) | ||||
|         .ToList(); | ||||
|  | ||||
|         // 4. Obtener el estado del recuento general | ||||
|         // Asumimos que existe un registro en EstadosRecuentosGenerales para el ámbito de la elección | ||||
|         var estadoRecuento = await _dbContext.EstadosRecuentosGenerales | ||||
|             .AsNoTracking() | ||||
|             .FirstOrDefaultAsync(e => e.EleccionId == eleccionId); | ||||
|  | ||||
|         var respuesta = new PanelElectoralDto | ||||
|         { | ||||
|             AmbitoNombre = "Argentina", | ||||
|             MapaData = mapaData, | ||||
|             ResultadosPanel = resultadosPanel, | ||||
|             EstadoRecuento = new EstadoRecuentoDto() | ||||
|         }; | ||||
|         return Ok(respuesta); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("mapa-resultados")] | ||||
|     public async Task<IActionResult> GetResultadosMapaPorMunicipio( | ||||
|     [FromRoute] int eleccionId, | ||||
|     [FromQuery] int categoriaId, | ||||
|     [FromQuery] string? distritoId = null) | ||||
|     { | ||||
|         if (string.IsNullOrEmpty(distritoId)) | ||||
|         { | ||||
|             // --- VISTA NACIONAL (Ya corregida y funcionando) --- | ||||
|             var votosAgregadosPorProvincia = await _dbContext.ResultadosVotos | ||||
|                 .AsNoTracking() | ||||
|                 .Where(r => r.EleccionId == eleccionId | ||||
|                             && r.CategoriaId == categoriaId | ||||
|                             && r.AmbitoGeografico.NivelId == 30 | ||||
|                             && r.AmbitoGeografico.DistritoId != null) | ||||
|                 .GroupBy(r => new { r.AmbitoGeografico.DistritoId, r.AgrupacionPoliticaId }) | ||||
|                 .Select(g => new | ||||
|                 { | ||||
|                     g.Key.DistritoId, | ||||
|                     g.Key.AgrupacionPoliticaId, | ||||
|                     TotalVotos = g.Sum(r => r.CantidadVotos) | ||||
|                 }) | ||||
|                 .ToListAsync(); | ||||
|  | ||||
|             var agrupacionesInfo = await _dbContext.AgrupacionesPoliticas.AsNoTracking().ToDictionaryAsync(a => a.Id); | ||||
|             var provinciasInfo = await _dbContext.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 10).ToListAsync(); | ||||
|  | ||||
|             var ganadoresPorProvincia = votosAgregadosPorProvincia | ||||
|                 .GroupBy(r => r.DistritoId) | ||||
|                 .Select(g => g.OrderByDescending(x => x.TotalVotos).First()) | ||||
|                 .ToList(); | ||||
|  | ||||
|             var mapaDataNacional = ganadoresPorProvincia.Select(g => new ResultadoMapaDto | ||||
|             { | ||||
|                 AmbitoId = g.DistritoId!, | ||||
|                 AmbitoNombre = provinciasInfo.FirstOrDefault(p => p.DistritoId == g.DistritoId)?.Nombre ?? "Desconocido", | ||||
|                 AgrupacionGanadoraId = g.AgrupacionPoliticaId, | ||||
|                 ColorGanador = agrupacionesInfo.GetValueOrDefault(g.AgrupacionPoliticaId)?.Color ?? "#808080" | ||||
|             }).ToList(); | ||||
|  | ||||
|             return Ok(mapaDataNacional); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // --- VISTA PROVINCIAL (AHORA CORREGIDA CON LA MISMA LÓGICA) --- | ||||
|  | ||||
|             // PASO 1: Agrupar por IDs y sumar votos en la base de datos. | ||||
|             var votosAgregadosPorMunicipio = await _dbContext.ResultadosVotos | ||||
|                 .AsNoTracking() | ||||
|                 .Where(r => r.EleccionId == eleccionId | ||||
|                             && r.CategoriaId == categoriaId | ||||
|                             && r.AmbitoGeografico.DistritoId == distritoId | ||||
|                             && r.AmbitoGeografico.NivelId == 30) | ||||
|                 // Agrupamos por los IDs (int y string) | ||||
|                 .GroupBy(r => new { r.AmbitoGeograficoId, r.AgrupacionPoliticaId }) | ||||
|                 .Select(g => new | ||||
|                 { | ||||
|                     g.Key.AmbitoGeograficoId, | ||||
|                     g.Key.AgrupacionPoliticaId, | ||||
|                     TotalVotos = g.Sum(r => r.CantidadVotos) | ||||
|                 }) | ||||
|                 .ToListAsync(); | ||||
|  | ||||
|             // PASO 2: Encontrar el ganador para cada municipio en memoria. | ||||
|             var ganadoresPorMunicipio = votosAgregadosPorMunicipio | ||||
|                 .GroupBy(r => r.AmbitoGeograficoId) | ||||
|                 .Select(g => g.OrderByDescending(x => x.TotalVotos).First()) | ||||
|                 .ToList(); | ||||
|  | ||||
|             // PASO 3: Hidratar con los nombres y colores (muy rápido). | ||||
|             var idsMunicipios = ganadoresPorMunicipio.Select(g => g.AmbitoGeograficoId).ToList(); | ||||
|             var idsAgrupaciones = ganadoresPorMunicipio.Select(g => g.AgrupacionPoliticaId).ToList(); | ||||
|  | ||||
|             var municipiosInfo = await _dbContext.AmbitosGeograficos.AsNoTracking() | ||||
|                 .Where(a => idsMunicipios.Contains(a.Id)).ToDictionaryAsync(a => a.Id); | ||||
|  | ||||
|             var agrupacionesInfo = await _dbContext.AgrupacionesPoliticas.AsNoTracking() | ||||
|                 .Where(a => idsAgrupaciones.Contains(a.Id)).ToDictionaryAsync(a => a.Id); | ||||
|  | ||||
|             // Mapeo final a DTO. | ||||
|             var mapaDataProvincial = ganadoresPorMunicipio.Select(g => new ResultadoMapaDto | ||||
|             { | ||||
|                 AmbitoId = g.AmbitoGeograficoId.ToString(), | ||||
|                 AmbitoNombre = municipiosInfo.GetValueOrDefault(g.AmbitoGeograficoId)?.Nombre ?? "Desconocido", | ||||
|                 AgrupacionGanadoraId = g.AgrupacionPoliticaId, | ||||
|                 ColorGanador = agrupacionesInfo.GetValueOrDefault(g.AgrupacionPoliticaId)?.Color ?? "#808080" | ||||
|             }).ToList(); | ||||
|  | ||||
|             return Ok(mapaDataProvincial); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user