| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | // src/components/MapaBsAsSecciones.tsx
 | 
					
						
							|  |  |  | import { useState, useMemo, useCallback, useEffect } from 'react'; | 
					
						
							|  |  |  | import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; | 
					
						
							|  |  |  | import { Tooltip } from 'react-tooltip'; | 
					
						
							|  |  |  | import { useQuery } from '@tanstack/react-query'; | 
					
						
							|  |  |  | import axios from 'axios'; | 
					
						
							|  |  |  | import { geoCentroid } from 'd3-geo'; | 
					
						
							| 
									
										
										
										
											2025-09-04 17:19:54 -03:00
										 |  |  | import { getDetalleSeccion, API_BASE_URL, assetBaseUrl } from '../apiService'; | 
					
						
							|  |  |  | import { type ResultadoDetalleSeccion } from '../apiService'; | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | import './MapaBsAs.css'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // --- Interfaces y Tipos ---
 | 
					
						
							|  |  |  | type PointTuple = [number, number]; | 
					
						
							|  |  |  | interface ResultadoMapaSeccion { | 
					
						
							|  |  |  |   seccionId: string; | 
					
						
							|  |  |  |   seccionNombre: string; | 
					
						
							|  |  |  |   agrupacionGanadoraId: string | null; | 
					
						
							|  |  |  |   colorGanador: string | null; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | interface Agrupacion { id: string; nombre: string; } | 
					
						
							|  |  |  | interface Categoria { id: number; nombre: string; } | 
					
						
							|  |  |  | type SeccionGeography = { | 
					
						
							|  |  |  |   rsmKey: string; | 
					
						
							|  |  |  |   properties: { seccion: string; fna: string; }; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // --- Constantes ---
 | 
					
						
							|  |  |  | const DEFAULT_MAP_COLOR = '#E0E0E0'; | 
					
						
							|  |  |  | const CATEGORIAS: Categoria[] = [{ id: 5, nombre: 'Senadores' }, { id: 6, nombre: 'Diputados' }]; | 
					
						
							|  |  |  | const SECCION_ID_TO_ROMAN: Record<string, string> = { '1': 'I', '2': 'II', '3': 'III', '4': 'IV', '5': 'V', '6': 'VI', '7': 'VII', '8': 'VIII' }; | 
					
						
							|  |  |  | const ROMAN_TO_SECCION_ID: Record<string, string> = { 'I': '1', 'II': '2', 'III': '3', 'IV': '4', 'V': '5', 'VI': '6', 'VII': '7', 'VIII': '8' }; | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:17 -03:00
										 |  |  | const NOMBRES_SECCIONES: Record<string, string> = { | 
					
						
							|  |  |  |     'I': 'Sección Primera', 'II': 'Sección Segunda', 'III': 'Sección Tercera', 'IV': 'Sección Cuarta', | 
					
						
							|  |  |  |     'V': 'Sección Quinta', 'VI': 'Sección Sexta', 'VII': 'Sección Séptima', 'VIII': 'Sección Capital' | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | const MIN_ZOOM = 1; | 
					
						
							|  |  |  | const MAX_ZOOM = 5; | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  | const TRANSLATE_EXTENT: [[number, number], [number, number]] = [[-100, -1000], [1100, 800]]; | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | const INITIAL_POSITION = { center: [-60.5, -37.2] as PointTuple, zoom: MIN_ZOOM }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:17 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | // --- Componente de Detalle ---
 | 
					
						
							|  |  |  | const DetalleSeccion = ({ seccion, categoriaId, onReset }: { seccion: SeccionGeography | null, categoriaId: number, onReset: () => void }) => { | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const seccionId = seccion ? ROMAN_TO_SECCION_ID[seccion.properties.seccion] : null; | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const { data: resultadosDetalle, isLoading, error } = useQuery<ResultadoDetalleSeccion[]>({ | 
					
						
							|  |  |  |         queryKey: ['detalleSeccion', seccionId, categoriaId], | 
					
						
							|  |  |  |         queryFn: () => getDetalleSeccion(seccionId!, categoriaId), | 
					
						
							|  |  |  |         enabled: !!seccionId, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     if (!seccion) { | 
					
						
							|  |  |  |         return ( | 
					
						
							|  |  |  |         <div className="detalle-placeholder"> | 
					
						
							|  |  |  |             <h3>Resultados por Sección</h3> | 
					
						
							|  |  |  |             <p>Haga clic en una sección del mapa para ver los resultados detallados.</p> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (isLoading) return (<div className="detalle-loading"><div className="spinner"></div><p>Cargando resultados de la sección...</p></div>); | 
					
						
							|  |  |  |     if (error) return <div className="detalle-error">Error al cargar los datos de la sección.</div>; | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const nombreSeccionLegible = NOMBRES_SECCIONES[seccion.properties.seccion] || "Sección Desconocida"; | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     return ( | 
					
						
							|  |  |  |         <div className="detalle-content"> | 
					
						
							|  |  |  |         <button className="reset-button-panel" onClick={onReset}>← VOLVER</button> | 
					
						
							|  |  |  |         <h3>{nombreSeccionLegible}</h3> | 
					
						
							|  |  |  |         <ul className="resultados-lista"> | 
					
						
							|  |  |  |             {resultadosDetalle?.map((r) => ( | 
					
						
							|  |  |  |             <li key={r.id}> | 
					
						
							|  |  |  |                 <div className="resultado-info"> | 
					
						
							|  |  |  |                 <span className="partido-nombre">{r.nombre}</span> | 
					
						
							|  |  |  |                 <span className="partido-votos">{r.votos.toLocaleString('es-AR')} ({r.porcentaje.toFixed(2)}%)</span> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |                 <div className="progress-bar"> | 
					
						
							|  |  |  |                 <div className="progress-fill" style={{ width: `${r.porcentaje}%`, backgroundColor: r.color || DEFAULT_MAP_COLOR }}></div> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |             </li> | 
					
						
							|  |  |  |             ))} | 
					
						
							|  |  |  |         </ul> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // --- Componente de Controles del Mapa ---
 | 
					
						
							|  |  |  | const ControlesMapa = ({ onReset }: { onReset: () => void }) => ( | 
					
						
							|  |  |  |   <div className="map-controls"> | 
					
						
							|  |  |  |     <button onClick={onReset}>← VOLVER</button> | 
					
						
							|  |  |  |   </div> | 
					
						
							|  |  |  | ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // --- Componente Principal ---
 | 
					
						
							|  |  |  | const MapaBsAsSecciones = () => { | 
					
						
							|  |  |  |   const [position, setPosition] = useState(INITIAL_POSITION); | 
					
						
							|  |  |  |   const [selectedCategoriaId, setSelectedCategoriaId] = useState<number>(6); | 
					
						
							|  |  |  |   const [clickedSeccion, setClickedSeccion] = useState<SeccionGeography | null>(null); | 
					
						
							|  |  |  |   const [tooltipContent, setTooltipContent] = useState(''); | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |   const [isPanning, setIsPanning] = useState(false); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const { data: geoData, isLoading: isLoadingGeo } = useQuery<any>({ | 
					
						
							|  |  |  |         queryKey: ['mapaGeoDataSecciones'], | 
					
						
							| 
									
										
										
										
											2025-09-04 17:19:54 -03:00
										 |  |  |         queryFn: async () => (await axios.get(`${assetBaseUrl}/secciones-electorales-pba.topojson`)).data, | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const { data: resultadosData, isLoading: isLoadingResultados } = useQuery<ResultadoMapaSeccion[]>({ | 
					
						
							|  |  |  |         queryKey: ['mapaResultadosPorSeccion', selectedCategoriaId], | 
					
						
							|  |  |  |         queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/mapa-por-seccion?categoriaId=${selectedCategoriaId}`)).data, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const { data: agrupacionesData, isLoading: isLoadingAgrupaciones } = useQuery<Agrupacion[]>({ | 
					
						
							|  |  |  |         queryKey: ['catalogoAgrupaciones'], | 
					
						
							|  |  |  |         queryFn: async () => (await axios.get(`${API_BASE_URL}/Catalogos/agrupaciones`)).data, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const { nombresAgrupaciones, resultadosPorSeccionRomana } = useMemo<{ | 
					
						
							|  |  |  |         nombresAgrupaciones: Map<string, string>; | 
					
						
							|  |  |  |         resultadosPorSeccionRomana: Map<string, ResultadoMapaSeccion>; | 
					
						
							|  |  |  |     }>(( | 
					
						
							|  |  |  |     ) => { | 
					
						
							|  |  |  |         const nombresMap = new Map<string, string>(); | 
					
						
							|  |  |  |         const resultadosMap = new Map<string, ResultadoMapaSeccion>(); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |         if (agrupacionesData) { | 
					
						
							|  |  |  |         agrupacionesData.forEach(a => nombresMap.set(a.id, a.nombre)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (resultadosData) { | 
					
						
							|  |  |  |         resultadosData.forEach(r => { | 
					
						
							|  |  |  |             const roman = SECCION_ID_TO_ROMAN[r.seccionId]; | 
					
						
							|  |  |  |             if (roman) resultadosMap.set(roman, r); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return { nombresAgrupaciones: nombresMap, resultadosPorSeccionRomana: resultadosMap }; | 
					
						
							|  |  |  |     }, [agrupacionesData, resultadosData]); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const isLoading = isLoadingGeo || isLoadingResultados || isLoadingAgrupaciones; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const handleReset = useCallback(() => { | 
					
						
							|  |  |  |         setClickedSeccion(null); | 
					
						
							|  |  |  |         setPosition(INITIAL_POSITION); | 
					
						
							|  |  |  |     }, []); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const handleGeographyClick = useCallback((geo: SeccionGeography) => { | 
					
						
							|  |  |  |         if (clickedSeccion?.rsmKey === geo.rsmKey) { | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |         handleReset(); | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |         } else { | 
					
						
							|  |  |  |         const centroid = geoCentroid(geo as any) as PointTuple; | 
					
						
							|  |  |  |         setPosition({ center: centroid, zoom: 2 }); | 
					
						
							|  |  |  |         setClickedSeccion(geo); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [clickedSeccion, handleReset]); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const handleMoveEnd = (newPosition: { coordinates: PointTuple; zoom: number }) => { | 
					
						
							|  |  |  |         if (newPosition.zoom <= MIN_ZOOM) { | 
					
						
							|  |  |  |         if (position.zoom > MIN_ZOOM || clickedSeccion !== null) { | 
					
						
							|  |  |  |             handleReset(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (newPosition.zoom < position.zoom && clickedSeccion !== null) { | 
					
						
							|  |  |  |         setClickedSeccion(null); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         setPosition({ center: newPosition.coordinates, zoom: newPosition.zoom }); | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         const handleKeyDown = (e: KeyboardEvent) => e.key === 'Escape' && handleReset(); | 
					
						
							|  |  |  |         window.addEventListener('keydown', handleKeyDown); | 
					
						
							|  |  |  |         return () => window.removeEventListener('keydown', handleKeyDown); | 
					
						
							|  |  |  |     }, [handleReset]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const getSectionFillColor = (seccionRomana: string) => { | 
					
						
							|  |  |  |         return resultadosPorSeccionRomana.get(seccionRomana)?.colorGanador || DEFAULT_MAP_COLOR; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const handleZoomIn = () => { | 
					
						
							|  |  |  |         if (position.zoom < MAX_ZOOM) { | 
					
						
							|  |  |  |         setPosition(p => ({ ...p, zoom: Math.min(p.zoom * 1.5, MAX_ZOOM) })); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <div className="mapa-wrapper"> | 
					
						
							|  |  |  |       <div className="mapa-container"> | 
					
						
							|  |  |  |         {isLoading ? <div className="spinner"></div> : ( | 
					
						
							|  |  |  |           <ComposableMap | 
					
						
							|  |  |  |             key={selectedCategoriaId} | 
					
						
							|  |  |  |             projection="geoMercator" | 
					
						
							|  |  |  |             projectionConfig={{ scale: 4400, center: [-60.5, -37.2] }} | 
					
						
							|  |  |  |             className="rsm-svg" | 
					
						
							|  |  |  |             data-tooltip-id="seccion-tooltip" | 
					
						
							|  |  |  |           > | 
					
						
							|  |  |  |             <ZoomableGroup | 
					
						
							|  |  |  |               center={position.center} | 
					
						
							|  |  |  |               zoom={position.zoom} | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |               onMoveEnd={(newPosition: { coordinates: PointTuple; zoom: number }) => { | 
					
						
							|  |  |  |                 setIsPanning(false); | 
					
						
							|  |  |  |                 handleMoveEnd(newPosition); | 
					
						
							|  |  |  |               }} | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |               minZoom={MIN_ZOOM} | 
					
						
							|  |  |  |               maxZoom={MAX_ZOOM} | 
					
						
							|  |  |  |               translateExtent={TRANSLATE_EXTENT} | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |               className={isPanning ? 'panning' : ''} | 
					
						
							|  |  |  |               onMoveStart={() => setIsPanning(true)} | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |               filterZoomEvent={(e: WheelEvent) => { | 
					
						
							|  |  |  |                 if (e.deltaY > 0) { | 
					
						
							|  |  |  |                   handleReset(); | 
					
						
							|  |  |  |                 } else if (e.deltaY < 0) { | 
					
						
							|  |  |  |                   handleZoomIn(); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return true; | 
					
						
							|  |  |  |               }} | 
					
						
							|  |  |  |             > | 
					
						
							|  |  |  |               {geoData && ( | 
					
						
							|  |  |  |                 <Geographies geography={geoData}> | 
					
						
							|  |  |  |                   {({ geographies }: { geographies: SeccionGeography[] }) => | 
					
						
							|  |  |  |                     geographies.map((geo) => { | 
					
						
							|  |  |  |                       const seccionRomana = geo.properties.seccion; | 
					
						
							|  |  |  |                       const resultado = resultadosPorSeccionRomana.get(seccionRomana); | 
					
						
							|  |  |  |                       const nombreGanador = resultado?.agrupacionGanadoraId ? nombresAgrupaciones.get(resultado.agrupacionGanadoraId) : 'Sin datos'; | 
					
						
							|  |  |  |                       const isSelected = clickedSeccion?.rsmKey === geo.rsmKey; | 
					
						
							|  |  |  |                       const isFaded = clickedSeccion && !isSelected; | 
					
						
							|  |  |  |                       const isClickable = !!resultado; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                       return ( | 
					
						
							|  |  |  |                         <Geography | 
					
						
							|  |  |  |                           key={geo.rsmKey + (isSelected ? '-selected' : '')} | 
					
						
							|  |  |  |                           geography={geo as any} | 
					
						
							|  |  |  |                           data-tooltip-id="seccion-tooltip" | 
					
						
							|  |  |  |                           onClick={isClickable ? () => handleGeographyClick(geo) : undefined} | 
					
						
							|  |  |  |                           onMouseEnter={() => { | 
					
						
							|  |  |  |                             if (isClickable) { | 
					
						
							| 
									
										
										
										
											2025-09-02 15:39:17 -03:00
										 |  |  |                               const nombreSeccionLegible = NOMBRES_SECCIONES[geo.properties.seccion] || "Sección Desconocida"; | 
					
						
							|  |  |  |                               setTooltipContent(`${nombreSeccionLegible}: ${nombreGanador}`); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |                             } | 
					
						
							|  |  |  |                           }} | 
					
						
							|  |  |  |                           onMouseLeave={() => setTooltipContent("")} | 
					
						
							|  |  |  |                           className={`rsm-geography ${isSelected ? 'selected' : ''} ${isFaded ? 'faded' : ''} ${!isClickable ? 'no-results' : ''}`} | 
					
						
							|  |  |  |                           fill={getSectionFillColor(seccionRomana)} | 
					
						
							|  |  |  |                         /> | 
					
						
							|  |  |  |                       ); | 
					
						
							|  |  |  |                     }) | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |                 </Geographies> | 
					
						
							|  |  |  |               )} | 
					
						
							|  |  |  |             </ZoomableGroup> | 
					
						
							|  |  |  |           </ComposableMap> | 
					
						
							|  |  |  |         )} | 
					
						
							|  |  |  |         {clickedSeccion && <ControlesMapa onReset={handleReset} />} | 
					
						
							|  |  |  |         <Tooltip id="seccion-tooltip" content={tooltipContent} /> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |       <div className="info-panel"> | 
					
						
							|  |  |  |         <div className="mapa-categoria-selector"> | 
					
						
							|  |  |  |           <select | 
					
						
							|  |  |  |             className="mapa-categoria-combobox" | 
					
						
							|  |  |  |             value={selectedCategoriaId} | 
					
						
							|  |  |  |             onChange={(e) => { | 
					
						
							|  |  |  |               setSelectedCategoriaId(Number(e.target.value)); | 
					
						
							|  |  |  |               handleReset(); | 
					
						
							|  |  |  |             }} | 
					
						
							|  |  |  |           > | 
					
						
							|  |  |  |             {CATEGORIAS.map(cat => ( | 
					
						
							|  |  |  |               <option key={cat.id} value={cat.id}> | 
					
						
							|  |  |  |                 {cat.nombre} | 
					
						
							|  |  |  |               </option> | 
					
						
							|  |  |  |             ))} | 
					
						
							|  |  |  |           </select> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         <DetalleSeccion seccion={clickedSeccion} categoriaId={selectedCategoriaId} onReset={handleReset} /> | 
					
						
							|  |  |  |         <LegendSecciones resultados={resultadosPorSeccionRomana} nombresAgrupaciones={nombresAgrupaciones} /> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  | // --- Sub-componente para la Leyenda (sin cambios) ---
 | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | const LegendSecciones = ({ resultados, nombresAgrupaciones }: { resultados: Map<string, ResultadoMapaSeccion>, nombresAgrupaciones: Map<string, string> }) => { | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     const legendItems = useMemo(() => { | 
					
						
							|  |  |  |         const ganadoresUnicos = new Map<string, { nombre: string; color: string }>(); | 
					
						
							|  |  |  |         resultados.forEach(resultado => { | 
					
						
							|  |  |  |         if (resultado.agrupacionGanadoraId && resultado.colorGanador && !ganadoresUnicos.has(resultado.agrupacionGanadoraId)) { | 
					
						
							|  |  |  |             ganadoresUnicos.set(resultado.agrupacionGanadoraId, { | 
					
						
							|  |  |  |             nombre: nombresAgrupaciones.get(resultado.agrupacionGanadoraId) || 'Desconocido', | 
					
						
							|  |  |  |             color: resultado.colorGanador | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |         return Array.from(ganadoresUnicos.values()); | 
					
						
							|  |  |  |     }, [resultados, nombresAgrupaciones]); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     return ( | 
					
						
							|  |  |  |         <div className="legend"> | 
					
						
							| 
									
										
										
										
											2025-09-08 13:09:30 -03:00
										 |  |  |         <h4>Leyenda de Ganadores</h4> | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |         {legendItems.map(item => ( | 
					
						
							|  |  |  |             <div key={item.nombre} className="legend-item"> | 
					
						
							|  |  |  |             <div className="legend-color-box" style={{ backgroundColor: item.color }} /> | 
					
						
							|  |  |  |             <span>{item.nombre}</span> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |         ))} | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  |         </div> | 
					
						
							| 
									
										
										
										
											2025-09-02 20:34:49 -03:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2025-09-02 09:48:46 -03:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default MapaBsAsSecciones; |