// src/features/legislativas/provinciales/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'; import { getDetalleSeccion, API_BASE_URL, assetBaseUrl } from '../../../apiService'; import { type ResultadoDetalleSeccion } from '../../../apiService'; 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 = { '1': 'I', '2': 'II', '3': 'III', '4': 'IV', '5': 'V', '6': 'VI', '7': 'VII', '8': 'VIII' }; const ROMAN_TO_SECCION_ID: Record = { 'I': '1', 'II': '2', 'III': '3', 'IV': '4', 'V': '5', 'VI': '6', 'VII': '7', 'VIII': '8' }; const NOMBRES_SECCIONES: Record = { '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' }; const MIN_ZOOM = 1; const MAX_ZOOM = 5; const TRANSLATE_EXTENT: [[number, number], [number, number]] = [[-100, -1000], [1100, 800]]; const INITIAL_POSITION = { center: [-60.5, -37.2] as PointTuple, zoom: MIN_ZOOM }; // --- Componente de Detalle --- const DetalleSeccion = ({ seccion, categoriaId, onReset }: { seccion: SeccionGeography | null, categoriaId: number, onReset: () => void }) => { const seccionId = seccion ? ROMAN_TO_SECCION_ID[seccion.properties.seccion] : null; const { data: resultadosDetalle, isLoading, error } = useQuery({ queryKey: ['detalleSeccion', seccionId, categoriaId], queryFn: () => getDetalleSeccion(1,seccionId!, categoriaId), enabled: !!seccionId, }); if (!seccion) { return (

Resultados por Sección

Haga clic en una sección del mapa para ver los resultados detallados.

); } if (isLoading) return (

Cargando resultados de la sección...

); if (error) return
Error al cargar los datos de la sección.
; const nombreSeccionLegible = NOMBRES_SECCIONES[seccion.properties.seccion] || "Sección Desconocida"; return (

{nombreSeccionLegible}

    {resultadosDetalle?.map((r) => (
  • {r.nombre} {r.votos.toLocaleString('es-AR')} ({r.porcentaje.toFixed(2)}%)
  • ))}
); }; // --- Componente de Controles del Mapa --- const ControlesMapa = ({ onReset }: { onReset: () => void }) => (
); // --- Componente Principal --- const MapaBsAsSecciones = () => { const [position, setPosition] = useState(INITIAL_POSITION); const [selectedCategoriaId, setSelectedCategoriaId] = useState(6); const [clickedSeccion, setClickedSeccion] = useState(null); const [tooltipContent, setTooltipContent] = useState(''); const [isPanning, setIsPanning] = useState(false); const { data: geoData, isLoading: isLoadingGeo } = useQuery({ queryKey: ['mapaGeoDataSecciones'], queryFn: async () => (await axios.get(`${assetBaseUrl}/secciones-electorales-pba.topojson`)).data, }); const { data: resultadosData, isLoading: isLoadingResultados } = useQuery({ queryKey: ['mapaResultadosPorSeccion', selectedCategoriaId], queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/mapa-por-seccion?categoriaId=${selectedCategoriaId}`)).data, }); const { data: agrupacionesData, isLoading: isLoadingAgrupaciones } = useQuery({ queryKey: ['catalogoAgrupaciones'], queryFn: async () => (await axios.get(`${API_BASE_URL}/Catalogos/agrupaciones`)).data, }); const { nombresAgrupaciones, resultadosPorSeccionRomana } = useMemo<{ nombresAgrupaciones: Map; resultadosPorSeccionRomana: Map; }>(( ) => { const nombresMap = new Map(); const resultadosMap = new Map(); 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]); const isLoading = isLoadingGeo || isLoadingResultados || isLoadingAgrupaciones; const handleReset = useCallback(() => { setClickedSeccion(null); setPosition(INITIAL_POSITION); }, []); const handleGeographyClick = useCallback((geo: SeccionGeography) => { if (clickedSeccion?.rsmKey === geo.rsmKey) { handleReset(); } else { const centroid = geoCentroid(geo as any) as PointTuple; setPosition({ center: centroid, zoom: 2 }); setClickedSeccion(geo); } }, [clickedSeccion, handleReset]); 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 }); }; 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) })); } }; return (
{isLoading ?
: ( { setIsPanning(false); handleMoveEnd(newPosition); }} minZoom={MIN_ZOOM} maxZoom={MAX_ZOOM} translateExtent={TRANSLATE_EXTENT} className={isPanning ? 'panning' : ''} onMoveStart={() => setIsPanning(true)} filterZoomEvent={(e: WheelEvent) => { if (e.deltaY > 0) { handleReset(); } else if (e.deltaY < 0) { handleZoomIn(); } return true; }} > {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 ( handleGeographyClick(geo) : undefined} onMouseEnter={() => { if (isClickable) { const nombreSeccionLegible = NOMBRES_SECCIONES[geo.properties.seccion] || "Sección Desconocida"; setTooltipContent(`${nombreSeccionLegible}: ${nombreGanador}`); } }} onMouseLeave={() => setTooltipContent("")} className={`rsm-geography ${isSelected ? 'selected' : ''} ${isFaded ? 'faded' : ''} ${!isClickable ? 'no-results' : ''}`} fill={getSectionFillColor(seccionRomana)} /> ); }) } )} )} {clickedSeccion && }
); }; // --- Sub-componente para la Leyenda (sin cambios) --- const LegendSecciones = ({ resultados, nombresAgrupaciones }: { resultados: Map, nombresAgrupaciones: Map }) => { const legendItems = useMemo(() => { const ganadoresUnicos = new Map(); 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 }); } }); return Array.from(ganadoresUnicos.values()); }, [resultados, nombresAgrupaciones]); return (

Leyenda de Ganadores

{legendItems.map(item => (
{item.nombre}
))}
); }; export default MapaBsAsSecciones;