Feat Widgets
This commit is contained in:
@@ -92,59 +92,69 @@ public class ResultadosController : ControllerBase
|
||||
[HttpGet("provincia/{distritoId}")]
|
||||
public async Task<IActionResult> GetResultadosProvinciales(string distritoId)
|
||||
{
|
||||
_logger.LogInformation("Solicitud de resultados para la provincia con distritoId: {DistritoId}", distritoId);
|
||||
|
||||
// PASO 1: Encontrar el ámbito geográfico de la provincia.
|
||||
var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
||||
.FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10);
|
||||
|
||||
if (provincia == null)
|
||||
{
|
||||
_logger.LogWarning("No se encontró la provincia con distritoId: {DistritoId}", distritoId);
|
||||
return NotFound(new { message = $"No se encontró la provincia con distritoId {distritoId}" });
|
||||
}
|
||||
|
||||
// PASO 2: Obtener el estado general del recuento para la provincia.
|
||||
// Como las estadísticas generales (mesas, participación) son las mismas para todas las categorías,
|
||||
// simplemente tomamos la primera que encontremos para este ámbito.
|
||||
var estadoGeneral = await _dbContext.EstadosRecuentosGenerales.AsNoTracking()
|
||||
.FirstOrDefaultAsync(e => e.AmbitoGeograficoId == provincia.Id);
|
||||
|
||||
// PASO 3: Obtener el resumen de votos por agrupación para la provincia.
|
||||
// Hacemos un JOIN manual entre ResumenesVotos y AgrupacionesPoliticas para obtener los nombres.
|
||||
var resultados = await _dbContext.ResumenesVotos
|
||||
.AsNoTracking()
|
||||
.Where(r => r.AmbitoGeograficoId == provincia.Id)
|
||||
.Join(
|
||||
_dbContext.AgrupacionesPoliticas.AsNoTracking(),
|
||||
resumen => resumen.AgrupacionPoliticaId,
|
||||
agrupacion => agrupacion.Id,
|
||||
(resumen, agrupacion) => new AgrupacionResultadoDto
|
||||
{
|
||||
Nombre = agrupacion.Nombre,
|
||||
Votos = resumen.Votos,
|
||||
Porcentaje = resumen.VotosPorcentaje
|
||||
})
|
||||
.OrderByDescending(r => r.Votos)
|
||||
var todosLosResumenes = await _dbContext.ResumenesVotos.AsNoTracking()
|
||||
.Include(r => r.AgrupacionPolitica)
|
||||
.ToListAsync();
|
||||
|
||||
// PASO 4: Construir el objeto de respuesta (DTO).
|
||||
// Si no hay datos de recuento aún, usamos valores por defecto para evitar errores en el frontend.
|
||||
var respuestaDto = new ResumenProvincialDto
|
||||
{
|
||||
ProvinciaNombre = provincia.Nombre,
|
||||
UltimaActualizacion = estadoGeneral?.FechaTotalizacion ?? DateTime.UtcNow,
|
||||
PorcentajeEscrutado = estadoGeneral?.MesasTotalizadasPorcentaje ?? 0,
|
||||
PorcentajeParticipacion = estadoGeneral?.ParticipacionPorcentaje ?? 0,
|
||||
Resultados = resultados,
|
||||
// NOTA: Los votos adicionales (nulos, en blanco) no están en la tabla de resumen provincial.
|
||||
// Esto es una mejora pendiente en el Worker. Por ahora, devolvemos 0.
|
||||
VotosAdicionales = new VotosAdicionalesDto { EnBlanco = 0, Nulos = 0, Recurridos = 0 }
|
||||
};
|
||||
// OBTENER TODOS LOS LOGOS EN UNA SOLA CONSULTA
|
||||
var logosLookup = (await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync())
|
||||
.ToLookup(l => $"{l.AgrupacionPoliticaId}-{l.CategoriaId}");
|
||||
|
||||
_logger.LogInformation("Devolviendo {NumResultados} resultados de agrupaciones para la provincia.", respuestaDto.Resultados.Count);
|
||||
if (provincia == null) return NotFound($"No se encontró la provincia con distritoId {distritoId}");
|
||||
|
||||
return Ok(respuestaDto);
|
||||
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
|
||||
.AsNoTracking()
|
||||
.Include(r => r.AgrupacionPolitica)
|
||||
.Where(r => r.AmbitoGeografico.NivelId == 30)
|
||||
.ToListAsync();
|
||||
|
||||
var resultadosAgrupados = resultadosPorMunicipio
|
||||
.GroupBy(r => r.CategoriaId)
|
||||
.Select(g => new
|
||||
{
|
||||
CategoriaId = 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()
|
||||
})
|
||||
.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()
|
||||
})
|
||||
.OrderBy(c => c.CategoriaId)
|
||||
.ToList();
|
||||
|
||||
return Ok(resultadosAgrupados);
|
||||
}
|
||||
|
||||
|
||||
@@ -503,4 +513,77 @@ public class ResultadosController : ControllerBase
|
||||
|
||||
return Ok(bancadasConOcupantes);
|
||||
}
|
||||
[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);
|
||||
}
|
||||
|
||||
[HttpGet("concejales/{seccionId}")]
|
||||
public async Task<IActionResult> GetResultadosConcejalesPorSeccion(string seccionId)
|
||||
{
|
||||
// 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
|
||||
.ToListAsync();
|
||||
|
||||
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))
|
||||
.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 resultadosFinales = resultadosMunicipales
|
||||
.GroupBy(r => r.AgrupacionPolitica)
|
||||
.Select(g => new
|
||||
{
|
||||
Agrupacion = g.Key,
|
||||
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
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Ok(resultadosFinales);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user