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

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

View File

@@ -17,6 +17,8 @@ export const CongresoWidget = () => {
const { data: composicionData, isLoading: isLoadingComposicion, error: errorComposicion } = useQuery<ComposicionData>({
queryKey: ['composicionCongreso'],
queryFn: getComposicionCongreso,
// Vuelve a buscar los datos cada 20 segundos
refetchInterval: 20000,
});
const { data: bancadasDetalle = [] } = useQuery<BancadaDetalle[]>({
@@ -29,28 +31,41 @@ export const CongresoWidget = () => {
// --- LÓGICA DE SEATFILLDATA ---
const seatFillData = useMemo(() => {
if (!datosCamaraActual || !bancadasDetalle.length) return [];
if (!datosCamaraActual) return [];
const camaraId = camaraActiva === 'diputados' ? 0 : 1;
const bancadasDeCamara = bancadasDetalle.filter(b => b.camara === camaraId);
// --- LÓGICA DEL INTERRUPTOR ---
// 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
const colorMap = new Map<string, string>();
datosCamaraActual.partidos.forEach(p => {
if (p.id && p.color) {
colorMap.set(p.id, p.color);
}
});
if (modoOficialActivo) {
// --- MODO OFICIAL: Construir desde las bancas físicas ---
const camaraId = camaraActiva === 'diputados' ? 0 : 1;
const bancadasDeCamara = bancadasDetalle.filter(b => b.camara === camaraId);
// 1. Aseguramos que la lista de bancas esté en orden físico (por su ID).
const bancadasOrdenadas = bancadasDeCamara.sort((a, b) => a.id - b.id);
const colorMap = new Map<string, string>();
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.
// El índice del array corresponderá al asiento visual.
return bancadasOrdenadas.map(bancada => ({
color: bancada.agrupacionPoliticaId ? colorMap.get(bancada.agrupacionPoliticaId) || DEFAULT_COLOR : DEFAULT_COLOR,
ocupante: bancada.ocupante
}));
const bancadasOrdenadas = bancadasDeCamara.sort((a, b) => a.id - b.id);
return bancadasOrdenadas.map(bancada => ({
color: bancada.agrupacionPoliticaId ? colorMap.get(bancada.agrupacionPoliticaId) || DEFAULT_COLOR : DEFAULT_COLOR,
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]);

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.
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);
if (lastSeatIndex !== -1) {
// Eliminamos ese asiento de la lista de asientos electorales.
finalSeatData.splice(lastSeatIndex, 1);
}
}
const renderedElements = seatElements.map((child, index) => {
// El asiento presidencial sigue siendo un caso especial
if (index === PRESIDENTE_SEAT_INDEX) {
// --- CASO ESPECIAL: ASIENTO PRESIDENCIAL ---
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, {
fill: presidenteBancada?.color || '#A9A9A9',
stroke: '#000000',
strokeWidth: 2,
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,
});
}
// 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 (
<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.
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);
if (lastSeatIndex !== -1) {
// Eliminamos ese asiento de la lista de asientos electorales.
finalSeatData.splice(lastSeatIndex, 1);
}
}
const renderedElements = seatElements.map((child, index) => {
// El asiento presidencial sigue siendo un caso especial
// --- CASO ESPECIAL: ASIENTO PRESIDENCIAL ---
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>`
});
}
// 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];
// --- 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 });