| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | // src/features/legislativas/provinciales/BancasWidget.tsx (Corregido)
 | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  | import { useState, useEffect, useMemo } from 'react'; | 
					
						
							| 
									
										
										
										
											2025-09-02 19:38:04 -03:00
										 |  |  | import { useQuery } from '@tanstack/react-query'; | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  | import Select from 'react-select'; // --- CAMBIO: Importar react-select ---
 | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | import { getBancasPorSeccion, getSeccionesElectoralesConCargos } from '../../../apiService'; | 
					
						
							|  |  |  | import type { ProyeccionBancas, MunicipioSimple } from '../../../types/types'; | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  | import { Tooltip } from 'react-tooltip'; | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  | import './BancasWidget.css'; | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  | import type { Property } from 'csstype'; | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  | type CamaraType = 'diputados' | 'senadores'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // --- CAMBIO: Estilos para el nuevo selector ---
 | 
					
						
							|  |  |  | const customSelectStyles = { | 
					
						
							|  |  |  |     control: (base: any) => ({ ...base, minWidth: '200px', border: '1px solid #ced4da', boxShadow: 'none', '&:hover': { borderColor: '#86b7fe' } }), | 
					
						
							|  |  |  |     menu: (base: any) => ({ ...base, zIndex: 10 }), | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const WaffleDisplay = ({ data }: { data: ProyeccionBancas['proyeccion'] }) => { | 
					
						
							|  |  |  |     // El componente WaffleDisplay no necesita cambios en su lógica
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |         <div className="waffle-grid-container"> | 
					
						
							|  |  |  |             {data.map(partido => ( | 
					
						
							|  |  |  |                 partido.bancas > 0 && ( | 
					
						
							|  |  |  |                     <div | 
					
						
							|  |  |  |                         key={partido.agrupacionId} | 
					
						
							|  |  |  |                         className="partido-bloque" | 
					
						
							|  |  |  |                         data-tooltip-id="banca-tooltip" | 
					
						
							|  |  |  |                         data-tooltip-content={`${partido.nombreCorto || partido.agrupacionNombre}: ${partido.bancas} bancas`} | 
					
						
							|  |  |  |                     > | 
					
						
							|  |  |  |                         {Array.from({ length: partido.bancas }).map((_, index) => ( | 
					
						
							|  |  |  |                             <div | 
					
						
							|  |  |  |                                 key={index} | 
					
						
							|  |  |  |                                 className="waffle-cell" | 
					
						
							|  |  |  |                                 style={{ backgroundColor: partido.color as Property.BackgroundColor || '#cccccc' }} | 
					
						
							|  |  |  |                             /> | 
					
						
							|  |  |  |                         ))} | 
					
						
							|  |  |  |                     </div> | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             ))} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  | export const BancasWidget = () => { | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |     const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |     // --- CAMBIO: Adaptar el estado para react-select ---
 | 
					
						
							|  |  |  |     const [selectedSeccion, setSelectedSeccion] = useState<{ value: string; label: string } | null>(null); | 
					
						
							|  |  |  |     const [camaraActiva, setCamaraActiva] = useState<CamaraType>('diputados'); | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |     useEffect(() => { | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |         const fetchSecciones = async () => { | 
					
						
							|  |  |  |             try { | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |                 const seccionesData = await getSeccionesElectoralesConCargos(); | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |                 if (seccionesData && seccionesData.length > 0) { | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |                     // --- LÓGICA DE ORDENAMIENTO ---
 | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |                     const orden = new Map([ | 
					
						
							|  |  |  |                         ['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3], | 
					
						
							|  |  |  |                         ['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7] | 
					
						
							|  |  |  |                     ]); | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |                     const getOrden = (nombre: string) => { | 
					
						
							|  |  |  |                         const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/); | 
					
						
							|  |  |  |                         return match ? orden.get(match[0]) ?? 99 : 99; | 
					
						
							|  |  |  |                     }; | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |                      | 
					
						
							|  |  |  |                     // Ordenamos el array de datos ANTES de guardarlo en el estado
 | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |                     seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre)); | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |                     setSecciones(seccionesData); | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     if (!selectedSeccion) { | 
					
						
							|  |  |  |                         setSelectedSeccion({ value: seccionesData[0].id, label: seccionesData[0].nombre }); | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } catch (err) { | 
					
						
							|  |  |  |                 console.error("Error cargando secciones electorales:", err); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         fetchSecciones(); | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |     }, [selectedSeccion]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // --- CAMBIO: Formatear opciones para react-select ---
 | 
					
						
							|  |  |  |     const seccionOptions = useMemo(() => | 
					
						
							|  |  |  |         secciones.map(s => ({ value: s.id, label: s.nombre })), | 
					
						
							|  |  |  |         [secciones]); | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |     const seccionSeleccionada = useMemo(() => | 
					
						
							|  |  |  |         secciones.find(s => s.id === selectedSeccion?.value), | 
					
						
							|  |  |  |         [secciones, selectedSeccion]); | 
					
						
							| 
									
										
										
										
											2025-08-25 10:25:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |     const camarasDisponibles = useMemo(() => | 
					
						
							|  |  |  |         seccionSeleccionada?.camarasDisponibles || [], | 
					
						
							|  |  |  |         [seccionSeleccionada]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         if (seccionSeleccionada && camarasDisponibles.length > 0) { | 
					
						
							|  |  |  |             if (!camarasDisponibles.includes(camaraActiva)) { | 
					
						
							|  |  |  |                 setCamaraActiva(camarasDisponibles[0]); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [seccionSeleccionada, camarasDisponibles, camaraActiva]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const { | 
					
						
							|  |  |  |         data, | 
					
						
							|  |  |  |         isLoading, | 
					
						
							|  |  |  |         error | 
					
						
							| 
									
										
										
										
											2025-09-02 19:38:04 -03:00
										 |  |  |     } = useQuery<ProyeccionBancas, Error>({ | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |         queryKey: ['bancasPorSeccion', selectedSeccion?.value, camaraActiva], | 
					
						
							| 
									
										
										
										
											2025-10-01 11:59:15 -03:00
										 |  |  |         queryFn: () => getBancasPorSeccion(1,selectedSeccion!.value, camaraActiva), | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |         enabled: !!selectedSeccion && camarasDisponibles.includes(camaraActiva), | 
					
						
							| 
									
										
										
										
											2025-09-02 19:38:04 -03:00
										 |  |  |         retry: (failureCount, error: any) => { | 
					
						
							|  |  |  |             if (error.response?.status === 404) return false; | 
					
						
							|  |  |  |             return failureCount < 3; | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 19:38:04 -03:00
										 |  |  |     const getErrorMessage = () => { | 
					
						
							|  |  |  |         if (error) { | 
					
						
							|  |  |  |             if ((error as any).response?.status === 404) { | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |                 return `La proyección para ${camaraActiva} en esta sección aún no está disponible.`; | 
					
						
							| 
									
										
										
										
											2025-09-02 19:38:04 -03:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |             return "No se pudo conectar para obtener los datos."; | 
					
						
							| 
									
										
										
										
											2025-09-02 19:38:04 -03:00
										 |  |  |         } | 
					
						
							|  |  |  |         return null; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     const errorMessage = getErrorMessage(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |     // --- CAMBIO: Ordenar la leyenda (y por lo tanto el gráfico) de más a menos bancas ---
 | 
					
						
							|  |  |  |     const leyendaData = useMemo(() => | 
					
						
							|  |  |  |         data?.proyeccion | 
					
						
							|  |  |  |             .filter(p => p.bancas > 0) | 
					
						
							|  |  |  |             .sort((a, b) => b.bancas - a.bancas) // Ordena de mayor a menor
 | 
					
						
							|  |  |  |         || [], | 
					
						
							|  |  |  |         [data]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const totalBancasEnJuego = useMemo(() => | 
					
						
							|  |  |  |         data?.proyeccion.reduce((sum, p) => sum + p.bancas, 0) || 0, | 
					
						
							|  |  |  |         [data]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |     return ( | 
					
						
							|  |  |  |         <div className="bancas-widget-container"> | 
					
						
							|  |  |  |             <div className="bancas-header"> | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |                 <h4>Bancas Proyectadas: {totalBancasEnJuego}</h4> | 
					
						
							|  |  |  |                 <Select | 
					
						
							|  |  |  |                     options={seccionOptions} | 
					
						
							|  |  |  |                     value={selectedSeccion} | 
					
						
							|  |  |  |                     onChange={(option) => setSelectedSeccion(option)} | 
					
						
							|  |  |  |                     isLoading={secciones.length === 0} | 
					
						
							|  |  |  |                     placeholder="Seleccionar..." | 
					
						
							|  |  |  |                     styles={customSelectStyles} | 
					
						
							|  |  |  |                     isSearchable={false} | 
					
						
							|  |  |  |                 /> | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |             </div> | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |             <div className="chamber-tabs-bancas"> | 
					
						
							|  |  |  |                 <button | 
					
						
							|  |  |  |                     className={camaraActiva === 'diputados' ? 'active' : ''} | 
					
						
							|  |  |  |                     onClick={() => setCamaraActiva('diputados')} | 
					
						
							|  |  |  |                     disabled={seccionSeleccionada && !camarasDisponibles.includes('diputados')} | 
					
						
							|  |  |  |                 > | 
					
						
							|  |  |  |                     Diputados | 
					
						
							|  |  |  |                 </button> | 
					
						
							|  |  |  |                 <button | 
					
						
							|  |  |  |                     className={camaraActiva === 'senadores' ? 'active' : ''} | 
					
						
							|  |  |  |                     onClick={() => setCamaraActiva('senadores')} | 
					
						
							|  |  |  |                     disabled={seccionSeleccionada && !camarasDisponibles.includes('senadores')} | 
					
						
							|  |  |  |                 > | 
					
						
							|  |  |  |                     Senadores | 
					
						
							|  |  |  |                 </button> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <div className="bancas-content-container"> | 
					
						
							|  |  |  |                 <div className="waffle-chart-container"> | 
					
						
							|  |  |  |                     {isLoading ? <p className="loading-text">Cargando...</p> : | 
					
						
							|  |  |  |                         errorMessage ? <p className="error-text">{errorMessage}</p> : | 
					
						
							|  |  |  |                             totalBancasEnJuego > 0 ? <WaffleDisplay data={leyendaData} /> : | 
					
						
							|  |  |  |                                 <p>No hay bancas proyectadas para mostrar.</p> | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |                 <div className="leyenda-container"> | 
					
						
							|  |  |  |                     <ul className="partido-lista-bancas"> | 
					
						
							|  |  |  |                         {leyendaData.map(partido => ( | 
					
						
							|  |  |  |                             <li key={partido.agrupacionId}> | 
					
						
							|  |  |  |                                 <span className="partido-color-box" style={{ backgroundColor: partido.color as Property.BackgroundColor || '#cccccc' }}></span> | 
					
						
							|  |  |  |                                 <span className="partido-nombre"> | 
					
						
							|  |  |  |                                     {partido.nombreCorto || partido.agrupacionNombre} | 
					
						
							|  |  |  |                                 </span> | 
					
						
							|  |  |  |                                 <strong className="partido-bancas">{partido.bancas}</strong> | 
					
						
							|  |  |  |                             </li> | 
					
						
							|  |  |  |                         ))} | 
					
						
							|  |  |  |                     </ul> | 
					
						
							|  |  |  |                 </div> | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |             </div> | 
					
						
							| 
									
										
										
										
											2025-09-04 11:27:12 -03:00
										 |  |  |             <Tooltip id="banca-tooltip" /> | 
					
						
							| 
									
										
										
										
											2025-08-22 21:55:03 -03:00
										 |  |  |         </div> | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2025-08-15 17:31:51 -03:00
										 |  |  | }; |