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-08-22 21:55:03 -03:00
|
|
|
[HttpGet("partido/{seccionId}")]
|
|
|
|
|
public async Task<IActionResult> GetResultadosPorPartido(string seccionId)
|
2025-08-14 15:27:45 -03:00
|
|
|
{
|
2025-08-22 21:55:03 -03:00
|
|
|
// 1. Buscamos el ámbito geográfico correspondiente al PARTIDO (Nivel 30)
|
2025-08-14 15:27:45 -03:00
|
|
|
var ambito = await _dbContext.AmbitosGeograficos
|
|
|
|
|
.AsNoTracking()
|
2025-08-22 21:55:03 -03:00
|
|
|
// CAMBIO CLAVE: Buscamos por SeccionId y NivelId para ser precisos
|
|
|
|
|
.FirstOrDefaultAsync(a => a.SeccionId == seccionId && a.NivelId == 30);
|
2025-08-14 15:27:45 -03:00
|
|
|
|
|
|
|
|
if (ambito == null)
|
|
|
|
|
{
|
2025-08-22 21:55:03 -03:00
|
|
|
return NotFound(new { message = $"No se encontró el partido con ID {seccionId}" });
|
2025-08-14 15:27:45 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Buscamos el estado del recuento para ese ámbito
|
|
|
|
|
var estadoRecuento = await _dbContext.EstadosRecuentos
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
.FirstOrDefaultAsync(e => e.AmbitoGeograficoId == ambito.Id);
|
|
|
|
|
|
|
|
|
|
if (estadoRecuento == null)
|
|
|
|
|
{
|
2025-08-22 21:55:03 -03:00
|
|
|
// Devolvemos una respuesta vacía pero válida para el frontend
|
|
|
|
|
return Ok(new MunicipioResultadosDto
|
|
|
|
|
{
|
|
|
|
|
MunicipioNombre = ambito.Nombre,
|
|
|
|
|
UltimaActualizacion = DateTime.UtcNow,
|
|
|
|
|
PorcentajeEscrutado = 0,
|
|
|
|
|
PorcentajeParticipacion = 0,
|
|
|
|
|
Resultados = new List<AgrupacionResultadoDto>(),
|
|
|
|
|
VotosAdicionales = new VotosAdicionalesDto()
|
|
|
|
|
});
|
2025-08-14 15:27:45 -03:00
|
|
|
}
|
|
|
|
|
|
2025-08-22 21:55:03 -03:00
|
|
|
// 3. Buscamos todos los votos para ese ámbito
|
2025-08-14 15:27:45 -03:00
|
|
|
var resultadosVotos = await _dbContext.ResultadosVotos
|
|
|
|
|
.AsNoTracking()
|
2025-08-22 21:55:03 -03:00
|
|
|
.Include(rv => rv.AgrupacionPolitica)
|
2025-08-14 15:27:45 -03:00
|
|
|
.Where(rv => rv.AmbitoGeograficoId == ambito.Id)
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
2025-08-22 21:55:03 -03:00
|
|
|
// 4. Calculamos el total de votos positivos
|
2025-08-14 15:27:45 -03:00
|
|
|
long totalVotosPositivos = resultadosVotos.Sum(r => r.CantidadVotos);
|
|
|
|
|
|
2025-08-22 21:55:03 -03:00
|
|
|
// 5. Mapeamos al DTO de respuesta
|
2025-08-14 15:27:45 -03:00
|
|
|
var respuestaDto = new MunicipioResultadosDto
|
|
|
|
|
{
|
|
|
|
|
MunicipioNombre = ambito.Nombre,
|
|
|
|
|
UltimaActualizacion = estadoRecuento.FechaTotalizacion,
|
2025-08-22 21:55:03 -03:00
|
|
|
PorcentajeEscrutado = estadoRecuento.MesasTotalizadasPorcentaje,
|
2025-08-14 15:27:45 -03:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return Ok(respuestaDto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet("provincia/{distritoId}")]
|
|
|
|
|
public async Task<IActionResult> GetResultadosProvinciales(string distritoId)
|
|
|
|
|
{
|
2025-08-25 10:25:54 -03:00
|
|
|
_logger.LogInformation("Solicitud de resultados para la provincia con distritoId: {DistritoId}", distritoId);
|
2025-08-14 15:27:45 -03:00
|
|
|
|
2025-08-25 10:25:54 -03:00
|
|
|
// PASO 1: Encontrar el ámbito geográfico de la provincia.
|
|
|
|
|
var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
|
|
|
|
.FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10);
|
2025-08-14 15:27:45 -03:00
|
|
|
|
2025-08-25 10:25:54 -03:00
|
|
|
if (provincia == null)
|
2025-08-14 15:27:45 -03:00
|
|
|
{
|
2025-08-25 10:25:54 -03:00
|
|
|
_logger.LogWarning("No se encontró la provincia con distritoId: {DistritoId}", distritoId);
|
|
|
|
|
return NotFound(new { message = $"No se encontró la provincia con distritoId {distritoId}" });
|
2025-08-14 15:27:45 -03:00
|
|
|
}
|
|
|
|
|
|
2025-08-25 10:25:54 -03:00
|
|
|
// 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)
|
|
|
|
|
.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
|
2025-08-14 15:27:45 -03:00
|
|
|
{
|
2025-08-25 10:25:54 -03:00
|
|
|
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 }
|
2025-08-14 15:27:45 -03:00
|
|
|
};
|
|
|
|
|
|
2025-08-25 10:25:54 -03:00
|
|
|
_logger.LogInformation("Devolviendo {NumResultados} resultados de agrupaciones para la provincia.", respuestaDto.Resultados.Count);
|
|
|
|
|
|
|
|
|
|
return Ok(respuestaDto);
|
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-08-15 17:31:51 -03:00
|
|
|
[HttpGet("bancas/{seccionId}")]
|
|
|
|
|
public async Task<IActionResult> GetBancasPorSeccion(string seccionId)
|
|
|
|
|
{
|
2025-08-25 10:25:54 -03:00
|
|
|
// 1. Buscamos el ámbito usando 'SeccionProvincialId'.
|
|
|
|
|
// La API oficial usa este campo para las secciones electorales.
|
|
|
|
|
// Además, el worker guarda estas secciones con NivelId = 20, por lo que lo usamos aquí para consistencia.
|
2025-08-15 17:31:51 -03:00
|
|
|
var seccion = await _dbContext.AmbitosGeograficos
|
|
|
|
|
.AsNoTracking()
|
2025-08-25 10:25:54 -03:00
|
|
|
.FirstOrDefaultAsync(a => a.SeccionProvincialId == seccionId && a.NivelId == 20); // Nivel 20 = Sección Electoral Provincial
|
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-08-25 10:25:54 -03:00
|
|
|
// 2. Buscamos todas las proyecciones para ese ámbito (usando su clave primaria 'Id')
|
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-08-15 17:31:51 -03:00
|
|
|
.Where(p => p.AmbitoGeograficoId == seccion.Id)
|
|
|
|
|
.Select(p => new
|
|
|
|
|
{
|
|
|
|
|
AgrupacionNombre = p.AgrupacionPolitica.Nombre,
|
|
|
|
|
Bancas = p.NroBancas
|
|
|
|
|
})
|
|
|
|
|
.OrderByDescending(p => p.Bancas)
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
if (!proyecciones.Any())
|
|
|
|
|
{
|
2025-08-25 10:25:54 -03:00
|
|
|
// Este caso es posible si aún no hay proyecciones calculadas para esta sección.
|
|
|
|
|
_logger.LogWarning("No se encontraron proyecciones de bancas para la sección: {SeccionNombre}", seccion.Nombre);
|
2025-08-15 17:31:51 -03:00
|
|
|
return NotFound(new { message = $"No se han encontrado proyecciones de bancas para la sección {seccion.Nombre}" });
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-25 10:25:54 -03:00
|
|
|
// 3. Devolvemos la respuesta
|
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
|
|
|
|
|
|
|
|
[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
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation("Buscando resultados para AmbitoGeograficoId: {AmbitoId}", 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.
|
|
|
|
|
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)
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
// PASO 4: Calcular el total de votos positivos para el porcentaje.
|
|
|
|
|
long totalVotosPositivos = 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
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return Ok(respuestaDto);
|
|
|
|
|
}
|
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);
|
|
|
|
|
|
|
|
|
|
config.TryGetValue("UsarDatosDeBancadasOficiales", out var usarDatosOficialesValue);
|
|
|
|
|
bool usarDatosOficiales = usarDatosOficialesValue == "true";
|
|
|
|
|
|
|
|
|
|
if (usarDatosOficiales)
|
|
|
|
|
{
|
|
|
|
|
return await GetComposicionDesdeBancadasOficiales(config);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return await GetComposicionDesdeProyecciones(config);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<IActionResult> GetComposicionDesdeBancadasOficiales(Dictionary<string, string> config)
|
|
|
|
|
{
|
|
|
|
|
config.TryGetValue("MostrarOcupantes", out var mostrarOcupantesValue);
|
|
|
|
|
bool mostrarOcupantes = mostrarOcupantesValue == "true";
|
|
|
|
|
|
|
|
|
|
// Se declara la variable explícitamente como IQueryable<Bancada>
|
|
|
|
|
IQueryable<Bancada> bancadasQuery = _dbContext.Bancadas.AsNoTracking()
|
|
|
|
|
.Include(b => b.AgrupacionPolitica);
|
|
|
|
|
|
|
|
|
|
if (mostrarOcupantes)
|
|
|
|
|
{
|
|
|
|
|
// Ahora sí podemos añadir otro .Include() sin problemas de tipo
|
|
|
|
|
bancadasQuery = bancadasQuery.Include(b => b.Ocupante);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bancadas = await bancadasQuery.ToListAsync();
|
|
|
|
|
|
|
|
|
|
// --- CAMBIO 2: Eliminar la carga manual de Ocupantes ---
|
|
|
|
|
// Ya no necesitamos 'ocupantesLookup'. Se puede borrar todo este bloque:
|
|
|
|
|
/*
|
|
|
|
|
var ocupantesLookup = new Dictionary<int, OcupanteBanca>();
|
|
|
|
|
if (mostrarOcupantes)
|
|
|
|
|
{
|
|
|
|
|
ocupantesLookup = (await _dbContext.OcupantesBancas.AsNoTracking()
|
|
|
|
|
.ToListAsync())
|
|
|
|
|
.ToDictionary(o => o.BancadaId);
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
var bancasPorAgrupacion = bancadas
|
|
|
|
|
.Where(b => b.AgrupacionPoliticaId != null)
|
|
|
|
|
.GroupBy(b => new { b.AgrupacionPoliticaId, b.Camara })
|
|
|
|
|
.Select(g => new
|
|
|
|
|
{
|
|
|
|
|
Agrupacion = g.First().AgrupacionPolitica,
|
|
|
|
|
g.Key.Camara,
|
|
|
|
|
BancasTotales = g.Count()
|
|
|
|
|
})
|
|
|
|
|
.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);
|
|
|
|
|
|
|
|
|
|
object MapearPartidos(Core.Enums.TipoCamara camara)
|
|
|
|
|
{
|
|
|
|
|
// 1. Filtramos las bancadas que nos interesan (por cámara y que tengan partido).
|
|
|
|
|
var bancadasDeCamara = bancadas
|
|
|
|
|
.Where(b => b.Camara == camara && b.AgrupacionPolitica != null);
|
|
|
|
|
|
|
|
|
|
// 2. --- ¡EL CAMBIO CLAVE ESTÁ AQUÍ! ---
|
|
|
|
|
// Agrupamos por el ID de la Agrupación, no por el objeto.
|
|
|
|
|
// Esto garantiza que todas las bancadas del mismo partido terminen en el MISMO grupo.
|
|
|
|
|
var partidosDeCamara = bancadasDeCamara
|
|
|
|
|
.GroupBy(b => b.AgrupacionPolitica!.Id)
|
|
|
|
|
.Select(g => new
|
|
|
|
|
{
|
|
|
|
|
// La Agrupacion la podemos tomar del primer elemento del grupo,
|
|
|
|
|
// ya que todas las bancadas del grupo pertenecen al mismo partido.
|
|
|
|
|
Agrupacion = g.First().AgrupacionPolitica!,
|
|
|
|
|
// g ahora contiene la lista COMPLETA de bancadas para esta agrupación.
|
|
|
|
|
BancasDelPartido = g.ToList()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 3. Ordenamos, como antes, pero ahora sobre una lista de grupos correcta.
|
|
|
|
|
var partidosOrdenados = (camara == Core.Enums.TipoCamara.Diputados)
|
|
|
|
|
? partidosDeCamara.OrderBy(p => p.Agrupacion.OrdenDiputados ?? 999)
|
|
|
|
|
: partidosDeCamara.OrderBy(p => p.Agrupacion.OrdenSenadores ?? 999);
|
|
|
|
|
|
|
|
|
|
// 4. Mapeamos al resultado final.
|
|
|
|
|
return partidosOrdenados
|
|
|
|
|
.ThenByDescending(p => p.BancasDelPartido.Count)
|
|
|
|
|
.Select(p =>
|
|
|
|
|
{
|
|
|
|
|
// Ahora 'p.BancasDelPartido' contiene TODAS las bancadas del partido (en tu caso, las 2).
|
|
|
|
|
// Cuando hagamos el .Select() aquí, recorrerá ambas y encontrará a los ocupantes.
|
|
|
|
|
var ocupantesDelPartido = p.BancasDelPartido
|
|
|
|
|
.Select(b => b.Ocupante)
|
|
|
|
|
.Where(o => o != null)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
return new
|
|
|
|
|
{
|
|
|
|
|
p.Agrupacion.Id,
|
|
|
|
|
p.Agrupacion.Nombre,
|
|
|
|
|
p.Agrupacion.NombreCorto,
|
|
|
|
|
p.Agrupacion.Color,
|
|
|
|
|
BancasTotales = p.BancasDelPartido.Count,
|
|
|
|
|
// ¡Esta lista ahora debería contener a tus 2 ocupantes!
|
|
|
|
|
Ocupantes = mostrarOcupantes ? ocupantesDelPartido : new List<OcupanteBanca?>()
|
|
|
|
|
};
|
|
|
|
|
}).ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// El resto del método permanece igual...
|
|
|
|
|
var diputados = new
|
|
|
|
|
{
|
|
|
|
|
CamaraNombre = "Cámara de Diputados",
|
|
|
|
|
TotalBancas = 92,
|
|
|
|
|
BancasEnJuego = 0,
|
|
|
|
|
Partidos = MapearPartidos(Core.Enums.TipoCamara.Diputados),
|
|
|
|
|
PresidenteBancada = presidenteDiputados != null ? new { presidenteDiputados.Color } : null
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var senadores = new
|
|
|
|
|
{
|
|
|
|
|
CamaraNombre = "Cámara de Senadores",
|
|
|
|
|
TotalBancas = 46,
|
|
|
|
|
BancasEnJuego = 0,
|
|
|
|
|
Partidos = MapearPartidos(Core.Enums.TipoCamara.Senadores),
|
|
|
|
|
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
|
|
|
|
|
.Select(b => new
|
|
|
|
|
{
|
|
|
|
|
b.Agrupacion.Id,
|
|
|
|
|
b.Agrupacion.Nombre,
|
|
|
|
|
b.Agrupacion.NombreCorto,
|
|
|
|
|
b.Agrupacion.Color,
|
|
|
|
|
b.Bancas.BancasTotales
|
|
|
|
|
})
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
config.TryGetValue("MostrarOcupantes", out var mostrarOcupantesValue);
|
|
|
|
|
if (mostrarOcupantesValue != "true")
|
|
|
|
|
{
|
|
|
|
|
// Si la opción está desactivada, devolvemos un array vacío.
|
|
|
|
|
return Ok(new List<object>());
|
2025-08-25 15:04:09 -03:00
|
|
|
}
|
2025-08-25 10:25:54 -03:00
|
|
|
|
2025-08-29 09:54:22 -03:00
|
|
|
var bancadasConOcupantes = await _dbContext.Bancadas
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
.Include(b => b.Ocupante)
|
|
|
|
|
.Where(b => b.Ocupante != null) // Solo las que tienen un ocupante asignado
|
|
|
|
|
.Select(b => new
|
|
|
|
|
{
|
|
|
|
|
b.Id,
|
|
|
|
|
b.Camara,
|
|
|
|
|
b.AgrupacionPoliticaId,
|
|
|
|
|
Ocupante = b.Ocupante
|
|
|
|
|
})
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
return Ok(bancadasConOcupantes);
|
2025-08-25 10:25:54 -03:00
|
|
|
}
|
2025-08-14 15:27:45 -03:00
|
|
|
}
|