-
CONCEJALES - LA PLATA
+
CONCEJALES POR SECCIÓN ELECTORAL
diff --git a/Elecciones-Web/frontend/src/components/MapaBsAs.css b/Elecciones-Web/frontend/src/components/MapaBsAs.css
index 2826a1d..8b365dd 100644
--- a/Elecciones-Web/frontend/src/components/MapaBsAs.css
+++ b/Elecciones-Web/frontend/src/components/MapaBsAs.css
@@ -76,7 +76,7 @@
min-height: 0;
background-color: var(--background-panel-color);
border-radius: 8px;
- padding: 1.5rem;
+ padding: 1rem;
border: none;
}
@@ -194,4 +194,52 @@
/* Un tamaño de fuente legible en móviles. */
font-size: 1em;
}
+}
+
+/* --- ESTILOS PARA EL SELECTOR DE CATEGORÍA --- */
+.mapa-categoria-selector {
+ display: flex;
+ margin-bottom: 1.5rem;
+}
+
+.mapa-categoria-combobox {
+ width: 100%;
+ padding: 0.75rem 1rem;
+ font-size: 1em;
+ font-weight: 500;
+ color: var(--text-color);
+ background-color: #f8f9fa;
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ cursor: pointer;
+ appearance: none;
+ background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%230073e6%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.4-12.9z%22%2F%3E%3C%2Fsvg%3E');
+ background-repeat: no-repeat;
+ background-position: right 1rem center;
+ background-size: 0.8em;
+ transition: all 0.2s ease-in-out;
+}
+
+.mapa-categoria-combobox:hover {
+ border-color: var(--primary-accent-color);
+ background-color: #e9ecef;
+}
+
+.mapa-categoria-combobox:focus {
+ outline: none;
+ border-color: var(--primary-accent-color);
+ box-shadow: 0 0 0 2px rgba(0, 115, 230, 0.25);
+}
+
+/* --- ESTILOS PARA SECCIONES NO CLICLEABLES --- */
+.rsm-geography.no-results {
+ pointer-events: none; /* Ignora todos los eventos del ratón (click, hover, etc.) */
+ cursor: default; /* Muestra el cursor por defecto en lugar de la mano */
+}
+
+/* Opcional pero recomendado: modificar la regla :hover para que no afecte a las secciones no clicleables */
+.rsm-geography:not(.no-results):hover {
+ stroke: var(--primary-accent-color);
+ stroke-width: 1.5px;
+ filter: brightness(1.05);
}
\ No newline at end of file
diff --git a/Elecciones-Web/frontend/src/components/MapaBsAs.tsx b/Elecciones-Web/frontend/src/components/MapaBsAs.tsx
index b40f372..ab55eac 100644
--- a/Elecciones-Web/frontend/src/components/MapaBsAs.tsx
+++ b/Elecciones-Web/frontend/src/components/MapaBsAs.tsx
@@ -1,6 +1,5 @@
// src/components/MapaBsAs.tsx
import { useState, useMemo, useCallback, useEffect } from 'react';
-import type { MouseEvent } from 'react';
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
import { Tooltip } from 'react-tooltip';
import { useQuery } from '@tanstack/react-query';
@@ -24,7 +23,7 @@ interface ResultadoDetalladoMunicipio {
ultimaActualizacion: string;
porcentajeEscrutado: number;
porcentajeParticipacion: number;
- resultados: { nombre: string; votos: number; porcentaje: number }[];
+ resultados: { id: string; nombre: string; votos: number; porcentaje: number }[];
votosAdicionales: { enBlanco: number; nulos: number; recurridos: number };
}
@@ -33,11 +32,13 @@ interface Agrupacion {
nombre: string;
}
+interface Categoria {
+ id: number;
+ nombre: string;
+}
+
interface PartidoProperties {
- id: string;
departamento: string;
- cabecera: string;
- provincia: string;
}
type PartidoGeography = Feature
& { rsmKey: string };
@@ -50,52 +51,57 @@ const TRANSLATE_EXTENT: [[number, number], [number, number]] = [[-100, -600], [1
const INITIAL_POSITION = { center: [-60.5, -37.2] as PointTuple, zoom: MIN_ZOOM };
const DEFAULT_MAP_COLOR = '#E0E0E0';
+const CATEGORIAS: Categoria[] = [
+ { id: 5, nombre: 'Senadores' },
+ { id: 6, nombre: 'Diputados' },
+ { id: 7, nombre: 'Concejales' }
+];
+
// --- Componente Principal ---
const MapaBsAs = () => {
const [position, setPosition] = useState(INITIAL_POSITION);
const [selectedAmbitoId, setSelectedAmbitoId] = useState(null);
+ const [selectedCategoriaId, setSelectedCategoriaId] = useState(6);
+ const [tooltipContent, setTooltipContent] = useState('');
const { data: resultadosData, isLoading: isLoadingResultados } = useQuery({
- queryKey: ['mapaResultados'],
- queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/mapa`)).data,
+ queryKey: ['mapaResultadosPorMunicipio', selectedCategoriaId],
+ queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/mapa-por-municipio?categoriaId=${selectedCategoriaId}`)).data,
});
+
const { data: geoData, isLoading: isLoadingGeo } = useQuery({
queryKey: ['mapaGeoData'],
queryFn: async () => (await axios.get('/partidos-bsas.topojson')).data,
});
+
const { data: agrupacionesData, isLoading: isLoadingAgrupaciones } = useQuery({
queryKey: ['catalogoAgrupaciones'],
queryFn: async () => (await axios.get(`${API_BASE_URL}/Catalogos/agrupaciones`)).data,
});
- // --- SU SOLUCIÓN CORRECTA INTEGRADA ---
const { nombresAgrupaciones, resultadosPorDepartamento } = useMemo<{
nombresAgrupaciones: Map;
resultadosPorDepartamento: Map;
}>(() => {
const nombresMap = new Map();
const resultadosMap = new Map();
-
if (agrupacionesData) {
agrupacionesData.forEach((agrupacion) => {
nombresMap.set(agrupacion.id, agrupacion.nombre);
});
}
-
if (resultadosData) {
- resultadosData.forEach(r => resultadosMap.set(r.departamentoNombre.toUpperCase(), r));
+ resultadosData.forEach(r => {
+ if (r.departamentoNombre) {
+ resultadosMap.set(r.departamentoNombre.toUpperCase(), r)
+ }
+ });
}
-
- return {
- nombresAgrupaciones: nombresMap,
- resultadosPorDepartamento: resultadosMap
- };
+ return { nombresAgrupaciones: nombresMap, resultadosPorDepartamento: resultadosMap };
}, [agrupacionesData, resultadosData]);
const isLoading = isLoadingResultados || isLoadingAgrupaciones || isLoadingGeo;
- // ... (el resto del componente no necesita cambios)
-
const handleReset = useCallback(() => {
setSelectedAmbitoId(null);
setPosition(INITIAL_POSITION);
@@ -142,75 +148,104 @@ const MapaBsAs = () => {
const getPartyFillColor = (departamentoNombre: string) => {
const resultado = resultadosPorDepartamento.get(departamentoNombre.toUpperCase());
- if (!resultado || !resultado.colorGanador) {
- return DEFAULT_MAP_COLOR;
- }
- return resultado.colorGanador;
+ return resultado?.colorGanador || DEFAULT_MAP_COLOR;
};
- const handleMouseEnter = (e: MouseEvent) => {
- const path = e.target as SVGPathElement;
- if (path.parentNode) {
- path.parentNode.appendChild(path);
- }
- };
+ // --- Helper de Renderizado ---
+ const renderGeography = (geo: PartidoGeography, isSelectedGeo: boolean = false) => {
+ const departamentoNombre = geo.properties.departamento.toUpperCase();
+ const resultado = resultadosPorDepartamento.get(departamentoNombre);
+ const isClickable = !!resultado;
+ const isSelected = isSelectedGeo || (selectedAmbitoId !== null && selectedAmbitoId === resultado?.ambitoId);
+ const isFaded = !isSelectedGeo && selectedAmbitoId !== null && !isSelected;
+ const nombreAgrupacionGanadora = resultado ? nombresAgrupaciones.get(resultado.agrupacionGanadoraId) : 'Sin datos';
- if (isLoading) return Cargando datos del mapa...
;
+ return (
+ handleGeographyClick(geo) : undefined}
+ onMouseEnter={() => setTooltipContent(`${geo.properties.departamento}: ${nombreAgrupacionGanadora}`)}
+ onMouseLeave={() => setTooltipContent("")}
+ />
+ );
+ };
return (
-
- {
- if (e.deltaY > 0) {
- handleReset();
- } else if (e.deltaY < 0) {
- handleZoomIn();
- }
- return true;
- }}
+ {isLoading ? : (
+
- {geoData && (
-
- {({ geographies }: { geographies: PartidoGeography[] }) =>
- geographies.map((geo) => {
- const departamentoNombre = geo.properties.departamento.toUpperCase();
- const resultado = resultadosPorDepartamento.get(departamentoNombre);
- const isSelected = resultado ? selectedAmbitoId === resultado.ambitoId : false;
- const isFaded = selectedAmbitoId !== null && !isSelected;
- const nombreAgrupacionGanadora = resultado ? nombresAgrupaciones.get(resultado.agrupacionGanadoraId) : 'Sin datos';
+ {
+ if (e.deltaY > 0) {
+ handleReset();
+ } else if (e.deltaY < 0) {
+ handleZoomIn();
+ }
+ return true;
+ }}
+ >
+ {geoData && (
+
+ {({ geographies }: { geographies: PartidoGeography[] }) => {
+ const selectedGeo = selectedAmbitoId
+ ? geographies.find(geo => {
+ const resultado = resultadosPorDepartamento.get(geo.properties.departamento.toUpperCase());
+ return resultado?.ambitoId === selectedAmbitoId;
+ })
+ : null;
return (
- handleGeographyClick(geo)}
- onMouseEnter={handleMouseEnter}
- />
+ <>
+ {geographies.map(geo => (!selectedGeo || geo.rsmKey !== selectedGeo.rsmKey) ? renderGeography(geo) : null)}
+ {selectedGeo && renderGeography(selectedGeo, true)}
+ >
);
- })
- }
-
- )}
-
-
-
+ }}
+
+ )}
+
+
+ )}
+
{selectedAmbitoId !== null &&
}
-
+
+
+
+
@@ -224,10 +259,10 @@ const ControlesMapa = ({ onReset }: { onReset: () => void }) => (
);
-const DetalleMunicipio = ({ ambitoId, onReset }: { ambitoId: number | null; onReset: () => void }) => {
+const DetalleMunicipio = ({ ambitoId, onReset, categoriaId }: { ambitoId: number | null; onReset: () => void; categoriaId: number; }) => {
const { data, isLoading, error } = useQuery