Feat Widgets 0209

This commit is contained in:
2025-09-02 09:48:46 -03:00
parent 12860f2406
commit 271a86b632
15 changed files with 671 additions and 166 deletions

View File

@@ -235,67 +235,36 @@ public class ResultadosController : ControllerBase
return Ok(resultadosGanadores);
}
[HttpGet("municipio/{ambitoId}")] // Cambiamos el nombre del parámetro de ruta
public async Task<IActionResult> GetResultadosPorMunicipio(int ambitoId) // Cambiamos el tipo de string a int
[HttpGet("municipio/{ambitoId}")]
public async Task<IActionResult> GetResultadosPorMunicipio(int ambitoId, [FromQuery] int categoriaId)
{
_logger.LogInformation("Buscando resultados para AmbitoGeograficoId: {AmbitoId}", ambitoId);
// Validamos que el ámbito exista
var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking()
.FirstOrDefaultAsync(a => a.Id == ambitoId && a.NivelId == 30);
if (ambito == null) return NotFound($"No se encontró el municipio con ID {ambitoId}");
// PASO 1: Buscar el Ámbito Geográfico directamente por su CLAVE PRIMARIA (AmbitoGeograficoId).
var ambito = await _dbContext.AmbitosGeograficos
.AsNoTracking()
.FirstOrDefaultAsync(a => a.Id == ambitoId && a.NivelId == 30); // Usamos a.Id == ambitoId
if (ambito == null)
{
_logger.LogWarning("No se encontró el ámbito para el ID interno: {AmbitoId} o no es Nivel 30.", ambitoId);
return NotFound(new { message = $"No se encontró el municipio con ID interno {ambitoId}" });
}
_logger.LogInformation("Ámbito encontrado: Id={AmbitoId}, Nombre={AmbitoNombre}", ambito.Id, ambito.Nombre);
// PASO 2: Usar la CLAVE PRIMARIA (ambito.Id) para buscar el estado del recuento.
var estadoRecuento = await _dbContext.EstadosRecuentos
.AsNoTracking()
.FirstOrDefaultAsync(e => e.AmbitoGeograficoId == ambito.Id);
if (estadoRecuento == null)
{
_logger.LogWarning("No se encontró EstadoRecuento para AmbitoGeograficoId: {AmbitoId}", ambito.Id);
return NotFound(new { message = $"No se han encontrado resultados de recuento para el municipio {ambito.Nombre}" });
}
// PASO 3: Usar la CLAVE PRIMARIA (ambito.Id) para buscar los votos.
// Obtenemos los votos para ESE municipio y ESA categoría
var resultadosVotos = await _dbContext.ResultadosVotos
.AsNoTracking()
.Include(rv => rv.AgrupacionPolitica) // Incluimos el nombre del partido
.Where(rv => rv.AmbitoGeograficoId == ambito.Id)
.OrderByDescending(rv => rv.CantidadVotos)
.Include(rv => rv.AgrupacionPolitica)
.Where(rv => rv.AmbitoGeograficoId == ambitoId && rv.CategoriaId == categoriaId)
.ToListAsync();
// PASO 4: Calcular el total de votos positivos para el porcentaje.
long totalVotosPositivos = resultadosVotos.Sum(r => r.CantidadVotos);
// Calculamos el total de votos solo para esta selección
var totalVotosPositivos = (decimal)resultadosVotos.Sum(r => r.CantidadVotos);
// PASO 5: Mapear todo al DTO de respuesta que el frontend espera.
var respuestaDto = new MunicipioResultadosDto
{
MunicipioNombre = ambito.Nombre,
UltimaActualizacion = estadoRecuento.FechaTotalizacion,
PorcentajeEscrutado = estadoRecuento.MesasTotalizadasPorcentaje,
PorcentajeParticipacion = estadoRecuento.ParticipacionPorcentaje,
Resultados = resultadosVotos.Select(rv => new AgrupacionResultadoDto
// Mapeamos a la respuesta final que espera el frontend
var respuesta = resultadosVotos
.OrderByDescending(r => r.CantidadVotos)
.Select(rv => new
{
Nombre = rv.AgrupacionPolitica.Nombre,
Votos = rv.CantidadVotos,
Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0
}).ToList(),
VotosAdicionales = new VotosAdicionalesDto
{
EnBlanco = estadoRecuento.VotosEnBlanco,
Nulos = estadoRecuento.VotosNulos,
Recurridos = estadoRecuento.VotosRecurridos
}
};
id = rv.AgrupacionPolitica.Id,
nombre = rv.AgrupacionPolitica.NombreCorto ?? rv.AgrupacionPolitica.Nombre,
votos = rv.CantidadVotos,
porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos / totalVotosPositivos) * 100 : 0
}).ToList();
return Ok(respuestaDto);
return Ok(respuesta);
}
[HttpGet("composicion-congreso")]
@@ -535,55 +504,180 @@ public class ResultadosController : ControllerBase
}
[HttpGet("concejales/{seccionId}")]
public async Task<IActionResult> GetResultadosConcejalesPorSeccion(string seccionId)
public async Task<IActionResult> GetResultadosConcejalesPorSeccion(string seccionId)
{
var municipiosDeLaSeccion = await _dbContext.AmbitosGeograficos
.AsNoTracking()
.Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId)
.Select(a => a.Id)
.ToListAsync();
if (!municipiosDeLaSeccion.Any())
{
return Ok(new List<object>());
}
var resultadosMunicipales = await _dbContext.ResultadosVotos
.AsNoTracking()
.Include(r => r.AgrupacionPolitica)
.Where(r => r.CategoriaId == 7 && municipiosDeLaSeccion.Contains(r.AmbitoGeograficoId))
.ToListAsync();
var logosConcejales = await _dbContext.LogosAgrupacionesCategorias
.AsNoTracking()
.Where(l => l.CategoriaId == 7)
.ToDictionaryAsync(l => l.AgrupacionPoliticaId);
var totalVotosSeccion = (decimal)resultadosMunicipales.Sum(r => r.CantidadVotos);
var resultadosFinales = resultadosMunicipales
// 1. Agrupamos por el ID del partido para evitar duplicados.
.GroupBy(r => r.AgrupacionPoliticaId)
.Select(g => new
{
// 2. Obtenemos la entidad completa del primer elemento del grupo.
Agrupacion = g.First().AgrupacionPolitica,
Votos = g.Sum(r => r.CantidadVotos)
})
.OrderByDescending(r => r.Votos)
.Select(r => new
{
Id = r.Agrupacion.Id, // Aseguramos que el Id esté en el objeto final
r.Agrupacion.Nombre,
r.Agrupacion.NombreCorto,
r.Agrupacion.Color,
LogoUrl = logosConcejales.GetValueOrDefault(r.Agrupacion.Id)?.LogoUrl,
Votos = r.Votos,
// --- CORRECCIÓN CLAVE ---
// 3. Usamos el nombre de propiedad correcto que el frontend espera: 'votosPorcentaje'
VotosPorcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0
})
.ToList();
return Ok(resultadosFinales);
}
[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.
var resultadosMunicipales = await _dbContext.ResultadosVotos
.AsNoTracking()
.Include(r => r.AmbitoGeografico)
.Include(r => r.AgrupacionPolitica)
.Where(r => r.CategoriaId == categoriaId && r.AmbitoGeografico.NivelId == 30)
.ToListAsync();
// 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)
{
// 1. Encontrar todos los municipios (Nivel 30) que pertenecen a la sección dada (Nivel 20)
var municipiosDeLaSeccion = await _dbContext.AmbitosGeograficos
.AsNoTracking()
.Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId)
.Select(a => a.Id) // Solo necesitamos sus IDs
.Select(a => a.Id)
.ToListAsync();
if (!municipiosDeLaSeccion.Any())
{
return Ok(new List<object>());
}
if (!municipiosDeLaSeccion.Any()) return Ok(new List<object>());
// 2. Obtener todos los resultados de la categoría Concejales (ID 7) para esos municipios
var resultadosMunicipales = await _dbContext.ResultadosVotos
.AsNoTracking()
.Include(r => r.AgrupacionPolitica)
.Where(r => r.CategoriaId == 7 && municipiosDeLaSeccion.Contains(r.AmbitoGeograficoId))
.Where(r => r.CategoriaId == categoriaId && municipiosDeLaSeccion.Contains(r.AmbitoGeograficoId))
.ToListAsync();
var logosConcejales = await _dbContext.LogosAgrupacionesCategorias
.AsNoTracking()
.Where(l => l.CategoriaId == 7)
.ToDictionaryAsync(l => l.AgrupacionPoliticaId);
// 3. Agrupar y sumar en memoria para obtener el total por partido para la sección
var totalVotosSeccion = resultadosMunicipales.Sum(r => r.CantidadVotos);
var totalVotosSeccion = (decimal)resultadosMunicipales.Sum(r => r.CantidadVotos);
var resultadosFinales = resultadosMunicipales
.GroupBy(r => r.AgrupacionPolitica)
// 1. Agrupamos por el ID del partido, que es un valor único y estable
.GroupBy(r => r.AgrupacionPoliticaId)
.Select(g => new
{
Agrupacion = g.Key,
// 2. Obtenemos el objeto Agrupacion del primer elemento del grupo
Agrupacion = g.First().AgrupacionPolitica,
Votos = g.Sum(r => r.CantidadVotos)
})
.OrderByDescending(r => r.Votos)
.Select(r => new
{
r.Agrupacion.Id,
r.Agrupacion.Nombre,
r.Agrupacion.NombreCorto,
r.Agrupacion.Color,
LogoUrl = logosConcejales.GetValueOrDefault(r.Agrupacion.Id)?.LogoUrl,
r.Votos,
votosPorcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0
id = r.Agrupacion.Id,
nombre = r.Agrupacion.NombreCorto ?? r.Agrupacion.Nombre,
votos = r.Votos,
porcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0
})
.ToList();
return Ok(resultadosFinales);
}
[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);
}
}