| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  | // src/Elecciones.Api/Controllers/ResultadosController.cs | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | using Elecciones.Core.DTOs; | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  | using Elecciones.Core.DTOs.ApiResponses; | 
					
						
							|  |  |  | using Elecciones.Database; | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  | using Elecciones.Database.Entities; | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  | using Microsoft.AspNetCore.Mvc; | 
					
						
							|  |  |  | using Microsoft.EntityFrameworkCore; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Elecciones.Api.Controllers; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | [ApiController] | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | [Route("api/elecciones/{eleccionId}")] | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  | public class ResultadosController : ControllerBase | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     private readonly EleccionesDbContext _dbContext; | 
					
						
							|  |  |  |     private readonly ILogger<ResultadosController> _logger; | 
					
						
							| 
									
										
										
										
											2025-08-25 15:04:09 -03:00
										 |  |  |     private readonly IConfiguration _configuration; | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 15:04:09 -03:00
										 |  |  |     public ResultadosController(EleccionesDbContext dbContext, ILogger<ResultadosController> logger, IConfiguration configuration) | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |     { | 
					
						
							|  |  |  |         _dbContext = dbContext; | 
					
						
							|  |  |  |         _logger = logger; | 
					
						
							| 
									
										
										
										
											2025-08-25 15:04:09 -03:00
										 |  |  |         _configuration = configuration; | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |     private string? GetLogoUrl( | 
					
						
							|  |  |  |         string agrupacionId, | 
					
						
							|  |  |  |         int categoriaId, | 
					
						
							|  |  |  |         int? ambitoId, | 
					
						
							|  |  |  |         List<LogoAgrupacionCategoria> todosLosLogos) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Prioridad 1: Buscar un logo específico para este partido, categoría Y ámbito. | 
					
						
							|  |  |  |         var logoEspecifico = todosLosLogos.FirstOrDefault(l => | 
					
						
							|  |  |  |             l.AgrupacionPoliticaId == agrupacionId && | 
					
						
							|  |  |  |             l.CategoriaId == categoriaId && | 
					
						
							|  |  |  |             l.AmbitoGeograficoId == ambitoId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (logoEspecifico != null) return logoEspecifico.LogoUrl; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Prioridad 2: Si no hay uno específico, buscar un logo general (sin ámbito) para este partido y categoría. | 
					
						
							|  |  |  |         var logoGeneral = todosLosLogos.FirstOrDefault(l => | 
					
						
							|  |  |  |             l.AgrupacionPoliticaId == agrupacionId && | 
					
						
							|  |  |  |             l.CategoriaId == categoriaId && | 
					
						
							|  |  |  |             l.AmbitoGeograficoId == null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return logoGeneral?.LogoUrl; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-05 11:38:25 -03:00
										 |  |  |     [HttpGet("partido/{municipioId}")] | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |     public async Task<IActionResult> GetResultadosPorPartido([FromRoute] int eleccionId, string municipioId, [FromQuery] int categoriaId) | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-09-02 17:08:56 -03:00
										 |  |  |         var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking() | 
					
						
							|  |  |  |             .FirstOrDefaultAsync(a => a.SeccionId == municipioId && a.NivelId == 30); | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (ambito == null) | 
					
						
							|  |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-09-02 17:08:56 -03:00
										 |  |  |             return NotFound(new { message = $"No se encontró el partido con ID {municipioId}" }); | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 17:08:56 -03:00
										 |  |  |         var estadoRecuento = await _dbContext.EstadosRecuentos.AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .FirstOrDefaultAsync(e => e.EleccionId == eleccionId && e.AmbitoGeograficoId == ambito.Id && e.CategoriaId == categoriaId); | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         var agrupacionIds = await _dbContext.ResultadosVotos | 
					
						
							| 
									
										
										
										
											2025-09-02 17:08:56 -03:00
										 |  |  |             .Where(rv => rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId) | 
					
						
							|  |  |  |             .Select(rv => rv.AgrupacionPoliticaId).Distinct().ToListAsync(); | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 17:08:56 -03:00
										 |  |  |         var logosRelevantes = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking() | 
					
						
							|  |  |  |             .Where(l => l.CategoriaId == categoriaId && // <-- Usamos la categoría del parámetro | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                         agrupacionIds.Contains(l.AgrupacionPoliticaId) && | 
					
						
							|  |  |  |                         (l.AmbitoGeograficoId == null || l.AmbitoGeograficoId == ambito.Id)) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 17:08:56 -03:00
										 |  |  |         var resultadosVotos = await _dbContext.ResultadosVotos.AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |             .Include(rv => rv.AgrupacionPolitica) | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .Where(rv => rv.EleccionId == eleccionId && rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId) | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-05 11:38:25 -03:00
										 |  |  |         var candidatosRelevantes = await _dbContext.CandidatosOverrides.AsNoTracking() | 
					
						
							|  |  |  |             .Where(c => c.CategoriaId == categoriaId && agrupacionIds.Contains(c.AgrupacionPoliticaId) && | 
					
						
							|  |  |  |                 (c.AmbitoGeograficoId == null || c.AmbitoGeograficoId == ambito.Id)) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |         long totalVotosPositivos = resultadosVotos.Sum(r => r.CantidadVotos); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var respuestaDto = new MunicipioResultadosDto | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             MunicipioNombre = ambito.Nombre, | 
					
						
							| 
									
										
										
										
											2025-09-02 17:08:56 -03:00
										 |  |  |             UltimaActualizacion = estadoRecuento?.FechaTotalizacion ?? DateTime.UtcNow, | 
					
						
							|  |  |  |             PorcentajeEscrutado = estadoRecuento?.MesasTotalizadasPorcentaje ?? 0, | 
					
						
							|  |  |  |             PorcentajeParticipacion = estadoRecuento?.ParticipacionPorcentaje ?? 0, | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |             Resultados = resultadosVotos.Select(rv => | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-09-02 17:08:56 -03:00
										 |  |  |                 var logoUrl = logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == ambito.Id)?.LogoUrl | 
					
						
							|  |  |  |                            ?? logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == null)?.LogoUrl; | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-05 11:38:25 -03:00
										 |  |  |                 var nombreCandidato = candidatosRelevantes.FirstOrDefault(c => c.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && c.AmbitoGeograficoId == ambito.Id)?.NombreCandidato | 
					
						
							|  |  |  |                            ?? candidatosRelevantes.FirstOrDefault(c => c.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && c.AmbitoGeograficoId == null)?.NombreCandidato; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                 return new AgrupacionResultadoDto | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     Id = rv.AgrupacionPolitica.Id, | 
					
						
							|  |  |  |                     Nombre = rv.AgrupacionPolitica.Nombre, | 
					
						
							|  |  |  |                     NombreCorto = rv.AgrupacionPolitica.NombreCorto, | 
					
						
							|  |  |  |                     Color = rv.AgrupacionPolitica.Color, | 
					
						
							| 
									
										
										
										
											2025-09-02 17:08:56 -03:00
										 |  |  |                     LogoUrl = logoUrl, | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                     Votos = rv.CantidadVotos, | 
					
						
							| 
									
										
										
										
											2025-09-05 11:38:25 -03:00
										 |  |  |                     NombreCandidato = nombreCandidato, | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                     Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0 | 
					
						
							|  |  |  |                 }; | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |             }).OrderByDescending(r => r.Votos).ToList(), | 
					
						
							|  |  |  |             VotosAdicionales = new VotosAdicionalesDto | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-09-02 17:08:56 -03:00
										 |  |  |                 EnBlanco = estadoRecuento?.VotosEnBlanco ?? 0, | 
					
						
							|  |  |  |                 Nulos = estadoRecuento?.VotosNulos ?? 0, | 
					
						
							|  |  |  |                 Recurridos = estadoRecuento?.VotosRecurridos ?? 0 | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |             } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(respuestaDto); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     [HttpGet("provincia/{distritoId}")] | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |     public async Task<IActionResult> GetResultadosProvinciales([FromRoute] int eleccionId, string distritoId) | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |         var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking() | 
					
						
							|  |  |  |             .FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10); | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         if (provincia == null) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             return NotFound($"No se encontró la provincia con distritoId {distritoId}"); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         var estadosPorCategoria = await _dbContext.EstadosRecuentosGenerales.AsNoTracking() | 
					
						
							|  |  |  |             .Include(e => e.CategoriaElectoral) | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .Where(e => e.EleccionId == eleccionId && e.AmbitoGeograficoId == provincia.Id) | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |             .ToDictionaryAsync(e => e.CategoriaId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var resultadosPorMunicipio = await _dbContext.ResultadosVotos | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |             .Include(r => r.AgrupacionPolitica) | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .Where(r => r.EleccionId == eleccionId && r.AmbitoGeografico.NivelId == 30) // Nivel 30 = Municipio | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |         // Obtenemos TODOS los logos relevantes en una sola consulta | 
					
						
							|  |  |  |         var todosLosLogos = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync(); | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:43:27 -03:00
										 |  |  |         // --- LÓGICA DE AGRUPACIÓN Y CÁLCULO CORREGIDA --- | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |         var resultadosAgrupados = resultadosPorMunicipio | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |         .GroupBy(r => r.CategoriaId) | 
					
						
							|  |  |  |         .Select(g => new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             CategoriaId = g.Key, | 
					
						
							|  |  |  |             CategoriaNombre = estadosPorCategoria.ContainsKey(g.Key) ? estadosPorCategoria[g.Key].CategoriaElectoral.Nombre : "Desconocido", | 
					
						
							|  |  |  |             EstadoRecuento = estadosPorCategoria.GetValueOrDefault(g.Key), | 
					
						
							|  |  |  |             TotalVotosCategoria = g.Sum(r => r.CantidadVotos), | 
					
						
							|  |  |  |             // Agrupamos por el ID de la agrupación, no por el objeto, para evitar duplicados | 
					
						
							|  |  |  |             ResultadosAgrupados = g.GroupBy(r => r.AgrupacionPoliticaId) | 
					
						
							| 
									
										
										
										
											2025-09-02 15:43:27 -03:00
										 |  |  |                                        .Select(partidoGroup => new | 
					
						
							|  |  |  |                                        { | 
					
						
							|  |  |  |                                            Agrupacion = partidoGroup.First().AgrupacionPolitica, | 
					
						
							|  |  |  |                                            Votos = partidoGroup.Sum(r => r.CantidadVotos) | 
					
						
							|  |  |  |                                        }) | 
					
						
							|  |  |  |                                        .ToList() | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |             .Select(g => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 g.CategoriaId, | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                 g.CategoriaNombre, | 
					
						
							|  |  |  |                 g.EstadoRecuento, | 
					
						
							| 
									
										
										
										
											2025-09-02 15:43:27 -03:00
										 |  |  |                 Resultados = g.ResultadosAgrupados | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |                           .Select(r => | 
					
						
							|  |  |  |                           { | 
					
						
							|  |  |  |                               // --- USAMOS EL NUEVO MÉTODO HELPER --- | 
					
						
							|  |  |  |                               // Para el resumen provincial, el ámbito es siempre el de la provincia. | 
					
						
							|  |  |  |                               var logoUrl = GetLogoUrl(r.Agrupacion.Id, g.CategoriaId, provincia.Id, todosLosLogos); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                               return new | 
					
						
							| 
									
										
										
										
											2025-09-02 15:43:27 -03:00
										 |  |  |                               { | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |                                   Id = r.Agrupacion.Id, | 
					
						
							|  |  |  |                                   r.Agrupacion.Nombre, | 
					
						
							|  |  |  |                                   r.Agrupacion.NombreCorto, | 
					
						
							|  |  |  |                                   r.Agrupacion.Color, | 
					
						
							|  |  |  |                                   LogoUrl = logoUrl, | 
					
						
							|  |  |  |                                   r.Votos, | 
					
						
							|  |  |  |                                   Porcentaje = g.TotalVotosCategoria > 0 ? ((decimal)r.Votos * 100 / g.TotalVotosCategoria) : 0 | 
					
						
							|  |  |  |                               }; | 
					
						
							|  |  |  |                           }) | 
					
						
							|  |  |  |                           .OrderByDescending(r => r.Votos) | 
					
						
							|  |  |  |                           .ToList() | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |         .OrderBy(c => c.CategoriaId) | 
					
						
							|  |  |  |         .ToList(); | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |         return Ok(resultadosAgrupados); | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |     [HttpGet("bancas-por-seccion/{seccionId}/{camara}")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetBancasPorSeccion([FromRoute] int eleccionId, string seccionId, string camara) | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |         // 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. | 
					
						
							|  |  |  |         // O puedes usar un enum si lo tienes definido. | 
					
						
							|  |  |  |         int CategoriaId; | 
					
						
							|  |  |  |         if (camara.Equals("diputados", StringComparison.OrdinalIgnoreCase)) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             CategoriaId = 6; // Asume que 5 es el CategoriaId para Diputados Provinciales | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else if (camara.Equals("senadores", StringComparison.OrdinalIgnoreCase)) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             CategoriaId = 5; // Asume que 6 es el CategoriaId para Senadores Provinciales | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             return BadRequest(new { message = "El tipo de cámara especificado no es válido. Use 'diputados' o 'senadores'." }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  |         var seccion = await _dbContext.AmbitosGeograficos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |             .FirstOrDefaultAsync(a => a.SeccionProvincialId == seccionId && a.NivelId == 20); | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (seccion == null) | 
					
						
							|  |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |             _logger.LogWarning("No se encontró la sección electoral con SeccionProvincialId: {SeccionId}", seccionId); | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  |             return NotFound(new { message = $"No se encontró la sección electoral con ID {seccionId}" }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |         // --- CAMBIO 3: Filtrar también por el cargo (cámara) --- | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  |         var proyecciones = await _dbContext.ProyeccionesBancas | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |             .Include(p => p.AgrupacionPolitica) | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .Where(p => p.EleccionId == eleccionId && p.AmbitoGeograficoId == seccion.Id && p.CategoriaId == CategoriaId) | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  |             .Select(p => new | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |                 AgrupacionId = p.AgrupacionPolitica.Id, // Añadir para el 'key' en React | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  |                 AgrupacionNombre = p.AgrupacionPolitica.Nombre, | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |                 NombreCorto = p.AgrupacionPolitica.NombreCorto, // Añadir para el frontend | 
					
						
							|  |  |  |                 Color = p.AgrupacionPolitica.Color, // Añadir para el frontend | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  |                 Bancas = p.NroBancas | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .OrderByDescending(p => p.Bancas) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!proyecciones.Any()) | 
					
						
							|  |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |             _logger.LogWarning("No se encontraron proyecciones de bancas para la sección: {SeccionNombre} y cámara: {Camara}", seccion.Nombre, camara); | 
					
						
							|  |  |  |             return NotFound(new { message = $"No se han encontrado proyecciones de bancas para la sección {seccion.Nombre} ({camara})" }); | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             SeccionNombre = seccion.Nombre, | 
					
						
							|  |  |  |             Proyeccion = proyecciones | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     [HttpGet("mapa")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetResultadosParaMapa() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         var maxVotosPorAmbito = _dbContext.ResultadosVotos | 
					
						
							|  |  |  |             .GroupBy(rv => rv.AmbitoGeograficoId) | 
					
						
							|  |  |  |             .Select(g => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 AmbitoId = g.Key, | 
					
						
							|  |  |  |                 MaxVotos = g.Max(v => v.CantidadVotos) | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var resultadosGanadores = await _dbContext.ResultadosVotos | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |     .Join( | 
					
						
							|  |  |  |         maxVotosPorAmbito, | 
					
						
							|  |  |  |         voto => new { AmbitoId = voto.AmbitoGeograficoId, Votos = voto.CantidadVotos }, | 
					
						
							|  |  |  |         max => new { AmbitoId = max.AmbitoId, Votos = max.MaxVotos }, | 
					
						
							|  |  |  |         (voto, max) => voto | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     .Include(rv => rv.AmbitoGeografico) | 
					
						
							|  |  |  |     .Include(rv => rv.AgrupacionPolitica) | 
					
						
							|  |  |  |     .Where(rv => rv.AmbitoGeografico.NivelId == 30) | 
					
						
							|  |  |  |     .Select(rv => new | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         AmbitoId = rv.AmbitoGeografico.Id, | 
					
						
							|  |  |  |         DepartamentoNombre = rv.AmbitoGeografico.Nombre, | 
					
						
							|  |  |  |         AgrupacionGanadoraId = rv.AgrupacionPoliticaId, | 
					
						
							|  |  |  |         ColorGanador = rv.AgrupacionPolitica.Color | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |     .ToListAsync(); | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return Ok(resultadosGanadores); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |     [HttpGet("municipio/{ambitoId}")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetResultadosPorMunicipio(int ambitoId, [FromQuery] int categoriaId) | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         _logger.LogInformation("Buscando resultados para AmbitoGeograficoId: {AmbitoId}, CategoriaId: {CategoriaId}", ambitoId, categoriaId); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |         // Validamos que el ámbito exista | 
					
						
							|  |  |  |         var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking() | 
					
						
							|  |  |  |             .FirstOrDefaultAsync(a => a.Id == ambitoId && a.NivelId == 30); | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         if (ambito == null) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             _logger.LogWarning("No se encontró el municipio con ID: {AmbitoId}", ambitoId); | 
					
						
							|  |  |  |             return NotFound($"No se encontró el municipio con ID {ambitoId}"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Obtenemos el estado del recuento para el ámbito | 
					
						
							|  |  |  |         var estadoRecuento = await _dbContext.EstadosRecuentos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .FirstOrDefaultAsync(e => e.AmbitoGeograficoId == ambito.Id); | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |         // Obtenemos los votos para ESE municipio y ESA categoría | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |         var resultadosVotos = await _dbContext.ResultadosVotos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |             .Include(rv => rv.AgrupacionPolitica) | 
					
						
							|  |  |  |             .Where(rv => rv.AmbitoGeograficoId == ambitoId && rv.CategoriaId == categoriaId) | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |         // Calculamos el total de votos solo para esta selección | 
					
						
							|  |  |  |         var totalVotosPositivos = (decimal)resultadosVotos.Sum(r => r.CantidadVotos); | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         // Mapeamos los resultados de los partidos | 
					
						
							|  |  |  |         var resultadosPartidosDto = resultadosVotos | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |             .OrderByDescending(r => r.CantidadVotos) | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |             .Select(rv => new AgrupacionResultadoDto | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                 Id = rv.AgrupacionPolitica.Id, | 
					
						
							|  |  |  |                 Nombre = rv.AgrupacionPolitica.NombreCorto ?? rv.AgrupacionPolitica.Nombre, | 
					
						
							|  |  |  |                 Color = rv.AgrupacionPolitica.Color, | 
					
						
							|  |  |  |                 Votos = rv.CantidadVotos, | 
					
						
							|  |  |  |                 Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos / totalVotosPositivos) * 100 : 0 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |             }).ToList(); | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         // Construimos la respuesta completa del DTO | 
					
						
							|  |  |  |         var respuestaDto = new MunicipioResultadosDto | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             MunicipioNombre = ambito.Nombre, | 
					
						
							|  |  |  |             UltimaActualizacion = estadoRecuento?.FechaTotalizacion ?? DateTime.UtcNow, // Use null-conditional operator | 
					
						
							|  |  |  |             PorcentajeEscrutado = estadoRecuento?.MesasTotalizadasPorcentaje ?? 0, | 
					
						
							|  |  |  |             PorcentajeParticipacion = estadoRecuento?.ParticipacionPorcentaje ?? 0, | 
					
						
							|  |  |  |             Resultados = resultadosPartidosDto, | 
					
						
							|  |  |  |             VotosAdicionales = new VotosAdicionalesDto // Assuming default constructor is fine | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 EnBlanco = estadoRecuento?.VotosEnBlanco ?? 0, | 
					
						
							|  |  |  |                 Nulos = estadoRecuento?.VotosNulos ?? 0, | 
					
						
							|  |  |  |                 Recurridos = estadoRecuento?.VotosRecurridos ?? 0 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(respuestaDto); | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     [HttpGet("composicion-congreso")] | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |     public async Task<IActionResult> GetComposicionCongreso([FromRoute] int eleccionId) | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |     { | 
					
						
							|  |  |  |         var config = await _dbContext.Configuraciones | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .ToDictionaryAsync(c => c.Clave, c => c.Valor); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  |         // Aquí está el interruptor | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         config.TryGetValue("UsarDatosDeBancadasOficiales", out var usarDatosOficialesValue); | 
					
						
							|  |  |  |         bool usarDatosOficiales = usarDatosOficialesValue == "true"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (usarDatosOficiales) | 
					
						
							|  |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  |             // Si el interruptor está en 'true', llama a este método | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             return await GetComposicionDesdeBancadasOficiales(config, eleccionId); | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         } | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  |             // Si está en 'false' o no existe, llama a este otro | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             return await GetComposicionDesdeProyecciones(config, eleccionId); | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |     // En ResultadosController.cs | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |     private async Task<IActionResult> GetComposicionDesdeBancadasOficiales(Dictionary<string, string> config, int eleccionId) | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |         config.TryGetValue("MostrarOcupantes", out var mostrarOcupantesValue); | 
					
						
							|  |  |  |         bool mostrarOcupantes = mostrarOcupantesValue == "true"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         IQueryable<Bancada> bancadasQuery = _dbContext.Bancadas.AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |                                       .Where(b => b.EleccionId == eleccionId) | 
					
						
							|  |  |  |                                       .Include(b => b.AgrupacionPolitica); | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |         if (mostrarOcupantes) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             bancadasQuery = bancadasQuery.Include(b => b.Ocupante); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         var bancadas = await bancadasQuery.ToListAsync(); | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         var bancasPorAgrupacion = bancadas | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  |             .Where(b => b.AgrupacionPolitica != null) | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |             // Agrupamos por el ID del partido, que es un valor único y estable | 
					
						
							|  |  |  |             .GroupBy(b => b.AgrupacionPolitica!.Id) | 
					
						
							|  |  |  |             .Select(g => | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Tomamos la información de la agrupación del primer elemento (todas son iguales) | 
					
						
							|  |  |  |                 var primeraBancaDelGrupo = g.First(); | 
					
						
							|  |  |  |                 var agrupacion = primeraBancaDelGrupo.AgrupacionPolitica!; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Filtramos los ocupantes solo para este grupo | 
					
						
							|  |  |  |                 var ocupantesDelPartido = mostrarOcupantes | 
					
						
							|  |  |  |                     ? g.Select(b => b.Ocupante).Where(o => o != null).ToList() | 
					
						
							|  |  |  |                     : new List<OcupanteBanca?>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return new | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     Agrupacion = agrupacion, | 
					
						
							|  |  |  |                     Camara = primeraBancaDelGrupo.Camara, | 
					
						
							|  |  |  |                     BancasTotales = g.Count(), | 
					
						
							|  |  |  |                     Ocupantes = ocupantesDelPartido | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |             .ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var presidenteDiputados = bancasPorAgrupacion | 
					
						
							|  |  |  |             .Where(b => b.Camara == Core.Enums.TipoCamara.Diputados) | 
					
						
							|  |  |  |             .OrderByDescending(b => b.BancasTotales) | 
					
						
							|  |  |  |             .FirstOrDefault()?.Agrupacion; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         config.TryGetValue("PresidenciaSenadores", out var idPartidoPresidenteSenadores); | 
					
						
							|  |  |  |         var presidenteSenadores = await _dbContext.AgrupacionesPoliticas.FindAsync(idPartidoPresidenteSenadores); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |         object MapearPartidos(Core.Enums.TipoCamara camara) | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  |             var partidosDeCamara = bancasPorAgrupacion.Where(b => b.Camara == camara); | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |             if (camara == Core.Enums.TipoCamara.Diputados) | 
					
						
							|  |  |  |                 partidosDeCamara = partidosDeCamara.OrderBy(p => p.Agrupacion.OrdenDiputados ?? 999); | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |                 partidosDeCamara = partidosDeCamara.OrderBy(p => p.Agrupacion.OrdenSenadores ?? 999); | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |             return partidosDeCamara | 
					
						
							|  |  |  |                 .OrderByDescending(p => p.BancasTotales) | 
					
						
							|  |  |  |                 .Select(p => new | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     p.Agrupacion.Id, | 
					
						
							|  |  |  |                     p.Agrupacion.Nombre, | 
					
						
							|  |  |  |                     p.Agrupacion.NombreCorto, | 
					
						
							|  |  |  |                     p.Agrupacion.Color, | 
					
						
							|  |  |  |                     p.BancasTotales, | 
					
						
							|  |  |  |                     p.Ocupantes // Pasamos la lista de ocupantes ya filtrada | 
					
						
							|  |  |  |                 }).ToList(); | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var diputados = new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             CamaraNombre = "Cámara de Diputados", | 
					
						
							|  |  |  |             TotalBancas = 92, | 
					
						
							|  |  |  |             BancasEnJuego = 0, | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |             Partidos = MapearPartidos(Core.Enums.TipoCamara.Diputados), | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |             PresidenteBancada = presidenteDiputados != null ? new { presidenteDiputados.Color } : null | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var senadores = new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             CamaraNombre = "Cámara de Senadores", | 
					
						
							|  |  |  |             TotalBancas = 46, | 
					
						
							|  |  |  |             BancasEnJuego = 0, | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |             Partidos = MapearPartidos(Core.Enums.TipoCamara.Senadores), | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |             PresidenteBancada = presidenteSenadores != null ? new { presidenteSenadores.Color } : null | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(new { Diputados = diputados, Senadores = senadores }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |     private async Task<IActionResult> GetComposicionDesdeProyecciones(Dictionary<string, string> config, int eleccionId) | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |         // --- INICIO DE LA CORRECCIÓN --- | 
					
						
							|  |  |  |         // 1. Obtenemos el ID del ámbito provincial para usarlo en el filtro. | 
					
						
							|  |  |  |         var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking() | 
					
						
							|  |  |  |             .FirstOrDefaultAsync(a => a.NivelId == 10); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (provincia == null) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             // Si no se encuentra la provincia, no podemos continuar. | 
					
						
							|  |  |  |             // Devolvemos un objeto vacío para no romper el frontend. | 
					
						
							|  |  |  |             return Ok(new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 Diputados = new { Partidos = new List<object>() }, | 
					
						
							|  |  |  |                 Senadores = new { Partidos = new List<object>() } | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // --- FIN DE LA CORRECCIÓN --- | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         var bancasPorAgrupacion = await _dbContext.ProyeccionesBancas | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .Where(p => p.EleccionId == eleccionId && p.AmbitoGeograficoId == provincia.Id) | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |             .GroupBy(p => new { p.AgrupacionPoliticaId, p.CategoriaId }) | 
					
						
							|  |  |  |             .Select(g => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 AgrupacionId = g.Key.AgrupacionPoliticaId, | 
					
						
							|  |  |  |                 CategoriaId = g.Key.CategoriaId, | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |                 // Ahora la suma es correcta porque solo considera los registros a nivel provincial | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |                 BancasTotales = g.Sum(p => p.NroBancas) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         var todasAgrupaciones = await _dbContext.AgrupacionesPoliticas.AsNoTracking().ToDictionaryAsync(a => a.Id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         config.TryGetValue("PresidenciaSenadores", out var idPartidoPresidenteSenadores); | 
					
						
							|  |  |  |         todasAgrupaciones.TryGetValue(idPartidoPresidenteSenadores ?? "", out var presidenteSenadores); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         string? idPartidoPresidenteDiputados = bancasPorAgrupacion | 
					
						
							|  |  |  |             .Where(b => b.CategoriaId == 6) | 
					
						
							|  |  |  |             .OrderByDescending(b => b.BancasTotales) | 
					
						
							|  |  |  |             .FirstOrDefault()?.AgrupacionId; | 
					
						
							|  |  |  |         todasAgrupaciones.TryGetValue(idPartidoPresidenteDiputados ?? "", out var presidenteDiputados); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         object MapearPartidos(int categoriaId) | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |             var partidosDeCamara = bancasPorAgrupacion | 
					
						
							|  |  |  |                 .Where(b => b.CategoriaId == categoriaId && b.BancasTotales > 0) | 
					
						
							|  |  |  |                 .Select(b => new { Bancas = b, Agrupacion = todasAgrupaciones[b.AgrupacionId] }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (categoriaId == 6) // Diputados | 
					
						
							|  |  |  |                 partidosDeCamara = partidosDeCamara.OrderBy(b => b.Agrupacion.OrdenDiputados ?? 999) | 
					
						
							|  |  |  |                                                    .ThenByDescending(b => b.Bancas.BancasTotales); | 
					
						
							|  |  |  |             else // Senadores | 
					
						
							|  |  |  |                 partidosDeCamara = partidosDeCamara.OrderBy(b => b.Agrupacion.OrdenSenadores ?? 999) | 
					
						
							|  |  |  |                                                    .ThenByDescending(b => b.Bancas.BancasTotales); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return partidosDeCamara | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  |                     .Select(b => new | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         b.Agrupacion.Id, | 
					
						
							|  |  |  |                         b.Agrupacion.Nombre, | 
					
						
							|  |  |  |                         b.Agrupacion.NombreCorto, | 
					
						
							|  |  |  |                         b.Agrupacion.Color, | 
					
						
							|  |  |  |                         b.Bancas.BancasTotales, | 
					
						
							|  |  |  |                         Ocupantes = new List<object>() // <-- Siempre vacío en modo proyección | 
					
						
							|  |  |  |                     }) | 
					
						
							|  |  |  |         .ToList(); | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var diputados = new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             CamaraNombre = "Cámara de Diputados", | 
					
						
							|  |  |  |             TotalBancas = 92, | 
					
						
							|  |  |  |             BancasEnJuego = 46, | 
					
						
							|  |  |  |             Partidos = MapearPartidos(6), | 
					
						
							|  |  |  |             PresidenteBancada = presidenteDiputados != null ? new { presidenteDiputados.Color } : null | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var senadores = new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             CamaraNombre = "Cámara de Senadores", | 
					
						
							|  |  |  |             TotalBancas = 46, | 
					
						
							|  |  |  |             BancasEnJuego = 23, | 
					
						
							|  |  |  |             Partidos = MapearPartidos(5), | 
					
						
							|  |  |  |             PresidenteBancada = presidenteSenadores != null ? new { presidenteSenadores.Color } : null | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(new { Diputados = diputados, Senadores = senadores }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     [HttpGet("bancadas-detalle")] | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |     public async Task<IActionResult> GetBancadasConOcupantes([FromRoute] int eleccionId) | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |     { | 
					
						
							|  |  |  |         var config = await _dbContext.Configuraciones.AsNoTracking().ToDictionaryAsync(c => c.Clave, c => c.Valor); | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         config.TryGetValue("UsarDatosDeBancadasOficiales", out var usarDatosOficialesValue); | 
					
						
							|  |  |  |         if (usarDatosOficialesValue != "true") | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  |             // Si el modo oficial no está activo, SIEMPRE devolvemos un array vacío. | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |             return Ok(new List<object>()); | 
					
						
							| 
									
										
										
										
											2025-08-25 15:04:09 -03:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  |         // Si el modo oficial SÍ está activo, devolvemos los detalles. | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         var bancadasConOcupantes = await _dbContext.Bancadas | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .Where(b => b.EleccionId == eleccionId) | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |             .Include(b => b.Ocupante) | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |             .Select(b => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 b.Id, | 
					
						
							|  |  |  |                 b.Camara, | 
					
						
							|  |  |  |                 b.NumeroBanca, | 
					
						
							|  |  |  |                 b.AgrupacionPoliticaId, | 
					
						
							|  |  |  |                 Ocupante = b.Ocupante | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  |             .OrderBy(b => b.Id) | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(bancadasConOcupantes); | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |     [HttpGet("configuracion-publica")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetConfiguracionPublica() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Definimos una lista de las claves de configuración que son seguras para el público. | 
					
						
							|  |  |  |         // De esta manera, si en el futuro añadimos claves sensibles (como contraseñas de API, etc.), | 
					
						
							|  |  |  |         // nunca se expondrán accidentalmente. | 
					
						
							|  |  |  |         var clavesPublicas = new List<string> | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         "TickerResultadosCantidad", | 
					
						
							|  |  |  |         "ConcejalesResultadosCantidad" | 
					
						
							|  |  |  |         // "OtraClavePublica"  | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var configuracionPublica = await _dbContext.Configuraciones | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .Where(c => clavesPublicas.Contains(c.Clave)) | 
					
						
							|  |  |  |             .ToDictionaryAsync(c => c.Clave, c => c.Valor); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(configuracionPublica); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-03 13:49:35 -03:00
										 |  |  |     [HttpGet("seccion-resultados/{seccionId}")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetResultadosAgregadosPorSeccion(string seccionId, [FromQuery] int categoriaId) | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         var municipiosDeLaSeccion = await _dbContext.AmbitosGeograficos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId) | 
					
						
							|  |  |  |             .Select(a => a.Id) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         if (!municipiosDeLaSeccion.Any()) | 
					
						
							|  |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-09-05 11:38:25 -03:00
										 |  |  |             return Ok(new { UltimaActualizacion = DateTime.UtcNow, Resultados = new List<object>() }); | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-05 11:38:25 -03:00
										 |  |  |         // --- INICIO DE LA CORRECCIÓN DE LOGOS --- | 
					
						
							| 
									
										
										
										
											2025-09-04 17:19:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-05 11:38:25 -03:00
										 |  |  |         // 1. Buscamos logos que sean para esta categoría Y que sean generales (ámbito null). | 
					
						
							|  |  |  |         var logosGenerales = await _dbContext.LogosAgrupacionesCategorias | 
					
						
							| 
									
										
										
										
											2025-09-04 17:19:54 -03:00
										 |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-05 11:38:25 -03:00
										 |  |  |             .Where(l => l.CategoriaId == categoriaId && l.AmbitoGeograficoId == null) | 
					
						
							|  |  |  |             .ToDictionaryAsync(l => l.AgrupacionPoliticaId); | 
					
						
							| 
									
										
										
										
											2025-09-04 17:19:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-05 11:38:25 -03:00
										 |  |  |         // --- FIN DE LA CORRECCIÓN DE LOGOS --- | 
					
						
							| 
									
										
										
										
											2025-09-04 17:19:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         var resultadosMunicipales = await _dbContext.ResultadosVotos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .Include(r => r.AgrupacionPolitica) | 
					
						
							| 
									
										
										
										
											2025-09-03 13:49:35 -03:00
										 |  |  |             .Where(r => r.CategoriaId == categoriaId && municipiosDeLaSeccion.Contains(r.AmbitoGeograficoId)) | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |             .ToListAsync(); | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         var totalVotosSeccion = (decimal)resultadosMunicipales.Sum(r => r.CantidadVotos); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var resultadosFinales = resultadosMunicipales | 
					
						
							|  |  |  |             .GroupBy(r => r.AgrupacionPoliticaId) | 
					
						
							|  |  |  |             .Select(g => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 Agrupacion = g.First().AgrupacionPolitica, | 
					
						
							|  |  |  |                 Votos = g.Sum(r => r.CantidadVotos) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .OrderByDescending(r => r.Votos) | 
					
						
							|  |  |  |             .Select(r => new | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-09-03 13:49:35 -03:00
										 |  |  |                 Id = r.Agrupacion.Id, | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                 r.Agrupacion.Nombre, | 
					
						
							|  |  |  |                 r.Agrupacion.NombreCorto, | 
					
						
							|  |  |  |                 r.Agrupacion.Color, | 
					
						
							| 
									
										
										
										
											2025-09-05 11:38:25 -03:00
										 |  |  |                 // 2. Usamos el diccionario de logos generales para buscar la URL. | 
					
						
							|  |  |  |                 LogoUrl = logosGenerales.GetValueOrDefault(r.Agrupacion.Id)?.LogoUrl, | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                 Votos = r.Votos, | 
					
						
							| 
									
										
										
										
											2025-09-03 13:49:35 -03:00
										 |  |  |                 Porcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |             }) | 
					
						
							|  |  |  |             .ToList(); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-03 13:49:35 -03:00
										 |  |  |         var seccionAmbito = await _dbContext.AmbitosGeograficos.AsNoTracking() | 
					
						
							|  |  |  |             .FirstOrDefaultAsync(a => a.SeccionProvincialId == seccionId && a.NivelId == 20); | 
					
						
							|  |  |  |         var estadoRecuento = seccionAmbito != null | 
					
						
							|  |  |  |             ? await _dbContext.EstadosRecuentos.AsNoTracking() | 
					
						
							|  |  |  |                 .FirstOrDefaultAsync(e => e.AmbitoGeograficoId == seccionAmbito.Id && e.CategoriaId == categoriaId) | 
					
						
							|  |  |  |             : null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             UltimaActualizacion = estadoRecuento?.FechaTotalizacion ?? DateTime.UtcNow, | 
					
						
							|  |  |  |             Resultados = resultadosFinales | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |     [HttpGet("mapa-por-seccion")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetResultadosMapaPorSeccion([FromQuery] int categoriaId) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // 1. Obtenemos todos los resultados a nivel de MUNICIPIO para la categoría dada. | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |         var resultadosMunicipales = await _dbContext.ResultadosVotos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |             .Include(r => r.AmbitoGeografico) | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |             .Include(r => r.AgrupacionPolitica) | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |             .Where(r => r.CategoriaId == categoriaId && r.AmbitoGeografico.NivelId == 30) | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |         // 2. Agrupamos en memoria por Sección Electoral y sumamos los votos. | 
					
						
							|  |  |  |         var ganadoresPorSeccion = resultadosMunicipales | 
					
						
							|  |  |  |             .GroupBy(r => r.AmbitoGeografico.SeccionProvincialId) | 
					
						
							|  |  |  |             .Select(g => | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |                 // --- INICIO DE LA CORRECCIÓN --- | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |                 // Para cada sección, encontramos al partido con más votos. | 
					
						
							|  |  |  |                 var ganador = g | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |                     // CAMBIO CLAVE: Agrupamos por el ID de la agrupación, no por el objeto. | 
					
						
							|  |  |  |                     .GroupBy(r => r.AgrupacionPolitica.Id) | 
					
						
							|  |  |  |                     .Select(pg => new | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         // Obtenemos la entidad completa del primer elemento del grupo | 
					
						
							|  |  |  |                         Agrupacion = pg.First().AgrupacionPolitica, | 
					
						
							|  |  |  |                         TotalVotos = pg.Sum(r => r.CantidadVotos) | 
					
						
							|  |  |  |                     }) | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |                     .OrderByDescending(x => x.TotalVotos) | 
					
						
							|  |  |  |                     .FirstOrDefault(); | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |                 // --- FIN DE LA CORRECCIÓN --- | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 // Buscamos el nombre de la sección | 
					
						
							|  |  |  |                 var seccionInfo = _dbContext.AmbitosGeograficos | 
					
						
							|  |  |  |                     .FirstOrDefault(a => a.SeccionProvincialId == g.Key && a.NivelId == 20); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return new | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     SeccionId = g.Key, | 
					
						
							|  |  |  |                     SeccionNombre = seccionInfo?.Nombre, | 
					
						
							|  |  |  |                     AgrupacionGanadoraId = ganador?.Agrupacion.Id, | 
					
						
							|  |  |  |                     ColorGanador = ganador?.Agrupacion.Color | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2025-09-07 21:44:22 -03:00
										 |  |  |             .Where(r => r.SeccionId != null) | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |             .ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(ganadoresPorSeccion); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     [HttpGet("seccion/{seccionId}")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetResultadosDetallePorSeccion(string seccionId, [FromQuery] int categoriaId) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         var municipiosDeLaSeccion = await _dbContext.AmbitosGeograficos | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |             .Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId) | 
					
						
							|  |  |  |             .Select(a => a.Id) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!municipiosDeLaSeccion.Any()) return Ok(new List<object>()); | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |         var resultadosMunicipales = await _dbContext.ResultadosVotos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .Include(r => r.AgrupacionPolitica) | 
					
						
							|  |  |  |             .Where(r => r.CategoriaId == categoriaId && municipiosDeLaSeccion.Contains(r.AmbitoGeograficoId)) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |         var totalVotosSeccion = (decimal)resultadosMunicipales.Sum(r => r.CantidadVotos); | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |         var resultadosFinales = resultadosMunicipales | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |             // 1. Agrupamos por el ID del partido para evitar duplicados. | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |             .GroupBy(r => r.AgrupacionPoliticaId) | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |             .Select(g => new | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                 // 2. Tomamos la entidad completa del primer elemento del grupo. | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |                 Agrupacion = g.First().AgrupacionPolitica, | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |                 Votos = g.Sum(r => r.CantidadVotos) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .OrderByDescending(r => r.Votos) | 
					
						
							|  |  |  |             .Select(r => new | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |                 id = r.Agrupacion.Id, | 
					
						
							|  |  |  |                 nombre = r.Agrupacion.NombreCorto ?? r.Agrupacion.Nombre, | 
					
						
							|  |  |  |                 votos = r.Votos, | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                 porcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0, | 
					
						
							|  |  |  |                 // 3. Añadimos el color a la respuesta. | 
					
						
							|  |  |  |                 color = r.Agrupacion.Color | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |             }) | 
					
						
							|  |  |  |             .ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(resultadosFinales); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     [HttpGet("mapa-por-municipio")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetResultadosMapaPorMunicipio([FromQuery] int categoriaId) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Obtenemos los votos primero | 
					
						
							|  |  |  |         var votosPorMunicipio = await _dbContext.ResultadosVotos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .Where(r => r.CategoriaId == categoriaId && r.AmbitoGeografico.NivelId == 30) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Luego, los agrupamos en memoria | 
					
						
							|  |  |  |         var ganadores = votosPorMunicipio | 
					
						
							|  |  |  |             .GroupBy(r => r.AmbitoGeograficoId) | 
					
						
							|  |  |  |             .Select(g => g.OrderByDescending(r => r.CantidadVotos).First()) | 
					
						
							|  |  |  |             .ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Ahora, obtenemos los detalles necesarios en una sola consulta adicional | 
					
						
							|  |  |  |         var idsAgrupacionesGanadoras = ganadores.Select(g => g.AgrupacionPoliticaId).ToList(); | 
					
						
							|  |  |  |         var idsAmbitosGanadores = ganadores.Select(g => g.AmbitoGeograficoId).ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var agrupacionesInfo = await _dbContext.AgrupacionesPoliticas | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .Where(a => idsAgrupacionesGanadoras.Contains(a.Id)) | 
					
						
							|  |  |  |             .ToDictionaryAsync(a => a.Id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var ambitosInfo = await _dbContext.AmbitosGeograficos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .Where(a => idsAmbitosGanadores.Contains(a.Id)) | 
					
						
							|  |  |  |             .ToDictionaryAsync(a => a.Id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Finalmente, unimos todo en memoria | 
					
						
							|  |  |  |         var resultadoFinal = ganadores.Select(g => new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             AmbitoId = g.AmbitoGeograficoId, | 
					
						
							|  |  |  |             DepartamentoNombre = ambitosInfo.GetValueOrDefault(g.AmbitoGeograficoId)?.Nombre, | 
					
						
							|  |  |  |             AgrupacionGanadoraId = g.AgrupacionPoliticaId, | 
					
						
							|  |  |  |             ColorGanador = agrupacionesInfo.GetValueOrDefault(g.AgrupacionPoliticaId)?.Color | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         .Where(r => r.DepartamentoNombre != null) // Filtramos por si acaso | 
					
						
							|  |  |  |         .ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(resultadoFinal); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     [HttpGet("secciones-electorales-con-cargos")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetSeccionesElectoralesConCargos() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         var secciones = await _dbContext.AmbitosGeograficos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .Where(a => a.NivelId == 20) // Nivel 20 = Sección Electoral Provincial | 
					
						
							|  |  |  |             .Select(a => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 Id = a.SeccionProvincialId, // Usamos el ID que el frontend espera | 
					
						
							|  |  |  |                 Nombre = a.Nombre, | 
					
						
							|  |  |  |                 // Obtenemos los CategoriaId de las relaciones que tiene esta sección. | 
					
						
							|  |  |  |                 // Esto asume que tienes una tabla que relaciona Ámbitos con Cargos. | 
					
						
							|  |  |  |                 // Por ejemplo, a través de la tabla de Proyecciones o Resultados. | 
					
						
							|  |  |  |                 Cargos = _dbContext.ProyeccionesBancas | 
					
						
							|  |  |  |                             .Where(p => p.AmbitoGeograficoId == a.Id) | 
					
						
							|  |  |  |                             .Select(p => p.CategoriaId) | 
					
						
							|  |  |  |                             .Distinct() | 
					
						
							|  |  |  |                             .ToList() | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Mapeamos los CategoriaId a los nombres que usa el frontend | 
					
						
							|  |  |  |         var resultado = secciones.Select(s => new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             s.Id, | 
					
						
							|  |  |  |             s.Nombre, | 
					
						
							|  |  |  |             // Convertimos la lista de IDs de cargo a una lista de strings ("diputados", "senadores") | 
					
						
							|  |  |  |             CamarasDisponibles = s.Cargos.Select(CategoriaId => | 
					
						
							|  |  |  |                 CategoriaId == 6 ? "diputados" : // Asume 5 = Diputados | 
					
						
							|  |  |  |                 CategoriaId == 5 ? "senadores" : // Asume 6 = Senadores | 
					
						
							|  |  |  |                 null | 
					
						
							|  |  |  |             ).Where(c => c != null).ToList() | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(resultado); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-09-04 14:35:12 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // En src/Elecciones.Api/Controllers/ResultadosController.cs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     [HttpGet("tabla-ranking-seccion/{seccionId}")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetTablaRankingPorSeccion(string seccionId) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // 1. Obtener los ámbitos de los municipios de la sección | 
					
						
							|  |  |  |         var municipios = await _dbContext.AmbitosGeograficos.AsNoTracking() | 
					
						
							|  |  |  |             .Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId) | 
					
						
							|  |  |  |             .OrderBy(a => a.Nombre).Select(a => new { a.Id, a.Nombre }).ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!municipios.Any()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             return Ok(new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 Categorias = new List<object>(), | 
					
						
							|  |  |  |                 PartidosPrincipales = new Dictionary<int, List<object>>(), | 
					
						
							|  |  |  |                 ResultadosPorMunicipio = new List<object>() | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var municipiosIds = municipios.Select(m => m.Id).ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // 2. Obtener todos los resultados de votos para esos municipios en una sola consulta | 
					
						
							|  |  |  |         var resultadosCrudos = await _dbContext.ResultadosVotos.AsNoTracking() | 
					
						
							|  |  |  |             .Include(r => r.AgrupacionPolitica) | 
					
						
							|  |  |  |             .Where(r => municipiosIds.Contains(r.AmbitoGeograficoId)) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var categoriasMap = await _dbContext.CategoriasElectorales.AsNoTracking().ToDictionaryAsync(c => c.Id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // 3. Determinar las categorías activas en la sección | 
					
						
							|  |  |  |         var categoriasActivas = resultadosCrudos.Select(r => r.CategoriaId).Distinct() | 
					
						
							|  |  |  |             .Select(id => categoriasMap.GetValueOrDefault(id)).Where(c => c != null) | 
					
						
							|  |  |  |             .OrderBy(c => c!.Orden).Select(c => new { c!.Id, c.Nombre }).ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // 4. Determinar los 2 partidos principales POR CATEGORÍA a nivel SECCIÓN | 
					
						
							|  |  |  |         var partidosPorCategoria = categoriasActivas.ToDictionary( | 
					
						
							|  |  |  |         c => c.Id, | 
					
						
							|  |  |  |         c => | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             var resultadosCategoriaSeccion = resultadosCrudos.Where(r => r.CategoriaId == c.Id); | 
					
						
							|  |  |  |             var totalVotosSeccionCategoria = (decimal)resultadosCategoriaSeccion.Sum(r => r.CantidadVotos); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return resultadosCategoriaSeccion | 
					
						
							|  |  |  |                 // --- CAMBIO CLAVE: Agrupamos por el ID (string), no por el objeto --- | 
					
						
							|  |  |  |                 .GroupBy(r => r.AgrupacionPolitica.Id) | 
					
						
							|  |  |  |                 .Select(g => new | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // g.Key ahora es el AgrupacionPoliticaId | 
					
						
							|  |  |  |                     // Tomamos la entidad completa del primer elemento del grupo | 
					
						
							|  |  |  |                     Agrupacion = g.First().AgrupacionPolitica, | 
					
						
							|  |  |  |                     TotalVotos = g.Sum(r => r.CantidadVotos) | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 .OrderByDescending(x => x.TotalVotos) | 
					
						
							|  |  |  |                 .Take(2) | 
					
						
							|  |  |  |                 .Select((x, index) => new | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     Puesto = index + 1, | 
					
						
							|  |  |  |                     x.Agrupacion.Id, | 
					
						
							|  |  |  |                     Nombre = x.Agrupacion.NombreCorto ?? x.Agrupacion.Nombre, | 
					
						
							|  |  |  |                     PorcentajeTotalSeccion = totalVotosSeccionCategoria > 0 ? (x.TotalVotos / totalVotosSeccionCategoria) * 100 : 0 | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 .ToList(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // 5. Construir los datos para las filas de la tabla (resultados por municipio) | 
					
						
							|  |  |  |         var resultadosPorMunicipio = municipios.Select(municipio => | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             var resultadosDelMunicipio = resultadosCrudos.Where(r => r.AmbitoGeograficoId == municipio.Id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             var celdas = resultadosDelMunicipio | 
					
						
							|  |  |  |                 .GroupBy(r => r.CategoriaId) | 
					
						
							|  |  |  |                 .ToDictionary( | 
					
						
							|  |  |  |                     g => g.Key, // CategoriaId | 
					
						
							|  |  |  |                     g => | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         var totalVotosMunicipioCategoria = (decimal)g.Sum(r => r.CantidadVotos); | 
					
						
							|  |  |  |                         return g.ToDictionary( | 
					
						
							|  |  |  |                             r => r.AgrupacionPoliticaId, // PartidoId | 
					
						
							|  |  |  |                             r => totalVotosMunicipioCategoria > 0 ? (r.CantidadVotos / totalVotosMunicipioCategoria) * 100 : 0 | 
					
						
							|  |  |  |                         ); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 MunicipioId = municipio.Id, | 
					
						
							|  |  |  |                 MunicipioNombre = municipio.Nombre, | 
					
						
							|  |  |  |                 Celdas = celdas | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |         }).ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             Categorias = categoriasActivas, | 
					
						
							|  |  |  |             PartidosPorCategoria = partidosPorCategoria, | 
					
						
							|  |  |  |             ResultadosPorMunicipio = resultadosPorMunicipio | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-09-04 15:54:00 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     [HttpGet("ranking-municipios-por-seccion/{seccionId}")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetRankingMunicipiosPorSeccion(string seccionId) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // 1. Obtener los municipios de la sección | 
					
						
							|  |  |  |         var municipios = await _dbContext.AmbitosGeograficos.AsNoTracking() | 
					
						
							|  |  |  |             .Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId) | 
					
						
							|  |  |  |             .OrderBy(a => a.Nombre) | 
					
						
							|  |  |  |             .Select(a => new { a.Id, a.Nombre }) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!municipios.Any()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             return Ok(new List<object>()); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var municipiosIds = municipios.Select(m => m.Id).ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // 2. Obtener todos los resultados de esos municipios en una sola consulta | 
					
						
							|  |  |  |         var resultadosCrudos = await _dbContext.ResultadosVotos.AsNoTracking() | 
					
						
							|  |  |  |             .Include(r => r.AgrupacionPolitica) | 
					
						
							|  |  |  |             .Where(r => municipiosIds.Contains(r.AmbitoGeograficoId)) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // 3. Procesar los datos por cada municipio | 
					
						
							|  |  |  |         var resultadosPorMunicipio = municipios.Select(municipio => | 
					
						
							| 
									
										
										
										
											2025-09-08 14:11:05 -03:00
										 |  |  |     { | 
					
						
							|  |  |  |         var resultadosDelMunicipio = resultadosCrudos.Where(r => r.AmbitoGeograficoId == municipio.Id); | 
					
						
							| 
									
										
										
										
											2025-09-04 15:54:00 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-08 14:11:05 -03:00
										 |  |  |         var resultadosPorCategoria = resultadosDelMunicipio | 
					
						
							|  |  |  |             .GroupBy(r => r.CategoriaId) | 
					
						
							|  |  |  |             .Select(g => | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 var totalVotosCategoria = (decimal)g.Sum(r => r.CantidadVotos); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 var ranking = g | 
					
						
							|  |  |  |                     .OrderByDescending(r => r.CantidadVotos) | 
					
						
							|  |  |  |                     .Take(2) | 
					
						
							|  |  |  |                     .Select(r => new | 
					
						
							| 
									
										
										
										
											2025-09-04 15:54:00 -03:00
										 |  |  |                     { | 
					
						
							| 
									
										
										
										
											2025-09-08 14:11:05 -03:00
										 |  |  |                         NombreCorto = r.AgrupacionPolitica.NombreCorto ?? r.AgrupacionPolitica.Nombre, | 
					
						
							|  |  |  |                         Porcentaje = totalVotosCategoria > 0 ? (r.CantidadVotos / totalVotosCategoria) * 100 : 0, | 
					
						
							|  |  |  |                         Votos = r.CantidadVotos // <-- AÑADIR ESTE CAMPO | 
					
						
							|  |  |  |                     }) | 
					
						
							|  |  |  |                     .ToList(); | 
					
						
							| 
									
										
										
										
											2025-09-04 15:54:00 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-08 14:11:05 -03:00
										 |  |  |                 return new { CategoriaId = g.Key, Ranking = ranking }; | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .ToDictionary(r => r.CategoriaId); // Lo convertimos a diccionario para fácil acceso | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             MunicipioId = municipio.Id, | 
					
						
							|  |  |  |             MunicipioNombre = municipio.Nombre, | 
					
						
							|  |  |  |             ResultadosPorCategoria = resultadosPorCategoria | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     }).ToList(); | 
					
						
							| 
									
										
										
										
											2025-09-04 15:54:00 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Devolvemos las categorías que tuvieron resultados en esta sección para construir la cabecera | 
					
						
							|  |  |  |         var categoriasMap = await _dbContext.CategoriasElectorales.AsNoTracking().ToDictionaryAsync(c => c.Id); | 
					
						
							|  |  |  |         var categoriasActivas = resultadosCrudos | 
					
						
							|  |  |  |             .Select(r => r.CategoriaId).Distinct() | 
					
						
							|  |  |  |             .Select(id => categoriasMap.GetValueOrDefault(id)).Where(c => c != null) | 
					
						
							|  |  |  |             .OrderBy(c => c!.Orden) | 
					
						
							|  |  |  |             .Select(c => new { Id = c!.Id, Nombre = c.Nombre }) | 
					
						
							|  |  |  |             .ToList(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return Ok(new | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             Categorias = categoriasActivas, | 
					
						
							|  |  |  |             Resultados = resultadosPorMunicipio | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     [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) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |             .FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10); | 
					
						
							|  |  |  |         if (provincia == null) return NotFound($"No se encontró la provincia con DistritoId {distritoId}."); | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         // --- INICIO DE LA OPTIMIZACIÓN --- | 
					
						
							|  |  |  |         // 1. Agrupar y sumar directamente en la base de datos. EF lo traducirá a un SQL eficiente. | 
					
						
							|  |  |  |         var resultadosAgregados = await _dbContext.ResultadosVotos.AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .Where(r => r.EleccionId == eleccionId && | 
					
						
							|  |  |  |                         r.CategoriaId == categoriaId && | 
					
						
							|  |  |  |                         r.AmbitoGeografico.DistritoId == distritoId && | 
					
						
							|  |  |  |                         r.AmbitoGeografico.NivelId == 30) | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |             .GroupBy(r => r.AgrupacionPolitica) // Agrupar por la entidad | 
					
						
							|  |  |  |             .Select(g => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 Agrupacion = g.Key, | 
					
						
							|  |  |  |                 TotalVotos = g.Sum(r => r.CantidadVotos) | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         // 2. Calcular el total de votos en memoria (sobre una lista ya pequeña) | 
					
						
							|  |  |  |         var totalVotosProvincia = (decimal)resultadosAgregados.Sum(r => r.TotalVotos); | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         // 3. Mapear a DTO (muy rápido) | 
					
						
							|  |  |  |         var resultadosPanel = resultadosAgregados | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .Select(g => new AgrupacionResultadoDto | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |                 Id = g.Agrupacion.Id, | 
					
						
							|  |  |  |                 Nombre = g.Agrupacion.Nombre, | 
					
						
							|  |  |  |                 NombreCorto = g.Agrupacion.NombreCorto, | 
					
						
							|  |  |  |                 Color = g.Agrupacion.Color, | 
					
						
							|  |  |  |                 Votos = g.TotalVotos, | 
					
						
							|  |  |  |                 Porcentaje = totalVotosProvincia > 0 ? (g.TotalVotos / totalVotosProvincia) * 100 : 0 | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             }) | 
					
						
							|  |  |  |             .OrderByDescending(r => r.Votos) | 
					
						
							|  |  |  |             .ToList(); | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         // --- FIN DE LA OPTIMIZACIÓN --- | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         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, | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |             MapaData = new List<ResultadoMapaDto>(), // Se carga por separado | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             ResultadosPanel = resultadosPanel, | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |             EstadoRecuento = new EstadoRecuentoDto | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 ParticipacionPorcentaje = estadoRecuento?.ParticipacionPorcentaje ?? 0, | 
					
						
							|  |  |  |                 MesasTotalizadasPorcentaje = estadoRecuento?.MesasTotalizadasPorcentaje ?? 0 | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |         }; | 
					
						
							|  |  |  |         return Ok(respuesta); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private async Task<IActionResult> GetPanelNacional(int eleccionId, int categoriaId) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         // --- INICIO DE LA OPTIMIZACIÓN --- | 
					
						
							|  |  |  |         // 1. Agrupar y sumar directamente en la base de datos a nivel nacional. | 
					
						
							|  |  |  |         var resultadosAgregados = await _dbContext.ResultadosVotos.AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .Where(r => r.EleccionId == eleccionId && r.CategoriaId == categoriaId) | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |             .GroupBy(r => r.AgrupacionPolitica) | 
					
						
							|  |  |  |             .Select(g => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 Agrupacion = g.Key, | 
					
						
							|  |  |  |                 TotalVotos = g.Sum(r => r.CantidadVotos) | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         // 2. Calcular el total de votos en memoria | 
					
						
							|  |  |  |         var totalVotosNacional = (decimal)resultadosAgregados.Sum(r => r.TotalVotos); | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         // 3. Mapear a DTO | 
					
						
							|  |  |  |         var resultadosPanel = resultadosAgregados | 
					
						
							|  |  |  |             .Select(g => new AgrupacionResultadoDto | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 Id = g.Agrupacion.Id, | 
					
						
							|  |  |  |                 Nombre = g.Agrupacion.Nombre, | 
					
						
							|  |  |  |                 NombreCorto = g.Agrupacion.NombreCorto, | 
					
						
							|  |  |  |                 Color = g.Agrupacion.Color, | 
					
						
							|  |  |  |                 Votos = g.TotalVotos, | 
					
						
							|  |  |  |                 Porcentaje = totalVotosNacional > 0 ? (g.TotalVotos / totalVotosNacional) * 100 : 0 | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             .OrderByDescending(r => r.Votos) | 
					
						
							|  |  |  |             .ToList(); | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         // --- FIN DE LA OPTIMIZACIÓN --- | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         var estadoRecuento = await _dbContext.EstadosRecuentosGenerales.AsNoTracking() | 
					
						
							|  |  |  |             .FirstOrDefaultAsync(e => e.EleccionId == eleccionId && e.CategoriaId == categoriaId); | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         var respuesta = new PanelElectoralDto | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             AmbitoNombre = "Argentina", | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |             MapaData = new List<ResultadoMapaDto>(), // Se carga por separado | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             ResultadosPanel = resultadosPanel, | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |             EstadoRecuento = new EstadoRecuentoDto | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 ParticipacionPorcentaje = estadoRecuento?.ParticipacionPorcentaje ?? 0, | 
					
						
							|  |  |  |                 MesasTotalizadasPorcentaje = estadoRecuento?.MesasTotalizadasPorcentaje ?? 0 | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |         }; | 
					
						
							|  |  |  |         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); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  | } |