| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  | // src/Elecciones.Api/Controllers/ResultadosController.cs | 
					
						
							|  |  |  | 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] | 
					
						
							|  |  |  | [Route("api/[controller]")]
 | 
					
						
							|  |  |  | 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-02 17:08:56 -03:00
										 |  |  |     [HttpGet("partido/{municipioId}")] // Renombramos el parámetro para mayor claridad | 
					
						
							|  |  |  |     public async Task<IActionResult> GetResultadosPorPartido(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() | 
					
						
							|  |  |  |         .FirstOrDefaultAsync(e => 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-02 17:08:56 -03:00
										 |  |  |             .Where(rv => rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId) | 
					
						
							| 
									
										
										
										
											2025-08-14 15:27:45 -03:00
										 |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         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
										 |  |  | 
 | 
					
						
							|  |  |  |                 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, | 
					
						
							|  |  |  |                     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}")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetResultadosProvinciales(string distritoId) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							|  |  |  |             .Where(e => e.AmbitoGeograficoId == provincia.Id) | 
					
						
							|  |  |  |             .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-02 15:39:01 -03:00
										 |  |  |             .Where(r => r.AmbitoGeografico.NivelId == 30) // Nivel 30 = Municipio | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |         var logos = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |             .GroupBy(r => r.CategoriaId) | 
					
						
							|  |  |  |             .Select(g => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 CategoriaId = g.Key, | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |                 CategoriaNombre = estadosPorCategoria.ContainsKey(g.Key) ? estadosPorCategoria[g.Key].CategoriaElectoral.Nombre : "Desconocido", | 
					
						
							|  |  |  |                 EstadoRecuento = estadosPorCategoria.GetValueOrDefault(g.Key), | 
					
						
							| 
									
										
										
										
											2025-09-01 14:04:40 -03:00
										 |  |  |                 TotalVotosCategoria = g.Sum(r => r.CantidadVotos), | 
					
						
							| 
									
										
										
										
											2025-09-02 15:43:27 -03:00
										 |  |  |                 // Agrupamos por el ID de la agrupación, no por el objeto, para evitar duplicados | 
					
						
							|  |  |  |                 ResultadosAgrupados = g.GroupBy(r => r.AgrupacionPoliticaId) | 
					
						
							|  |  |  |                                        .Select(partidoGroup => new | 
					
						
							|  |  |  |                                        { | 
					
						
							|  |  |  |                                            Agrupacion = partidoGroup.First().AgrupacionPolitica, | 
					
						
							|  |  |  |                                            Votos = partidoGroup.Sum(r => r.CantidadVotos) | 
					
						
							|  |  |  |                                        }) | 
					
						
							|  |  |  |                                        .ToList() | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |                               .Select(r => | 
					
						
							|  |  |  |                               { | 
					
						
							|  |  |  |                                   var logoUrl = logos.FirstOrDefault(l => l.AgrupacionPoliticaId == r.Agrupacion.Id && l.CategoriaId == g.CategoriaId && l.AmbitoGeograficoId != null)?.LogoUrl | 
					
						
							|  |  |  |                                              ?? logos.FirstOrDefault(l => l.AgrupacionPoliticaId == r.Agrupacion.Id && l.CategoriaId == g.CategoriaId && l.AmbitoGeograficoId == null)?.LogoUrl; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                                   return new | 
					
						
							|  |  |  |                                   { | 
					
						
							|  |  |  |                                       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
										 |  |  |             }) | 
					
						
							|  |  |  |             .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-08-15 17:31:51 -03:00
										 |  |  |     [HttpGet("bancas/{seccionId}")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetBancasPorSeccion(string seccionId) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |         // 1. Buscamos el ámbito usando 'SeccionProvincialId'. | 
					
						
							|  |  |  |         // La API oficial usa este campo para las secciones electorales. | 
					
						
							|  |  |  |         // Además, el worker guarda estas secciones con NivelId = 20, por lo que lo usamos aquí para consistencia. | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  |         var seccion = await _dbContext.AmbitosGeograficos | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |             .FirstOrDefaultAsync(a => a.SeccionProvincialId == seccionId && a.NivelId == 20); // Nivel 20 = Sección Electoral Provincial | 
					
						
							| 
									
										
										
										
											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-08-25 10:25:54 -03:00
										 |  |  |         // 2. Buscamos todas las proyecciones para ese ámbito (usando su clave primaria 'Id') | 
					
						
							| 
									
										
										
										
											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-08-15 17:31:51 -03:00
										 |  |  |             .Where(p => p.AmbitoGeograficoId == seccion.Id) | 
					
						
							|  |  |  |             .Select(p => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 AgrupacionNombre = p.AgrupacionPolitica.Nombre, | 
					
						
							|  |  |  |                 Bancas = p.NroBancas | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .OrderByDescending(p => p.Bancas) | 
					
						
							|  |  |  |             .ToListAsync(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!proyecciones.Any()) | 
					
						
							|  |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |             // Este caso es posible si aún no hay proyecciones calculadas para esta sección. | 
					
						
							|  |  |  |             _logger.LogWarning("No se encontraron proyecciones de bancas para la sección: {SeccionNombre}", seccion.Nombre); | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  |             return NotFound(new { message = $"No se han encontrado proyecciones de bancas para la sección {seccion.Nombre}" }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |         // 3. Devolvemos la respuesta | 
					
						
							| 
									
										
										
										
											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-08-29 09:54:22 -03:00
										 |  |  |     public async Task<IActionResult> GetComposicionCongreso() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         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-08-29 09:54:22 -03:00
										 |  |  |             return await GetComposicionDesdeBancadasOficiales(config); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |         { | 
					
						
							| 
									
										
										
										
											2025-08-29 15:49:13 -03:00
										 |  |  |             // Si está en 'false' o no existe, llama a este otro | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |             return await GetComposicionDesdeProyecciones(config); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |     // En ResultadosController.cs | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |     private async Task<IActionResult> GetComposicionDesdeBancadasOficiales(Dictionary<string, string> config) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-08-30 11:31:45 -03:00
										 |  |  |         config.TryGetValue("MostrarOcupantes", out var mostrarOcupantesValue); | 
					
						
							|  |  |  |         bool mostrarOcupantes = mostrarOcupantesValue == "true"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         IQueryable<Bancada> bancadasQuery = _dbContext.Bancadas.AsNoTracking() | 
					
						
							|  |  |  |                                              .Include(b => b.AgrupacionPolitica); | 
					
						
							|  |  |  |         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 }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private async Task<IActionResult> GetComposicionDesdeProyecciones(Dictionary<string, string> config) | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2025-08-29 09:54:22 -03:00
										 |  |  |         var bancasPorAgrupacion = await _dbContext.ProyeccionesBancas | 
					
						
							|  |  |  |             .AsNoTracking() | 
					
						
							|  |  |  |             .GroupBy(p => new { p.AgrupacionPoliticaId, p.CategoriaId }) | 
					
						
							|  |  |  |             .Select(g => new | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 AgrupacionId = g.Key.AgrupacionPoliticaId, | 
					
						
							|  |  |  |                 CategoriaId = g.Key.CategoriaId, | 
					
						
							|  |  |  |                 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")] | 
					
						
							|  |  |  |     public async Task<IActionResult> GetBancadasConOcupantes() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         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() | 
					
						
							|  |  |  |             .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()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             return Ok(new List<object>()); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -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
										 |  |  |             // Usamos la categoriaId del parámetro | 
					
						
							|  |  |  |             .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-03 13:49:35 -03:00
										 |  |  |         var logos = await _dbContext.LogosAgrupacionesCategorias | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |             .AsNoTracking() | 
					
						
							| 
									
										
										
										
											2025-09-03 13:49:35 -03:00
										 |  |  |             // Usamos la categoriaId del parámetro | 
					
						
							|  |  |  |             .Where(l => l.CategoriaId == categoriaId) | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:01 -03:00
										 |  |  |             .ToDictionaryAsync(l => l.AgrupacionPoliticaId); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -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-03 13:49:35 -03:00
										 |  |  |                 LogoUrl = logos.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
										 |  |  |         // Devolvemos un objeto para poder añadir la fecha de actualización | 
					
						
							|  |  |  |         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 => | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Para cada sección, encontramos al partido con más votos. | 
					
						
							|  |  |  |                 var ganador = g | 
					
						
							|  |  |  |                     .GroupBy(r => r.AgrupacionPolitica) | 
					
						
							|  |  |  |                     .Select(pg => new { Agrupacion = pg.Key, TotalVotos = pg.Sum(r => r.CantidadVotos) }) | 
					
						
							|  |  |  |                     .OrderByDescending(x => x.TotalVotos) | 
					
						
							|  |  |  |                     .FirstOrDefault(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // 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 | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .Where(r => r.SeccionId != null) // Filtramos cualquier posible nulo | 
					
						
							|  |  |  |             .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-08-14 15:27:45 -03:00
										 |  |  | } |