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-09-04 11:27:12 -03:00
|
|
|
[HttpGet("bancas-por-seccion/{seccionId}/{camara}")] // <-- CAMBIO 1: Modificar la ruta
|
|
|
|
|
public async Task<IActionResult> GetBancasPorSeccion(string seccionId, string camara) // <-- CAMBIO 2: Añadir el nuevo parámetro
|
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-04 11:27:12 -03:00
|
|
|
.Where(p => p.AmbitoGeograficoId == seccion.Id && p.CategoriaId == CategoriaId) // <-- AÑADIDO EL FILTRO
|
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-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-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-08-14 15:27:45 -03:00
|
|
|
}
|