// src/Elecciones.Api/Controllers/ResultadosController.cs using Elecciones.Core.DTOs.ApiResponses; using Elecciones.Database; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Linq; using System.Threading.Tasks; namespace Elecciones.Api.Controllers; [ApiController] [Route("api/[controller]")] public class ResultadosController : ControllerBase { private readonly EleccionesDbContext _dbContext; private readonly ILogger _logger; public ResultadosController(EleccionesDbContext dbContext, ILogger logger) { _dbContext = dbContext; _logger = logger; } [HttpGet("municipio/{municipioId}")] public async Task GetResultadosPorMunicipio(string municipioId) { // 1. Buscamos el ámbito geográfico correspondiente al municipio var ambito = await _dbContext.AmbitosGeograficos .AsNoTracking() .FirstOrDefaultAsync(a => a.MunicipioId == municipioId); if (ambito == null) { return NotFound(new { message = $"No se encontró el municipio con ID {municipioId}" }); } // 2. Buscamos el estado del recuento para ese ámbito var estadoRecuento = await _dbContext.EstadosRecuentos .AsNoTracking() .FirstOrDefaultAsync(e => e.AmbitoGeograficoId == ambito.Id); if (estadoRecuento == null) { return NotFound(new { message = $"No se han encontrado resultados para el municipio {ambito.Nombre}" }); } // 3. Buscamos todos los votos para ese ámbito, incluyendo el nombre de la agrupación var resultadosVotos = await _dbContext.ResultadosVotos .AsNoTracking() .Include(rv => rv.AgrupacionPolitica) // ¡Crucial para obtener el nombre del partido! .Where(rv => rv.AmbitoGeograficoId == ambito.Id) .ToListAsync(); // 4. Calculamos el total de votos positivos para el porcentaje long totalVotosPositivos = resultadosVotos.Sum(r => r.CantidadVotos); // 5. Mapeamos todo a nuestro DTO de respuesta var respuestaDto = new MunicipioResultadosDto { MunicipioNombre = ambito.Nombre, UltimaActualizacion = estadoRecuento.FechaTotalizacion, PorcentajeEscrutado = estadoRecuento.MesasTotalizadas * 100.0m / (estadoRecuento.MesasEsperadas > 0 ? estadoRecuento.MesasEsperadas : 1), PorcentajeParticipacion = estadoRecuento.ParticipacionPorcentaje, Resultados = resultadosVotos.Select(rv => new AgrupacionResultadoDto { Nombre = rv.AgrupacionPolitica.Nombre, Votos = rv.CantidadVotos, Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0 }).OrderByDescending(r => r.Votos).ToList(), VotosAdicionales = new VotosAdicionalesDto { EnBlanco = estadoRecuento.VotosEnBlanco, Nulos = estadoRecuento.VotosNulos, Recurridos = estadoRecuento.VotosRecurridos } }; // Devolvemos el resultado return Ok(respuestaDto); } [HttpGet("provincia/{distritoId}")] public async Task GetResultadosProvinciales(string distritoId) { // TODO: Esta lógica debe ser reemplazada para leer datos reales de la BD // cuando el worker comience a ingestar los totales a nivel provincial. // Por ahora, devolvemos datos simulados para permitir el desarrollo del frontend. var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking() .FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10); if (ambito == null) { return NotFound(new { message = "No se encontró la provincia" }); } // Simulación var random = new Random(); var respuestaSimulada = new ResumenProvincialDto { ProvinciaNombre = ambito.Nombre, UltimaActualizacion = DateTime.UtcNow, PorcentajeEscrutado = 78.45m, PorcentajeParticipacion = 65.12m, Resultados = [ new() { Nombre = "ALIANZA POR EL FUTURO", Votos = 2500000 + random.Next(1, 1000), Porcentaje = 45.12m }, new() { Nombre = "FRENTE DE AVANZADA", Votos = 2100000 + random.Next(1, 1000), Porcentaje = 38.78m }, new() { Nombre = "UNION POPULAR", Votos = 800000 + random.Next(1, 1000), Porcentaje = 14.10m }, ], VotosAdicionales = new VotosAdicionalesDto { EnBlanco = 150000, Nulos = 80000, Recurridos = 1200 } }; return Ok(await Task.FromResult(respuestaSimulada)); } [HttpGet("bancas/{seccionId}")] public async Task GetBancasPorSeccion(string seccionId) { // 1. Buscamos el ámbito de la sección electoral var seccion = await _dbContext.AmbitosGeograficos .AsNoTracking() .FirstOrDefaultAsync(a => a.SeccionId == seccionId && a.NivelId == 4); // Nivel 4 = Sección Electoral if (seccion == null) { return NotFound(new { message = $"No se encontró la sección electoral con ID {seccionId}" }); } // 2. Buscamos todas las proyecciones para ese ámbito, incluyendo el nombre de la agrupación var proyecciones = await _dbContext.ProyeccionesBancas .AsNoTracking() .Include(p => p.AgrupacionPolitica) // Incluimos el nombre del partido .Where(p => p.AmbitoGeograficoId == seccion.Id) .Select(p => new { // Creamos un objeto anónimo para la respuesta, más limpio que un DTO para este caso simple AgrupacionNombre = p.AgrupacionPolitica.Nombre, Bancas = p.NroBancas }) .OrderByDescending(p => p.Bancas) .ToListAsync(); if (!proyecciones.Any()) { return NotFound(new { message = $"No se han encontrado proyecciones de bancas para la sección {seccion.Nombre}" }); } // 3. Devolvemos un objeto que contiene el nombre de la sección y la lista de resultados return Ok(new { SeccionNombre = seccion.Nombre, Proyeccion = proyecciones }); } [HttpGet("mapa")] public async Task GetResultadosParaMapa() { // Esta consulta es mucho más eficiente y se traduce bien a SQL. // Paso 1: Para cada ámbito, encontrar la cantidad máxima de votos. var maxVotosPorAmbito = _dbContext.ResultadosVotos .GroupBy(rv => rv.AmbitoGeograficoId) .Select(g => new { AmbitoId = g.Key, MaxVotos = g.Max(v => v.CantidadVotos) }); // Paso 2: Unir los resultados originales con los máximos para encontrar el registro ganador. // Esto nos da, para cada ámbito, el registro completo del partido que tuvo más votos. var resultadosGanadores = await _dbContext.ResultadosVotos .Join( maxVotosPorAmbito, voto => new { AmbitoId = voto.AmbitoGeograficoId, Votos = voto.CantidadVotos }, max => new { AmbitoId = max.AmbitoId, Votos = max.MaxVotos }, (voto, max) => voto // Nos quedamos con el objeto 'ResultadoVoto' completo ) .Include(rv => rv.AmbitoGeografico) // Incluimos el ámbito para obtener el MunicipioId .Where(rv => rv.AmbitoGeografico.MunicipioId != null) .Select(rv => new { MunicipioId = rv.AmbitoGeografico.MunicipioId, AgrupacionGanadoraId = rv.AgrupacionPoliticaId }) .ToListAsync(); return Ok(resultadosGanadores); } }