// src/features/legislativas/nacionales/components/MapaNacional.tsx import axios from 'axios'; import { Suspense, useState, useEffect, useCallback, useRef } from 'react'; import { useSuspenseQuery } from '@tanstack/react-query'; import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; import { Tooltip } from 'react-tooltip'; import { geoCentroid } from 'd3-geo'; import { feature } from 'topojson-client'; import { API_BASE_URL, assetBaseUrl } from '../../../../apiService'; import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types'; import { MapaProvincial } from './MapaProvincial'; import { CabaLupa } from './CabaLupa'; import { BiZoomIn, BiZoomOut } from "react-icons/bi"; import toast from 'react-hot-toast'; const DEFAULT_MAP_COLOR = '#E0E0E0'; const FADED_BACKGROUND_COLOR = '#F0F0F0'; const normalizarTexto = (texto: string = '') => texto.trim().toUpperCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); type PointTuple = [number, number]; const PROVINCE_VIEW_CONFIG: Record = { "BUENOS AIRES": { center: [-60.5, -37.3], zoom: 5 }, "SANTA CRUZ": { center: [-69.5, -49.3], zoom: 5 }, "CIUDAD AUTONOMA DE BUENOS AIRES": { center: [-58.45, -34.6], zoom: 85 }, "CHUBUT": { center: [-68.5, -44.5], zoom: 5.5 }, "SANTA FE": { center: [-61, -31.2], zoom: 6 }, "CORRIENTES": { center: [-58, -29], zoom: 7 }, "RIO NEGRO": { center: [-67.5, -40], zoom: 5.5 }, "TIERRA DEL FUEGO": { center: [-66.5, -54.2], zoom: 7 }, }; const LUPA_SIZE_RATIO = 0.2; const MIN_LUPA_SIZE_PX = 100; const MAX_LUPA_SIZE_PX = 180; interface MapaNacionalProps { eleccionId: number; categoriaId: number; nivel: 'pais' | 'provincia' | 'municipio'; nombreAmbito: string; nombreProvinciaActiva: string | undefined | null; provinciaDistritoId: string | null; onAmbitoSelect: (ambitoId: string, nivel: 'provincia' | 'municipio', nombre: string) => void; onVolver: () => void; isMobileView: boolean; } // --- CONFIGURACIONES DEL MAPA --- const desktopProjectionConfig = { scale: 700, center: [-65, -40] as [number, number] }; const mobileProjectionConfig = { scale: 1100, center: [-64, -41] as [number, number] }; export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nombreProvinciaActiva, provinciaDistritoId, onAmbitoSelect, onVolver, isMobileView }: MapaNacionalProps) => { const [position, setPosition] = useState({ zoom: isMobileView ? 1.5 : 1.05, // 1.5 para móvil, 1.05 para desktop center: [-65, -40] as PointTuple }); const [isPanning, setIsPanning] = useState(false); const initialProvincePositionRef = useRef<{ zoom: number, center: PointTuple } | null>(null); const containerRef = useRef(null); const lupaRef = useRef(null); const cabaPathRef = useRef(null); const isAnimatingRef = useRef(false); const initialLoadRef = useRef(true); const [lupaStyle, setLupaStyle] = useState({ opacity: 0 }); const { data: mapaDataNacional } = useSuspenseQuery({ queryKey: ['mapaResultados', eleccionId, categoriaId, null], queryFn: async () => { const url = `${API_BASE_URL}/elecciones/${eleccionId}/mapa-resultados?categoriaId=${categoriaId}`; const response = await axios.get(url); return response.data; }, }); const { data: geoDataNacional } = useSuspenseQuery({ queryKey: ['geoDataNacional'], queryFn: () => axios.get(`${assetBaseUrl}/maps/argentina-provincias.topojson`).then(res => res.data), }); useEffect(() => { if (nivel === 'pais') { setPosition({ zoom: isMobileView ? 1.4 : 1.05, center: [-65, -40] }); initialProvincePositionRef.current = null; } else if (nivel === 'provincia') { const nombreNormalizado = normalizarTexto(nombreAmbito); const manualConfig = PROVINCE_VIEW_CONFIG[nombreNormalizado]; let provinceConfig = { zoom: 7, center: [-65, -40] as PointTuple }; if (manualConfig) { provinceConfig = manualConfig; } else { const provinciaGeo = geoDataNacional.objects.provincias.geometries.find((g: any) => normalizarTexto(g.properties.nombre) === nombreNormalizado); if (provinciaGeo) { const provinciaFeature = feature(geoDataNacional, provinciaGeo); const centroid = geoCentroid(provinciaFeature); provinceConfig = { zoom: 7, center: centroid as PointTuple }; } } setPosition(provinceConfig); initialProvincePositionRef.current = provinceConfig; } }, [nivel, nombreAmbito, geoDataNacional, isMobileView]); const resultadosNacionalesPorNombre = new Map(mapaDataNacional.map(d => [normalizarTexto(d.ambitoNombre), d])); const nombreMunicipioSeleccionado = nivel === 'municipio' ? nombreAmbito : null; const handleCalculatedCenter = useCallback((center: PointTuple, zoom: number) => { setPosition({ center, zoom }); }, []); useEffect(() => { const updateLupaPosition = () => { if (nivel === 'pais' && cabaPathRef.current && containerRef.current) { const containerRect = containerRef.current.getBoundingClientRect(); if (containerRect.width === 0) return; const cabaRect = cabaPathRef.current.getBoundingClientRect(); const cabaCenterX = (cabaRect.left - containerRect.left) + cabaRect.width / 2; const cabaCenterY = (cabaRect.top - containerRect.top) + cabaRect.height / 2; const calculatedSize = containerRect.width * LUPA_SIZE_RATIO; const newLupaSize = Math.max(MIN_LUPA_SIZE_PX, Math.min(calculatedSize, MAX_LUPA_SIZE_PX)); const horizontalOffset = newLupaSize * 0.5; const verticalOffset = newLupaSize * 0.2; setLupaStyle({ position: 'absolute', top: `${cabaCenterY - verticalOffset}px`, left: `${cabaCenterX + horizontalOffset}px`, width: `${newLupaSize}px`, opacity: 1, }); } else { setLupaStyle({ opacity: 0, pointerEvents: 'none' }); } }; isAnimatingRef.current = true; const handleResize = () => { if (!isAnimatingRef.current) updateLupaPosition(); }; const resizeObserver = new ResizeObserver(handleResize); if (containerRef.current) resizeObserver.observe(containerRef.current); let timerId: NodeJS.Timeout; if (initialLoadRef.current && nivel === 'pais') { timerId = setTimeout(() => { updateLupaPosition(); isAnimatingRef.current = false; }, 0); initialLoadRef.current = false; } else { timerId = setTimeout(() => { updateLupaPosition(); isAnimatingRef.current = false; }, 800); } return () => { if (containerRef.current) resizeObserver.unobserve(containerRef.current); clearTimeout(timerId); isAnimatingRef.current = false; }; }, [position, nivel]); const panEnabled = nivel === 'provincia' && initialProvincePositionRef.current !== null && position.zoom > initialProvincePositionRef.current.zoom && !nombreMunicipioSeleccionado; // --- INICIO DE LA CORRECCIÓN --- const handleZoomIn = () => { // Solo mostramos la notificación si el paneo NO está ya habilitado if (!panEnabled && initialProvincePositionRef.current) { // Calculamos cuál será el nuevo nivel de zoom const newZoom = position.zoom * 1.8; // Si el nuevo zoom supera el umbral inicial, activamos la notificación if (newZoom > initialProvincePositionRef.current.zoom) { toast.success('Desplazamiento Habilitado', { icon: '🖐️', style: { background: '#32e5f1ff', color: 'white' }, duration: 1000, }); } } setPosition(prev => ({ ...prev, zoom: Math.min(prev.zoom * 1.8, 100) })); }; const handleZoomOut = () => { // Solo mostramos la notificación si el paneo SÍ está habilitado actualmente if (panEnabled && initialProvincePositionRef.current) { const newZoom = position.zoom / 1.8; // Si el nuevo zoom es igual o menor al umbral, desactivamos if (newZoom <= initialProvincePositionRef.current.zoom) { toast.error('Desplazamiento Deshabilitado', { icon: '🔒', style: { background: '#32e5f1ff', color: 'white' }, duration: 1000, }); } } // La lógica para actualizar la posición no cambia setPosition(prev => { const newZoom = Math.max(prev.zoom / 1.8, 1); const initialPos = initialProvincePositionRef.current; if (initialPos && newZoom <= initialPos.zoom) return initialPos; return { ...prev, zoom: newZoom }; }); }; const handleMoveEnd = (newPosition: { coordinates: PointTuple, zoom: number }) => { setPosition(prev => ({ ...prev, center: newPosition.coordinates })); setIsPanning(false); }; const filterInteractionEvents = (event: any) => { if (event.sourceEvent && event.sourceEvent.type === 'wheel') return false; return panEnabled; }; const showZoomControls = nivel === 'provincia'; const isZoomOutDisabled = (nivel === 'provincia' && initialProvincePositionRef.current && position.zoom <= initialProvincePositionRef.current.zoom) || (nivel === 'pais' && position.zoom <= (isMobileView ? 1.4 : 1.05)); const mapContainerClasses = panEnabled ? 'mapa-componente-container map-pannable' : 'mapa-componente-container map-locked'; return (
{showZoomControls && (
)} {nivel !== 'pais' && }
setIsPanning(true)} onMoveEnd={handleMoveEnd} filterZoomEvent={filterInteractionEvents} className={isPanning ? 'panning' : ''} > {({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => { const nombreNormalizado = normalizarTexto(geo.properties.nombre); const esCABA = nombreNormalizado === 'CIUDAD AUTONOMA DE BUENOS AIRES'; const resultado = resultadosNacionalesPorNombre.get(nombreNormalizado); const esProvinciaActiva = provinciaDistritoId && resultado?.ambitoId === provinciaDistritoId; return ( !esCABA && resultado && onAmbitoSelect(resultado.ambitoId, 'provincia', resultado.ambitoNombre)} data-tooltip-id="mapa-tooltip" data-tooltip-content={geo.properties.nombre} /> ); })} {provinciaDistritoId && nombreProvinciaActiva && ( onAmbitoSelect(ambitoId, 'municipio', nombre)} onCalculatedCenter={handleCalculatedCenter} nivel={nivel as 'provincia' | 'municipio'} /> )}
{nivel === 'pais' && (
{(() => { const resultadoCABA = resultadosNacionalesPorNombre.get("CIUDAD AUTONOMA DE BUENOS AIRES"); const fillColor = resultadoCABA?.colorGanador || DEFAULT_MAP_COLOR; const handleClick = () => { if (resultadoCABA) { onAmbitoSelect(resultadoCABA.ambitoId, 'provincia', resultadoCABA.ambitoNombre); } }; return ; })()}
)}
); };