Feat Widgets 1540
This commit is contained in:
@@ -22,13 +22,11 @@ public class ResultadosController : ControllerBase
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
[HttpGet("partido/{seccionId}")]
|
||||
[HttpGet("partido/{seccionId}")] // 'seccionId' es el ID del municipio
|
||||
public async Task<IActionResult> GetResultadosPorPartido(string seccionId)
|
||||
{
|
||||
// 1. Buscamos el ámbito geográfico correspondiente al PARTIDO (Nivel 30)
|
||||
var ambito = await _dbContext.AmbitosGeograficos
|
||||
.AsNoTracking()
|
||||
// CAMBIO CLAVE: Buscamos por SeccionId y NivelId para ser precisos
|
||||
.FirstOrDefaultAsync(a => a.SeccionId == seccionId && a.NivelId == 30);
|
||||
|
||||
if (ambito == null)
|
||||
@@ -36,14 +34,12 @@ public class ResultadosController : ControllerBase
|
||||
return NotFound(new { message = $"No se encontró el partido con ID {seccionId}" });
|
||||
}
|
||||
|
||||
// 2. Buscamos el estado del recuento para ese ámbito
|
||||
var estadoRecuento = await _dbContext.EstadosRecuentos
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(e => e.AmbitoGeograficoId == ambito.Id);
|
||||
|
||||
if (estadoRecuento == null)
|
||||
{
|
||||
// Devolvemos una respuesta vacía pero válida para el frontend
|
||||
return Ok(new MunicipioResultadosDto
|
||||
{
|
||||
MunicipioNombre = ambito.Nombre,
|
||||
@@ -55,28 +51,57 @@ public class ResultadosController : ControllerBase
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Buscamos todos los votos para ese ámbito
|
||||
// 1. Obtenemos los IDs de las agrupaciones que tienen resultados en este municipio
|
||||
var agrupacionIds = await _dbContext.ResultadosVotos
|
||||
.Where(rv => rv.AmbitoGeograficoId == ambito.Id)
|
||||
.Select(rv => rv.AgrupacionPoliticaId)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
// 2. Buscamos TODOS los logos relevantes en una sola consulta:
|
||||
// - Los que son para la categoría Concejales (7)
|
||||
// - Y pertenecen a los partidos que compiten aquí
|
||||
// - Y son genéricos (sin ámbito) O específicos para ESTE municipio
|
||||
var logosRelevantes = await _dbContext.LogosAgrupacionesCategorias
|
||||
.AsNoTracking()
|
||||
.Where(l => l.CategoriaId == 7 &&
|
||||
agrupacionIds.Contains(l.AgrupacionPoliticaId) &&
|
||||
(l.AmbitoGeograficoId == null || l.AmbitoGeograficoId == ambito.Id))
|
||||
.ToListAsync();
|
||||
|
||||
var resultadosVotos = await _dbContext.ResultadosVotos
|
||||
.AsNoTracking()
|
||||
.Include(rv => rv.AgrupacionPolitica)
|
||||
.Where(rv => rv.AmbitoGeograficoId == ambito.Id)
|
||||
.ToListAsync();
|
||||
|
||||
// 4. Calculamos el total de votos positivos
|
||||
long totalVotosPositivos = resultadosVotos.Sum(r => r.CantidadVotos);
|
||||
|
||||
// 5. Mapeamos al DTO de respuesta
|
||||
var respuestaDto = new MunicipioResultadosDto
|
||||
{
|
||||
MunicipioNombre = ambito.Nombre,
|
||||
UltimaActualizacion = estadoRecuento.FechaTotalizacion,
|
||||
PorcentajeEscrutado = estadoRecuento.MesasTotalizadasPorcentaje,
|
||||
PorcentajeParticipacion = estadoRecuento.ParticipacionPorcentaje,
|
||||
Resultados = resultadosVotos.Select(rv => new AgrupacionResultadoDto
|
||||
Resultados = resultadosVotos.Select(rv =>
|
||||
{
|
||||
Nombre = rv.AgrupacionPolitica.Nombre,
|
||||
Votos = rv.CantidadVotos,
|
||||
Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0
|
||||
// --- LÓGICA DE FALLBACK ---
|
||||
var logoUrl =
|
||||
// 1. Buscamos primero el logo específico para este municipio.
|
||||
logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == ambito.Id)?.LogoUrl
|
||||
// 2. Si no lo encontramos, buscamos el logo genérico (sin ámbito).
|
||||
?? logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == null)?.LogoUrl;
|
||||
|
||||
return new AgrupacionResultadoDto
|
||||
{
|
||||
Id = rv.AgrupacionPolitica.Id,
|
||||
Nombre = rv.AgrupacionPolitica.Nombre,
|
||||
NombreCorto = rv.AgrupacionPolitica.NombreCorto,
|
||||
Color = rv.AgrupacionPolitica.Color,
|
||||
LogoUrl = logoUrl, // Asignamos el logo encontrado
|
||||
Votos = rv.CantidadVotos,
|
||||
Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0
|
||||
};
|
||||
}).OrderByDescending(r => r.Votos).ToList(),
|
||||
VotosAdicionales = new VotosAdicionalesDto
|
||||
{
|
||||
@@ -95,15 +120,10 @@ public class ResultadosController : ControllerBase
|
||||
var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
||||
.FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10);
|
||||
|
||||
var todosLosResumenes = await _dbContext.ResumenesVotos.AsNoTracking()
|
||||
.Include(r => r.AgrupacionPolitica)
|
||||
.ToListAsync();
|
||||
|
||||
// OBTENER TODOS LOS LOGOS EN UNA SOLA CONSULTA
|
||||
var logosLookup = (await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync())
|
||||
.ToLookup(l => $"{l.AgrupacionPoliticaId}-{l.CategoriaId}");
|
||||
|
||||
if (provincia == null) return NotFound($"No se encontró la provincia con distritoId {distritoId}");
|
||||
if (provincia == null)
|
||||
{
|
||||
return NotFound($"No se encontró la provincia con distritoId {distritoId}");
|
||||
}
|
||||
|
||||
var estadosPorCategoria = await _dbContext.EstadosRecuentosGenerales.AsNoTracking()
|
||||
.Include(e => e.CategoriaElectoral)
|
||||
@@ -113,43 +133,50 @@ public class ResultadosController : ControllerBase
|
||||
var resultadosPorMunicipio = await _dbContext.ResultadosVotos
|
||||
.AsNoTracking()
|
||||
.Include(r => r.AgrupacionPolitica)
|
||||
.Where(r => r.AmbitoGeografico.NivelId == 30)
|
||||
.Where(r => r.AmbitoGeografico.NivelId == 30) // Nivel 30 = Municipio
|
||||
.ToListAsync();
|
||||
|
||||
var logos = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync();
|
||||
|
||||
var resultadosAgrupados = resultadosPorMunicipio
|
||||
.GroupBy(r => r.CategoriaId)
|
||||
.Select(g => new
|
||||
{
|
||||
CategoriaId = g.Key,
|
||||
CategoriaNombre = estadosPorCategoria.ContainsKey(g.Key) ? estadosPorCategoria[g.Key].CategoriaElectoral.Nombre : "Desconocido",
|
||||
EstadoRecuento = estadosPorCategoria.GetValueOrDefault(g.Key),
|
||||
TotalVotosCategoria = g.Sum(r => r.CantidadVotos),
|
||||
// Agrupamos por el ID de la agrupación, no por el objeto
|
||||
Resultados = g.GroupBy(r => r.AgrupacionPoliticaId)
|
||||
.Select(partidoGroup => new
|
||||
{
|
||||
// El objeto Agrupacion lo tomamos del primer elemento del grupo
|
||||
Agrupacion = partidoGroup.First().AgrupacionPolitica,
|
||||
Votos = partidoGroup.Sum(r => r.CantidadVotos)
|
||||
})
|
||||
.ToList()
|
||||
Resultados = g
|
||||
.GroupBy(r => r.AgrupacionPolitica)
|
||||
.Select(partidoGroup => new
|
||||
{
|
||||
Agrupacion = partidoGroup.Key,
|
||||
Votos = partidoGroup.Sum(r => r.CantidadVotos)
|
||||
})
|
||||
.OrderByDescending(r => r.Votos)
|
||||
.ToList()
|
||||
})
|
||||
.Select(g => new
|
||||
{
|
||||
g.CategoriaId,
|
||||
CategoriaNombre = estadosPorCategoria.ContainsKey(g.CategoriaId) ? estadosPorCategoria[g.CategoriaId].CategoriaElectoral.Nombre : "Desconocido",
|
||||
EstadoRecuento = estadosPorCategoria.GetValueOrDefault(g.CategoriaId),
|
||||
Resultados = g.Resultados
|
||||
.Select(r => new
|
||||
{
|
||||
r.Agrupacion.Id,
|
||||
r.Agrupacion.Nombre,
|
||||
r.Agrupacion.NombreCorto,
|
||||
r.Agrupacion.Color,
|
||||
LogoUrl = logosLookup[$"{r.Agrupacion.Id}-{g.CategoriaId}"].FirstOrDefault()?.LogoUrl,
|
||||
r.Votos,
|
||||
VotosPorcentaje = g.TotalVotosCategoria > 0 ? ((decimal)r.Votos * 100 / g.TotalVotosCategoria) : 0
|
||||
})
|
||||
.OrderByDescending(r => r.Votos)
|
||||
.ToList()
|
||||
g.CategoriaNombre,
|
||||
g.EstadoRecuento,
|
||||
Resultados = g.Resultados.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
|
||||
};
|
||||
}).ToList()
|
||||
})
|
||||
.OrderBy(c => c.CategoriaId)
|
||||
.ToList();
|
||||
@@ -238,10 +265,21 @@ public class ResultadosController : ControllerBase
|
||||
[HttpGet("municipio/{ambitoId}")]
|
||||
public async Task<IActionResult> GetResultadosPorMunicipio(int ambitoId, [FromQuery] int categoriaId)
|
||||
{
|
||||
_logger.LogInformation("Buscando resultados para AmbitoGeograficoId: {AmbitoId}, CategoriaId: {CategoriaId}", ambitoId, categoriaId);
|
||||
|
||||
// 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}");
|
||||
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);
|
||||
|
||||
// Obtenemos los votos para ESE municipio y ESA categoría
|
||||
var resultadosVotos = await _dbContext.ResultadosVotos
|
||||
@@ -253,18 +291,35 @@ public class ResultadosController : ControllerBase
|
||||
// Calculamos el total de votos solo para esta selección
|
||||
var totalVotosPositivos = (decimal)resultadosVotos.Sum(r => r.CantidadVotos);
|
||||
|
||||
// Mapeamos a la respuesta final que espera el frontend
|
||||
var respuesta = resultadosVotos
|
||||
// Mapeamos los resultados de los partidos
|
||||
var resultadosPartidosDto = resultadosVotos
|
||||
.OrderByDescending(r => r.CantidadVotos)
|
||||
.Select(rv => new
|
||||
.Select(rv => new AgrupacionResultadoDto // Assuming AgrupacionResultadoDto is the correct DTO for individual party results
|
||||
{
|
||||
id = rv.AgrupacionPolitica.Id,
|
||||
nombre = rv.AgrupacionPolitica.NombreCorto ?? rv.AgrupacionPolitica.Nombre,
|
||||
votos = rv.CantidadVotos,
|
||||
porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos / totalVotosPositivos) * 100 : 0
|
||||
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
|
||||
}).ToList();
|
||||
|
||||
return Ok(respuesta);
|
||||
// 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);
|
||||
}
|
||||
|
||||
[HttpGet("composicion-congreso")]
|
||||
@@ -504,59 +559,59 @@ public class ResultadosController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("concejales/{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())
|
||||
public async Task<IActionResult> GetResultadosConcejalesPorSeccion(string seccionId)
|
||||
{
|
||||
return Ok(new List<object>());
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -616,12 +671,13 @@ public async Task<IActionResult> GetResultadosConcejalesPorSeccion(string seccio
|
||||
.ToListAsync();
|
||||
|
||||
var totalVotosSeccion = (decimal)resultadosMunicipales.Sum(r => r.CantidadVotos);
|
||||
|
||||
var resultadosFinales = resultadosMunicipales
|
||||
// 1. Agrupamos por el ID del partido, que es un valor único y estable
|
||||
// 1. Agrupamos por el ID del partido para evitar duplicados.
|
||||
.GroupBy(r => r.AgrupacionPoliticaId)
|
||||
.Select(g => new
|
||||
{
|
||||
// 2. Obtenemos el objeto Agrupacion del primer elemento del grupo
|
||||
// 2. Tomamos la entidad completa del primer elemento del grupo.
|
||||
Agrupacion = g.First().AgrupacionPolitica,
|
||||
Votos = g.Sum(r => r.CantidadVotos)
|
||||
})
|
||||
@@ -631,7 +687,9 @@ public async Task<IActionResult> GetResultadosConcejalesPorSeccion(string seccio
|
||||
id = r.Agrupacion.Id,
|
||||
nombre = r.Agrupacion.NombreCorto ?? r.Agrupacion.Nombre,
|
||||
votos = r.Votos,
|
||||
porcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0
|
||||
porcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0,
|
||||
// 3. Añadimos el color a la respuesta.
|
||||
color = r.Agrupacion.Color
|
||||
})
|
||||
.ToList();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user