Fix bancas widget

This commit is contained in:
2025-08-29 15:49:13 -03:00
parent 1ed9a49a53
commit 3b8c6bf754
13 changed files with 152 additions and 183 deletions

View File

@@ -1,73 +1,68 @@
// src/components/ConfiguracionGeneral.tsx // src/components/ConfiguracionGeneral.tsx
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { getAgrupaciones, getConfiguracion, updateConfiguracion } from '../services/apiService'; import { getAgrupaciones, getConfiguracion, updateConfiguracion } from '../services/apiService';
import type { AgrupacionPolitica } from '../types'; import type { AgrupacionPolitica } from '../types';
import './AgrupacionesManager.css'; // Reutilizamos los estilos para mantener la consistencia import './AgrupacionesManager.css';
export const ConfiguracionGeneral = () => { export const ConfiguracionGeneral = () => {
const queryClient = useQueryClient();
const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]); const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
// Estado específico para la configuración de la presidencia del Senado
const [presidenciaSenadoId, setPresidenciaSenadoId] = useState<string>(''); const [presidenciaSenadoId, setPresidenciaSenadoId] = useState<string>('');
const [usarDatosOficiales, setUsarDatosOficiales] = useState(false); // Renombramos el estado para mayor claridad
const [modoOficialActivo, setModoOficialActivo] = useState(false);
useEffect(() => { useEffect(() => {
const loadInitialData = async () => { const loadInitialData = async () => {
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
const [agrupacionesData, configData] = await Promise.all([getAgrupaciones(), getConfiguracion()]);
// Hacemos ambas llamadas a la API en paralelo para más eficiencia
const [agrupacionesData, configData] = await Promise.all([
getAgrupaciones(),
getConfiguracion()
]);
setAgrupaciones(agrupacionesData); setAgrupaciones(agrupacionesData);
setPresidenciaSenadoId(configData.PresidenciaSenadores || '');
// Asignamos el valor guardado, si existe setModoOficialActivo(configData.UsarDatosDeBancadasOficiales === 'true');
if (configData && configData.PresidenciaSenadores) {
setPresidenciaSenadoId(configData.PresidenciaSenadores);
}
setUsarDatosOficiales(configData.UsarDatosDeBancadasOficiales === 'true');
} catch (err) { } catch (err) {
console.error("Error al cargar datos de configuración:", err); console.error("Error al cargar datos de configuración:", err);
setError("No se pudieron cargar los datos necesarios para la configuración."); setError("No se pudieron cargar los datos necesarios para la configuración.");
} finally { } finally { setLoading(false); }
setLoading(false);
}
}; };
loadInitialData(); loadInitialData();
}, []); }, []);
const handleSave = async () => { const handleSave = async () => {
try { try {
await updateConfiguracion({ "PresidenciaSenadores": presidenciaSenadoId, "UsarDatosDeBancadasOficiales": usarDatosOficiales.toString() }); await updateConfiguracion({
alert('Configuración guardada con éxito.'); "PresidenciaSenadores": presidenciaSenadoId,
} catch (err) { "UsarDatosDeBancadasOficiales": modoOficialActivo.toString()
console.error("Error al guardar la configuración:", err); });
alert('Error al guardar la configuración.'); await queryClient.invalidateQueries({ queryKey: ['composicionCongreso'] });
await queryClient.invalidateQueries({ queryKey: ['bancadasDetalle'] });
alert('Configuración guardada.');
} catch {
alert('Error al guardar.');
} }
}; };
if (loading) return <div className="admin-module"><p>Cargando configuración...</p></div>; if (loading) return <div className="admin-module"><p>Cargando...</p></div>;
if (error) return <div className="admin-module"><p style={{ color: 'red' }}>{error}</p></div>; if (error) return <div className="admin-module"><p style={{ color: 'red' }}>{error}</p></div>;
return ( return (
<div className="admin-module"> <div className="admin-module">
<h3>Configuración General de Cámaras</h3> <h3>Configuración General de Visualización</h3>
<div className="form-group"> <div className="form-group">
<label> <label>
<input <input
type="checkbox" type="checkbox"
checked={usarDatosOficiales} checked={modoOficialActivo}
onChange={e => setUsarDatosOficiales(e.target.checked)} onChange={e => setModoOficialActivo(e.target.checked)}
/> />
Activar Modo "Resultados Oficiales" **Activar Modo "Resultados Oficiales"**
</label> </label>
<p style={{ fontSize: '0.8rem', color: '#666', margin: '0.5rem 0 0 0' }}> <p style={{ fontSize: '0.8rem', color: '#666' }}>
Si está activo, el widget del Congreso mostrará la composición gestionada manualmente en esta página. Si está inactivo, mostrará la proyección en tiempo real de las elecciones. Si está activo, el sitio público mostrará la composición de bancas y los ocupantes definidos manualmente en este panel. Si está inactivo, mostrará la proyección en tiempo real de la elección.
</p> </p>
</div> </div>
<div style={{ marginTop: '1rem', paddingBottom: '1rem', borderBottom: '1px solid #eee' }}> <div style={{ marginTop: '1rem', paddingBottom: '1rem', borderBottom: '1px solid #eee' }}>

View File

@@ -1,11 +1,13 @@
// src/components/LoginPage.tsx // src/components/LoginPage.tsx
import { useState } from 'react'; import { useState } from 'react';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import { useQueryClient } from '@tanstack/react-query';
export const LoginPage = () => { export const LoginPage = () => {
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [error, setError] = useState(''); const [error, setError] = useState('');
const queryClient = useQueryClient();
const { login } = useAuth(); const { login } = useAuth();
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
@@ -14,6 +16,9 @@ export const LoginPage = () => {
const success = await login({ username, password }); const success = await login({ username, password });
if (!success) { if (!success) {
setError('Usuario o contraseña incorrectos.'); setError('Usuario o contraseña incorrectos.');
} else {
// Si el login es exitoso, invalidamos todo para empezar de cero
await queryClient.invalidateQueries();
} }
}; };

View File

@@ -26,8 +26,8 @@ export interface ComposicionData {
export interface OcupanteBanca { export interface OcupanteBanca {
id: number; id: number;
nombreOcupante: string; nombreOcupante: string;
fotoUrl: string; fotoUrl: string | null;
periodo: string; periodo: string | null;
} }
interface PartidoData { interface PartidoData {
@@ -35,7 +35,6 @@ interface PartidoData {
nombre: string; nombre: string;
nombreCorto: string | null; nombreCorto: string | null;
bancasTotales: number; bancasTotales: number;
bancasEnJuego: number;
color: string | null; color: string | null;
ocupantes: OcupanteBanca[]; ocupantes: OcupanteBanca[];
} }

View File

@@ -17,6 +17,8 @@ export const CongresoWidget = () => {
const { data: composicionData, isLoading: isLoadingComposicion, error: errorComposicion } = useQuery<ComposicionData>({ const { data: composicionData, isLoading: isLoadingComposicion, error: errorComposicion } = useQuery<ComposicionData>({
queryKey: ['composicionCongreso'], queryKey: ['composicionCongreso'],
queryFn: getComposicionCongreso, queryFn: getComposicionCongreso,
// Vuelve a buscar los datos cada 20 segundos
refetchInterval: 20000,
}); });
const { data: bancadasDetalle = [] } = useQuery<BancadaDetalle[]>({ const { data: bancadasDetalle = [] } = useQuery<BancadaDetalle[]>({
@@ -29,28 +31,41 @@ export const CongresoWidget = () => {
// --- LÓGICA DE SEATFILLDATA --- // --- LÓGICA DE SEATFILLDATA ---
const seatFillData = useMemo(() => { const seatFillData = useMemo(() => {
if (!datosCamaraActual || !bancadasDetalle.length) return []; if (!datosCamaraActual) return [];
const camaraId = camaraActiva === 'diputados' ? 0 : 1; // --- LÓGICA DEL INTERRUPTOR ---
const bancadasDeCamara = bancadasDetalle.filter(b => b.camara === camaraId); // Verificamos si la respuesta de la API contiene datos de ocupantes.
// Si bancadasDetalle tiene elementos, significa que el modo "Oficial" está activo en el backend.
const modoOficialActivo = bancadasDetalle.length > 0;
// Creamos un mapa de AgrupacionId -> Color para un acceso rápido if (modoOficialActivo) {
const colorMap = new Map<string, string>(); // --- MODO OFICIAL: Construir desde las bancas físicas ---
datosCamaraActual.partidos.forEach(p => { const camaraId = camaraActiva === 'diputados' ? 0 : 1;
if (p.id && p.color) { const bancadasDeCamara = bancadasDetalle.filter(b => b.camara === camaraId);
colorMap.set(p.id, p.color);
}
});
// 1. Aseguramos que la lista de bancas esté en orden físico (por su ID). const colorMap = new Map<string, string>();
const bancadasOrdenadas = bancadasDeCamara.sort((a, b) => a.id - b.id); datosCamaraActual.partidos.forEach(p => {
if (p.id && p.color) {
colorMap.set(p.id, p.color);
}
});
// 2. Mapeamos cada banca física a un objeto SeatFillData. const bancadasOrdenadas = bancadasDeCamara.sort((a, b) => a.id - b.id);
// El índice del array corresponderá al asiento visual.
return bancadasOrdenadas.map(bancada => ({ return bancadasOrdenadas.map(bancada => ({
color: bancada.agrupacionPoliticaId ? colorMap.get(bancada.agrupacionPoliticaId) || DEFAULT_COLOR : DEFAULT_COLOR, color: bancada.agrupacionPoliticaId ? colorMap.get(bancada.agrupacionPoliticaId) || DEFAULT_COLOR : DEFAULT_COLOR,
ocupante: bancada.ocupante ocupante: bancada.ocupante
})); }));
} else {
// --- MODO PROYECCIÓN: Construir desde los totales de los partidos ---
// Esta es la lógica original que teníamos para el modo proyección.
return datosCamaraActual.partidos.flatMap(party => {
const seatColor = party.color || DEFAULT_COLOR;
// En modo proyección, no hay ocupantes individuales.
return Array(party.bancasTotales).fill({ color: seatColor, ocupante: null });
});
}
}, [datosCamaraActual, bancadasDetalle, camaraActiva]); }, [datosCamaraActual, bancadasDetalle, camaraActiva]);

View File

@@ -127,43 +127,44 @@ export const ParliamentLayout: React.FC<ParliamentLayoutProps> = ({
// Si hay un presidente y su partido tiene bancas, le quitamos una para asignarla. // Si hay un presidente y su partido tiene bancas, le quitamos una para asignarla.
if (presidenteBancada && presidenteBancada.color) { if (presidenteBancada && presidenteBancada.color) {
// Encontramos el índice del último asiento que pertenece al partido del presidente.
const lastSeatIndex = finalSeatData.map(s => s.color).lastIndexOf(presidenteBancada.color); const lastSeatIndex = finalSeatData.map(s => s.color).lastIndexOf(presidenteBancada.color);
if (lastSeatIndex !== -1) { if (lastSeatIndex !== -1) {
// Eliminamos ese asiento de la lista de asientos electorales.
finalSeatData.splice(lastSeatIndex, 1); finalSeatData.splice(lastSeatIndex, 1);
} }
} }
const renderedElements = seatElements.map((child, index) => { const renderedElements = seatElements.map((child, index) => {
// El asiento presidencial sigue siendo un caso especial // --- CASO ESPECIAL: ASIENTO PRESIDENCIAL ---
if (index === PRESIDENTE_SEAT_INDEX) { if (index === PRESIDENTE_SEAT_INDEX) {
return React.cloneElement(child, {
fill: presidenteBancada?.color || '#A9A9A9',
stroke: '#000000',
strokeWidth: 2,
// Le damos un tooltip genérico al presidente
'data-tooltip-id': 'seat-tooltip',
'data-tooltip-html': `<div class="seat-tooltip"><p>Presidencia de la Cámara</p></div>`
});
}
// --- LÓGICA NORMAL PARA EL RESTO DE ASIENTOS ---
// Usamos la copia modificada 'finalSeatData'. Como este array es más corto,
// el último asiento electoral no encontrará datos y quedará gris, lo cual es correcto.
const seat = finalSeatData[index];
if (!seat) {
return React.cloneElement(child, { fill: '#E0E0E0', stroke: '#ffffff', strokeWidth: 1.5 });
}
return React.cloneElement(child, { return React.cloneElement(child, {
fill: presidenteBancada?.color || '#A9A9A9', fill: seat.color,
stroke: '#000000', stroke: '#ffffff',
strokeWidth: 2, strokeWidth: 1.5,
'data-tooltip-id': seat.ocupante ? 'seat-tooltip' : undefined,
'data-tooltip-html': seat.ocupante
? `<div class="seat-tooltip"><img src="${seat.ocupante.fotoUrl || '/default-avatar.png'}" alt="${seat.ocupante.nombreOcupante}" /><p>${seat.ocupante.nombreOcupante}</p></div>`
: undefined,
}); });
}
// La lógica ahora es simple: el asiento en el índice X del SVG
// corresponde al asiento en el índice X de los datos.
const seat = seatData[index];
if (!seat) {
return React.cloneElement(child, { fill: '#E0E0E0', stroke: '#ffffff', strokeWidth: 1.5 });
}
return React.cloneElement(child, {
fill: seat.color,
stroke: '#ffffff',
strokeWidth: 1.5,
'data-tooltip-id': seat.ocupante ? 'seat-tooltip' : undefined,
'data-tooltip-html': seat.ocupante
? `<div class="seat-tooltip"><img src="${seat.ocupante.fotoUrl || '/default-avatar.png'}" alt="${seat.ocupante.nombreOcupante}" /><p>${seat.ocupante.nombreOcupante}</p></div>`
: undefined,
}); });
});
return ( return (
<svg viewBox="0 0 550 375" width={size} height={size * (375 / 550)} style={{ display: 'block', margin: 'auto' }}> <svg viewBox="0 0 550 375" width={size} height={size * (375 / 550)} style={{ display: 'block', margin: 'auto' }}>

View File

@@ -81,28 +81,29 @@ export const SenateLayout: React.FC<SenateLayoutProps> = ({
// Si hay un presidente y su partido tiene bancas, le quitamos una para asignarla. // Si hay un presidente y su partido tiene bancas, le quitamos una para asignarla.
if (presidenteBancada && presidenteBancada.color) { if (presidenteBancada && presidenteBancada.color) {
// Encontramos el índice del último asiento que pertenece al partido del presidente.
const lastSeatIndex = finalSeatData.map(s => s.color).lastIndexOf(presidenteBancada.color); const lastSeatIndex = finalSeatData.map(s => s.color).lastIndexOf(presidenteBancada.color);
if (lastSeatIndex !== -1) { if (lastSeatIndex !== -1) {
// Eliminamos ese asiento de la lista de asientos electorales.
finalSeatData.splice(lastSeatIndex, 1); finalSeatData.splice(lastSeatIndex, 1);
} }
} }
const renderedElements = seatElements.map((child, index) => { const renderedElements = seatElements.map((child, index) => {
// El asiento presidencial sigue siendo un caso especial // --- CASO ESPECIAL: ASIENTO PRESIDENCIAL ---
if (index === PRESIDENTE_SEAT_INDEX) { if (index === PRESIDENTE_SEAT_INDEX) {
return React.cloneElement(child, { return React.cloneElement(child, {
fill: presidenteBancada?.color || '#A9A9A9', fill: presidenteBancada?.color || '#A9A9A9',
stroke: '#000000', stroke: '#000000',
strokeWidth: 2, strokeWidth: 2,
// Le damos un tooltip genérico al presidente
'data-tooltip-id': 'seat-tooltip',
'data-tooltip-html': `<div class="seat-tooltip"><p>Presidencia de la Cámara</p></div>`
}); });
} }
// La lógica ahora es simple: el asiento en el índice X del SVG // --- LÓGICA NORMAL PARA EL RESTO DE ASIENTOS ---
// corresponde al asiento en el índice X de los datos. // Usamos la copia modificada 'finalSeatData'. Como este array es más corto,
const seat = seatData[index]; // el último asiento electoral no encontrará datos y quedará gris, lo cual es correcto.
const seat = finalSeatData[index];
if (!seat) { if (!seat) {
return React.cloneElement(child, { fill: '#E0E0E0', stroke: '#ffffff', strokeWidth: 1.5 }); return React.cloneElement(child, { fill: '#E0E0E0', stroke: '#ffffff', strokeWidth: 1.5 });

View File

@@ -295,57 +295,33 @@ public class ResultadosController : ControllerBase
.AsNoTracking() .AsNoTracking()
.ToDictionaryAsync(c => c.Clave, c => c.Valor); .ToDictionaryAsync(c => c.Clave, c => c.Valor);
// Aquí está el interruptor
config.TryGetValue("UsarDatosDeBancadasOficiales", out var usarDatosOficialesValue); config.TryGetValue("UsarDatosDeBancadasOficiales", out var usarDatosOficialesValue);
bool usarDatosOficiales = usarDatosOficialesValue == "true"; bool usarDatosOficiales = usarDatosOficialesValue == "true";
if (usarDatosOficiales) if (usarDatosOficiales)
{ {
// Si el interruptor está en 'true', llama a este método
return await GetComposicionDesdeBancadasOficiales(config); return await GetComposicionDesdeBancadasOficiales(config);
} }
else else
{ {
// Si está en 'false' o no existe, llama a este otro
return await GetComposicionDesdeProyecciones(config); return await GetComposicionDesdeProyecciones(config);
} }
} }
private async Task<IActionResult> GetComposicionDesdeBancadasOficiales(Dictionary<string, string> config) private async Task<IActionResult> GetComposicionDesdeBancadasOficiales(Dictionary<string, string> config)
{ {
config.TryGetValue("MostrarOcupantes", out var mostrarOcupantesValue); var bancadas = await _dbContext.Bancadas.AsNoTracking()
bool mostrarOcupantes = mostrarOcupantesValue == "true"; .Include(b => b.AgrupacionPolitica)
.Include(b => b.Ocupante)
// Se declara la variable explícitamente como IQueryable<Bancada> .ToListAsync();
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 var bancasPorAgrupacion = bancadas
.Where(b => b.AgrupacionPoliticaId != null) .Where(b => b.AgrupacionPolitica != null)
.GroupBy(b => new { b.AgrupacionPoliticaId, b.Camara }) .GroupBy(b => b.AgrupacionPolitica)
.Select(g => new .Select(g => new { Agrupacion = g.Key!, Camara = g.First().Camara, BancasTotales = g.Count() })
{
Agrupacion = g.First().AgrupacionPolitica,
g.Key.Camara,
BancasTotales = g.Count()
})
.ToList(); .ToList();
var presidenteDiputados = bancasPorAgrupacion var presidenteDiputados = bancasPorAgrupacion
@@ -356,25 +332,14 @@ public class ResultadosController : ControllerBase
config.TryGetValue("PresidenciaSenadores", out var idPartidoPresidenteSenadores); config.TryGetValue("PresidenciaSenadores", out var idPartidoPresidenteSenadores);
var presidenteSenadores = await _dbContext.AgrupacionesPoliticas.FindAsync(idPartidoPresidenteSenadores); var presidenteSenadores = await _dbContext.AgrupacionesPoliticas.FindAsync(idPartidoPresidenteSenadores);
object MapearPartidos(Core.Enums.TipoCamara camara) object MapearPartidosOficial(Core.Enums.TipoCamara camara)
{ {
// 1. Filtramos las bancadas que nos interesan (por cámara y que tengan partido). // 1. Filtramos las bancadas que nos interesan (por cámara y que tengan partido).
var bancadasDeCamara = bancadas var partidosDeCamara = bancasPorAgrupacion.Where(b => b.Camara == camara);
.Where(b => b.Camara == camara && b.AgrupacionPolitica != null);
// 2. --- ¡EL CAMBIO CLAVE ESTÁ AQUÍ! --- // 2. --- ¡EL CAMBIO CLAVE ESTÁ AQUÍ! ---
// Agrupamos por el ID de la Agrupación, no por el objeto. // 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. // 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. // 3. Ordenamos, como antes, pero ahora sobre una lista de grupos correcta.
var partidosOrdenados = (camara == Core.Enums.TipoCamara.Diputados) var partidosOrdenados = (camara == Core.Enums.TipoCamara.Diputados)
@@ -382,28 +347,19 @@ public class ResultadosController : ControllerBase
: partidosDeCamara.OrderBy(p => p.Agrupacion.OrdenSenadores ?? 999); : partidosDeCamara.OrderBy(p => p.Agrupacion.OrdenSenadores ?? 999);
// 4. Mapeamos al resultado final. // 4. Mapeamos al resultado final.
return partidosOrdenados return partidosDeCamara.Select(p => new
.ThenByDescending(p => p.BancasDelPartido.Count) {
.Select(p => p.Agrupacion.Id,
{ p.Agrupacion.Nombre,
// Ahora 'p.BancasDelPartido' contiene TODAS las bancadas del partido (en tu caso, las 2). p.Agrupacion.NombreCorto,
// Cuando hagamos el .Select() aquí, recorrerá ambas y encontrará a los ocupantes. p.Agrupacion.Color,
var ocupantesDelPartido = p.BancasDelPartido p.BancasTotales,
.Select(b => b.Ocupante) // Adjuntamos la lista de ocupantes para este partido
.Where(o => o != null) Ocupantes = bancadas
.ToList(); .Where(b => b.AgrupacionPoliticaId == p.Agrupacion.Id && b.Camara == p.Camara && b.Ocupante != null)
.Select(b => b.Ocupante)
return new .ToList()
{ }).ToList();
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... // El resto del método permanece igual...
@@ -412,7 +368,7 @@ public class ResultadosController : ControllerBase
CamaraNombre = "Cámara de Diputados", CamaraNombre = "Cámara de Diputados",
TotalBancas = 92, TotalBancas = 92,
BancasEnJuego = 0, BancasEnJuego = 0,
Partidos = MapearPartidos(Core.Enums.TipoCamara.Diputados), Partidos = MapearPartidosOficial(Core.Enums.TipoCamara.Diputados),
PresidenteBancada = presidenteDiputados != null ? new { presidenteDiputados.Color } : null PresidenteBancada = presidenteDiputados != null ? new { presidenteDiputados.Color } : null
}; };
@@ -421,7 +377,7 @@ public class ResultadosController : ControllerBase
CamaraNombre = "Cámara de Senadores", CamaraNombre = "Cámara de Senadores",
TotalBancas = 46, TotalBancas = 46,
BancasEnJuego = 0, BancasEnJuego = 0,
Partidos = MapearPartidos(Core.Enums.TipoCamara.Senadores), Partidos = MapearPartidosOficial(Core.Enums.TipoCamara.Senadores),
PresidenteBancada = presidenteSenadores != null ? new { presidenteSenadores.Color } : null PresidenteBancada = presidenteSenadores != null ? new { presidenteSenadores.Color } : null
}; };
@@ -466,15 +422,16 @@ public class ResultadosController : ControllerBase
.ThenByDescending(b => b.Bancas.BancasTotales); .ThenByDescending(b => b.Bancas.BancasTotales);
return partidosDeCamara return partidosDeCamara
.Select(b => new .Select(b => new
{ {
b.Agrupacion.Id, b.Agrupacion.Id,
b.Agrupacion.Nombre, b.Agrupacion.Nombre,
b.Agrupacion.NombreCorto, b.Agrupacion.NombreCorto,
b.Agrupacion.Color, b.Agrupacion.Color,
b.Bancas.BancasTotales b.Bancas.BancasTotales,
}) Ocupantes = new List<object>() // <-- Siempre vacío en modo proyección
.ToList(); })
.ToList();
} }
var diputados = new var diputados = new
@@ -502,24 +459,20 @@ public class ResultadosController : ControllerBase
public async Task<IActionResult> GetBancadasConOcupantes() public async Task<IActionResult> GetBancadasConOcupantes()
{ {
var config = await _dbContext.Configuraciones.AsNoTracking().ToDictionaryAsync(c => c.Clave, c => c.Valor); var config = await _dbContext.Configuraciones.AsNoTracking().ToDictionaryAsync(c => c.Clave, c => c.Valor);
config.TryGetValue("MostrarOcupantes", out var mostrarOcupantesValue);
if (mostrarOcupantesValue != "true") config.TryGetValue("UsarDatosDeBancadasOficiales", out var usarDatosOficialesValue);
if (usarDatosOficialesValue != "true")
{ {
// Si la opción está desactivada, devolvemos un array vacío. // Si el modo oficial no está activo, SIEMPRE devolvemos un array vacío.
return Ok(new List<object>()); return Ok(new List<object>());
} }
// Si el modo oficial SÍ está activo, devolvemos los detalles.
var bancadasConOcupantes = await _dbContext.Bancadas var bancadasConOcupantes = await _dbContext.Bancadas
.AsNoTracking() .AsNoTracking()
.Include(b => b.Ocupante) .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 })
.Select(b => new .OrderBy(b => b.Id)
{
b.Id,
b.Camara,
b.AgrupacionPoliticaId,
Ocupante = b.Ocupante
})
.ToListAsync(); .ToListAsync();
return Ok(bancadasConOcupantes); return Ok(bancadasConOcupantes);

View File

@@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+55954e18a797dce22f76f00b645832f361d97362")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ed9a49a5373209c105168d721df4c77b6c1f329")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","GeIeP3tog3JZwKJCFe6prPm1MG/MSEFptilJTMpLZdk=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","DfeTw\u002BIdhmMK9IhKuwlSfgckGaIOiGMaYzhCKVkysII="],"CachedAssets":{},"CachedCopyCandidates":{}} {"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","XNJwPCDHDCECwfNSMw\u002B6U9bmP9Oc1zMcX0NwP0k5bF8=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","T1/vt/jpzUAMkv7\u002BVei1e0uBlnnKJZz40wzx6s2b4L0="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","GeIeP3tog3JZwKJCFe6prPm1MG/MSEFptilJTMpLZdk=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","DfeTw\u002BIdhmMK9IhKuwlSfgckGaIOiGMaYzhCKVkysII="],"CachedAssets":{},"CachedCopyCandidates":{}} {"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","XNJwPCDHDCECwfNSMw\u002B6U9bmP9Oc1zMcX0NwP0k5bF8=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","T1/vt/jpzUAMkv7\u002BVei1e0uBlnnKJZz40wzx6s2b4L0="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+55954e18a797dce22f76f00b645832f361d97362")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ed9a49a5373209c105168d721df4c77b6c1f329")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+55954e18a797dce22f76f00b645832f361d97362")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ed9a49a5373209c105168d721df4c77b6c1f329")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+55954e18a797dce22f76f00b645832f361d97362")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1ed9a49a5373209c105168d721df4c77b6c1f329")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]