| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | // src/features/legislativas/nacionales/components/MapaProvincial.tsx
 | 
					
						
							|  |  |  | import axios from 'axios'; | 
					
						
							|  |  |  | import { useEffect } from 'react'; | 
					
						
							|  |  |  | import { useSuspenseQuery } from '@tanstack/react-query'; | 
					
						
							|  |  |  | import { Geographies, Geography } from 'react-simple-maps'; | 
					
						
							|  |  |  | import { geoCentroid } from 'd3-geo'; | 
					
						
							|  |  |  | import { feature } from 'topojson-client'; | 
					
						
							|  |  |  | import { API_BASE_URL, assetBaseUrl } from '../../../../apiService'; | 
					
						
							|  |  |  | import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types'; | 
					
						
							| 
									
										
										
										
											2025-10-04 20:41:23 -03:00
										 |  |  | // 1. A diferencia de otros componentes, este no necesita importar el CSS
 | 
					
						
							|  |  |  | // porque no tiene un contenedor propio ni clases únicas.
 | 
					
						
							|  |  |  | // Heredará y usará las clases globales (:global) definidas en PanelNacional.module.css
 | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | const DEFAULT_MAP_COLOR = '#E0E0E0'; | 
					
						
							|  |  |  | const normalizarTexto = (texto: string = ''): string => texto.trim().toUpperCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); | 
					
						
							|  |  |  | type PointTuple = [number, number]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface MapaProvincialProps { | 
					
						
							|  |  |  |   eleccionId: number; | 
					
						
							|  |  |  |   categoriaId: number; | 
					
						
							|  |  |  |   distritoId: string; | 
					
						
							|  |  |  |   nombreProvincia: string; | 
					
						
							|  |  |  |   nombreMunicipioSeleccionado: string | null; | 
					
						
							|  |  |  |   nivel: 'provincia' | 'municipio'; | 
					
						
							|  |  |  |   onMunicipioSelect: (ambitoId: string, nombre: string) => void; | 
					
						
							|  |  |  |   onCalculatedCenter: (center: PointTuple, zoom: number) => void; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const MapaProvincial = ({ eleccionId, categoriaId, distritoId, nombreProvincia, nombreMunicipioSeleccionado, nivel, onMunicipioSelect, onCalculatedCenter }: MapaProvincialProps) => { | 
					
						
							|  |  |  |   const { data: mapaData = [] } = useSuspenseQuery<ResultadoMapaDto[]>({ | 
					
						
							|  |  |  |     queryKey: ['mapaResultados', eleccionId, categoriaId, distritoId], | 
					
						
							|  |  |  |     queryFn: async () => { | 
					
						
							|  |  |  |       const url = `${API_BASE_URL}/elecciones/${eleccionId}/mapa-resultados?categoriaId=${categoriaId}&distritoId=${distritoId}`; | 
					
						
							|  |  |  |       const response = await axios.get(url); | 
					
						
							|  |  |  |       return response.data; | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |   const { data: geoData } = useSuspenseQuery<any>({ | 
					
						
							|  |  |  |     queryKey: ['geoDataProvincial', nombreProvincia], | 
					
						
							|  |  |  |     queryFn: async () => { | 
					
						
							|  |  |  |       const nombreNormalizado = nombreProvincia.toLowerCase().replace(/ /g, '_'); | 
					
						
							|  |  |  |       const mapFile = `departamentos-${nombreNormalizado}.topojson`; | 
					
						
							|  |  |  |       return axios.get(`${assetBaseUrl}/maps/${mapFile}`).then(res => res.data); | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     if (nivel === 'municipio' && geoData?.objects && nombreMunicipioSeleccionado) { | 
					
						
							|  |  |  |       const geometries = geoData.objects[Object.keys(geoData.objects)[0]].geometries; | 
					
						
							|  |  |  |       const municipioGeo = geometries.find((g: any) => normalizarTexto(g.properties.departamento) === normalizarTexto(nombreMunicipioSeleccionado)); | 
					
						
							|  |  |  |       if (municipioGeo) { | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         const municipioFeature = feature(geoData, municipioGeo); | 
					
						
							|  |  |  |         const centroid = geoCentroid(municipioFeature); | 
					
						
							|  |  |  |         onCalculatedCenter(centroid as PointTuple, 40); | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }, [nivel, nombreMunicipioSeleccionado, geoData, onCalculatedCenter]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const resultadosPorNombre = new Map<string, ResultadoMapaDto>(mapaData.map(d => [normalizarTexto(d.ambitoNombre), d])); | 
					
						
							| 
									
										
										
										
											2025-10-16 15:34:12 -03:00
										 |  |  |   const esCABA = normalizarTexto(nombreProvincia) === "CAPITAL FEDERAL"; | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <Geographies geography={geoData}> | 
					
						
							|  |  |  |       {({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => { | 
					
						
							|  |  |  |         const resultado = resultadosPorNombre.get(normalizarTexto(geo.properties.departamento)); | 
					
						
							|  |  |  |         const esSeleccionado = nombreMunicipioSeleccionado ? normalizarTexto(geo.properties.departamento) === normalizarTexto(nombreMunicipioSeleccionado) : false; | 
					
						
							| 
									
										
										
										
											2025-10-04 20:41:23 -03:00
										 |  |  |          | 
					
						
							|  |  |  |         // 2. Las clases aquí NO usan el objeto 'styles' porque son clases
 | 
					
						
							|  |  |  |         // que react-simple-maps necesita globalmente. El archivo CSS
 | 
					
						
							|  |  |  |         // ya se encarga de estilizarlas usando :global(.rsm-geography), etc.
 | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |         const classNames = [ | 
					
						
							|  |  |  |           'rsm-geography', | 
					
						
							|  |  |  |           'mapa-provincial-geography', | 
					
						
							|  |  |  |           esSeleccionado ? 'selected' : '', | 
					
						
							|  |  |  |           nombreMunicipioSeleccionado && !esSeleccionado ? 'rsm-geography-faded-municipality' : '', | 
					
						
							|  |  |  |           esCABA ? 'caba-comuna-geography' : '' | 
					
						
							|  |  |  |         ].filter(Boolean).join(' '); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |         return ( | 
					
						
							|  |  |  |           <Geography | 
					
						
							|  |  |  |             key={geo.rsmKey} | 
					
						
							|  |  |  |             geography={geo} | 
					
						
							| 
									
										
										
										
											2025-09-19 17:19:10 -03:00
										 |  |  |             className={classNames} | 
					
						
							| 
									
										
										
										
											2025-09-17 11:31:17 -03:00
										 |  |  |             fill={resultado?.colorGanador || DEFAULT_MAP_COLOR} | 
					
						
							|  |  |  |             onClick={resultado ? () => onMunicipioSelect(resultado.ambitoId.toString(), resultado.ambitoNombre) : undefined} | 
					
						
							|  |  |  |             data-tooltip-id="mapa-tooltip" | 
					
						
							|  |  |  |             data-tooltip-content={geo.properties.departamento} | 
					
						
							|  |  |  |           /> | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       })} | 
					
						
							|  |  |  |     </Geographies> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }; |