Fix Boostrap y Try Cache
This commit is contained in:
@@ -1,24 +1,33 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Añadimos una ubicación específica para los archivos .mjs
|
||||
location ~ \.mjs$ {
|
||||
# Incluimos los tipos MIME por defecto para que Nginx pueda inferir otros tipos si es necesario
|
||||
include /etc/nginx/mime.types;
|
||||
# Forzamos explícitamente el tipo de contenido para esta ubicación
|
||||
default_type application/javascript;
|
||||
# --- NUEVO BLOQUE ESPECÍFICO PARA BOOTSTRAP.JS ---
|
||||
location = /bootstrap.js {
|
||||
# Esta cabecera le dice a los proxies que deben revalidar el archivo
|
||||
# con el servidor de origen antes de servirlo desde la caché.
|
||||
add_header Cache-Control "no-cache, must-revalidate";
|
||||
|
||||
# 'expires off' es otra capa de seguridad para evitar que se cachee
|
||||
expires off;
|
||||
|
||||
# Intenta servir el archivo. Si no existe, devuelve 404.
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# Bloque para otros activos estáticos (con hash) que SÍ pueden ser cacheados agresivamente
|
||||
location ~* \.(?:js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
||||
# Estos archivos cambian de nombre en cada build, así que pueden
|
||||
# ser cacheados por mucho tiempo sin riesgo.
|
||||
expires 1y;
|
||||
add_header Cache-Control "public";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# Bloque para la SPA
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|svg|woff|woff2)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
}
|
||||
25
Elecciones-Web/frontend/package-lock.json
generated
25
Elecciones-Web/frontend/package-lock.json
generated
@@ -21,7 +21,8 @@
|
||||
"react-pdf": "^10.1.0",
|
||||
"react-select": "^5.10.2",
|
||||
"react-simple-maps": "github:ozimmortal/react-simple-maps#feat/react-19-support",
|
||||
"react-tooltip": "^5.29.1"
|
||||
"react-tooltip": "^5.29.1",
|
||||
"topojson-client": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
@@ -29,6 +30,7 @@
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/react-select": "^5.0.0",
|
||||
"@types/topojson-client": "^3.1.5",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
@@ -2215,6 +2217,27 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/topojson-client": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/topojson-client/-/topojson-client-3.1.5.tgz",
|
||||
"integrity": "sha512-C79rySTyPxnQNNguTZNI1Ct4D7IXgvyAs3p9HPecnl6mNrJ5+UhvGNYcZfpROYV2lMHI48kJPxwR+F9C6c7nmw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*",
|
||||
"@types/topojson-specification": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/topojson-specification": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/topojson-specification/-/topojson-specification-1.0.5.tgz",
|
||||
"integrity": "sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz",
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"react-pdf": "^10.1.0",
|
||||
"react-select": "^5.10.2",
|
||||
"react-simple-maps": "github:ozimmortal/react-simple-maps#feat/react-19-support",
|
||||
"react-tooltip": "^5.29.1"
|
||||
"react-tooltip": "^5.29.1",
|
||||
"topojson-client": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
@@ -31,6 +32,7 @@
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/react-select": "^5.0.0",
|
||||
"@types/topojson-client": "^3.1.5",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
|
||||
13
Elecciones-Web/frontend/public/bootstrap.js
vendored
13
Elecciones-Web/frontend/public/bootstrap.js
vendored
@@ -63,12 +63,15 @@
|
||||
}
|
||||
|
||||
// 5. Una vez cargado, llamar a la función de renderizado
|
||||
// 5. Una vez cargado, llamar a la función de renderizado
|
||||
console.log('Bootstrap: JS principal cargado. Buscando EleccionesWidgets.render...');
|
||||
|
||||
if (window.EleccionesWidgets && typeof window.EleccionesWidgets.render === 'function') {
|
||||
console.log('Bootstrap: La función render() FUE ENCONTRADA. Se procederá a llamarla.');
|
||||
window.EleccionesWidgets.render();
|
||||
console.log('Bootstrap: La función render existe. Llamando ahora.');
|
||||
// Encontramos los contenedores aquí y pasamos sus props.
|
||||
const widgetContainers = document.querySelectorAll('[data-elecciones-widget]');
|
||||
widgetContainers.forEach(container => {
|
||||
// 'dataset' es un objeto que contiene todos los atributos data-*
|
||||
// container.dataset = { eleccionesWidget: 'mapa-municipios', focoMunicipio: 'LA PLATA' }
|
||||
window.EleccionesWidgets.render(container, container.dataset);
|
||||
});
|
||||
} else {
|
||||
console.error('Bootstrap: ERROR CRÍTICO - La función render() NO SE ENCONTRÓ en window.EleccionesWidgets.');
|
||||
console.log('Bootstrap: Contenido de window.EleccionesWidgets:', window.EleccionesWidgets);
|
||||
|
||||
@@ -5,19 +5,6 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
|
||||
@@ -27,24 +27,60 @@ export const DevApp = () => {
|
||||
Showcase de Widgets - Elecciones 2025
|
||||
</h1>
|
||||
<main>
|
||||
<DipSenTickerWidget />
|
||||
<ResumenGeneralWidget />
|
||||
<DipSenTickerWidget />
|
||||
<ResumenGeneralWidget />
|
||||
<SenadoresWidget />
|
||||
<DiputadosWidget />
|
||||
<ConcejalesWidget />
|
||||
<SenadoresTickerWidget />
|
||||
<DiputadosTickerWidget />
|
||||
<ConcejalesTickerWidget />
|
||||
<DiputadosPorSeccionWidget />
|
||||
<SenadoresPorSeccionWidget />
|
||||
<ConcejalesPorSeccionWidget />
|
||||
<CongresoWidget />
|
||||
<BancasWidget />
|
||||
<MapaBsAs />
|
||||
<MapaBsAsSecciones />
|
||||
<TelegramaWidget />
|
||||
<ResultadosTablaDetalladaWidget />
|
||||
<ResultadosRankingMunicipioWidget />
|
||||
<SenadoresTickerWidget />
|
||||
<DiputadosTickerWidget />
|
||||
<ConcejalesTickerWidget />
|
||||
<DiputadosPorSeccionWidget />
|
||||
<SenadoresPorSeccionWidget />
|
||||
<ConcejalesPorSeccionWidget />
|
||||
<CongresoWidget />
|
||||
<BancasWidget />
|
||||
<MapaBsAs />
|
||||
<MapaBsAsSecciones />
|
||||
<TelegramaWidget />
|
||||
<ResultadosTablaDetalladaWidget />
|
||||
<ResultadosRankingMunicipioWidget />
|
||||
|
||||
<hr className="border-gray-300 my-8" />
|
||||
|
||||
<h2>Mapa - Vista General (Por Defecto)</h2>
|
||||
<p>Carga la vista provincial completa para Diputados.</p>
|
||||
<MapaBsAs />
|
||||
|
||||
<hr className="border-gray-300 my-8" />
|
||||
|
||||
<h2>Mapa - Foco en La Plata (Diputados por defecto)</h2>
|
||||
<p>Carga el mapa y automáticamente hace zoom en La Plata.</p>
|
||||
<MapaBsAs focoMunicipio="LA PLATA" />
|
||||
|
||||
<hr className="border-gray-300 my-8" />
|
||||
|
||||
<h2>Mapa - Foco en Campana</h2>
|
||||
<p>Carga el mapa y automáticamente hace zoom en Campana.</p>
|
||||
<MapaBsAs focoMunicipio="CAMPANA" focoCategoria="senadores" />
|
||||
|
||||
<hr className="border-gray-300 my-8" />
|
||||
|
||||
<h2>Mapa - Vista General de Senadores</h2>
|
||||
<p>Carga la vista provincial completa para la categoría Senadores.</p>
|
||||
<MapaBsAs focoCategoria="senadores" />
|
||||
|
||||
<hr className="border-gray-300 my-8" />
|
||||
|
||||
<h2>Mapa - Foco en Bahía Blanca para Concejales</h2>
|
||||
<p>Carga el mapa enfocado en Bahía Blanca y con la categoría Concejales seleccionada.</p>
|
||||
<MapaBsAs focoMunicipio="BAHIA BLANCA" focoCategoria="concejales" />
|
||||
|
||||
<hr className="border-gray-300 my-8" />
|
||||
|
||||
<h2>Mapa - Foco Inválido (La Plata para Senadores)</h2>
|
||||
<p>Debería mostrar la vista provincial de Senadores y un warning en la consola del navegador.</p>
|
||||
<MapaBsAs focoMunicipio="LA PLATA" focoCategoria="senadores" />
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simp
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import axios from 'axios';
|
||||
import { feature } from 'topojson-client';
|
||||
import type { Feature, Geometry } from 'geojson';
|
||||
import { geoCentroid } from 'd3-geo';
|
||||
import { API_BASE_URL, assetBaseUrl } from '../apiService';
|
||||
@@ -12,6 +13,11 @@ import './MapaBsAs.css';
|
||||
// --- Interfaces y Tipos ---
|
||||
type PointTuple = [number, number];
|
||||
|
||||
interface MapaBsAsProps {
|
||||
focoMunicipio?: string;
|
||||
focoCategoria?: string;
|
||||
}
|
||||
|
||||
interface ResultadoMapa {
|
||||
ambitoId: number;
|
||||
departamentoNombre: string;
|
||||
@@ -33,15 +39,8 @@ interface Agrupacion {
|
||||
nombre: string;
|
||||
}
|
||||
|
||||
interface Categoria {
|
||||
id: number;
|
||||
nombre: string;
|
||||
}
|
||||
|
||||
interface PartidoProperties {
|
||||
departamento: string;
|
||||
}
|
||||
|
||||
interface Categoria { id: number; nombre: string; }
|
||||
interface PartidoProperties { departamento: string; }
|
||||
type PartidoGeography = Feature<Geometry, PartidoProperties> & { rsmKey: string };
|
||||
|
||||
// --- Constantes ---
|
||||
@@ -52,19 +51,43 @@ 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: 5, nombre: 'Senadores' },
|
||||
{ id: 7, nombre: 'Concejales' }
|
||||
];
|
||||
|
||||
// --- Helper de Normalización ---
|
||||
const normalizarTexto = (texto: string = ''): string => {
|
||||
return texto
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.normalize("NFD") // Separa los acentos de las letras
|
||||
.replace(/[\u0300-\u036f]/g, ""); // Elimina los acentos
|
||||
};
|
||||
|
||||
// --- Componente Principal ---
|
||||
const MapaBsAs = () => {
|
||||
const MapaBsAs = ({ focoMunicipio, focoCategoria }: MapaBsAsProps) => {
|
||||
// --- LÓGICA DE ESTADO SIMPLIFICADA ---
|
||||
const categoriaInicial = useMemo(() => {
|
||||
const catNorm = focoCategoria?.toLowerCase();
|
||||
if (catNorm === 'senadores') return 5;
|
||||
if (catNorm === 'concejales') return 7;
|
||||
return 6;
|
||||
}, [focoCategoria]);
|
||||
|
||||
const [position, setPosition] = useState(INITIAL_POSITION);
|
||||
const [selectedAmbitoId, setSelectedAmbitoId] = useState<number | null>(null);
|
||||
const [selectedCategoriaId, setSelectedCategoriaId] = useState<number>(6);
|
||||
const [selectedCategoriaId, setSelectedCategoriaId] = useState<number>(categoriaInicial);
|
||||
const [tooltipContent, setTooltipContent] = useState('');
|
||||
const [isPanning, setIsPanning] = useState(false);
|
||||
|
||||
// Sincroniza el estado si la prop cambia. Esto es para cuando el widget ya está montado
|
||||
// y recibe nuevas props (no ocurrirá en tu caso actual, pero es buena práctica).
|
||||
useEffect(() => {
|
||||
setSelectedCategoriaId(categoriaInicial);
|
||||
}, [categoriaInicial]);
|
||||
|
||||
// --- QUERIES ---
|
||||
const { data: resultadosData, isLoading: isLoadingResultados } = useQuery<ResultadoMapa[]>({
|
||||
queryKey: ['mapaResultadosPorMunicipio', selectedCategoriaId],
|
||||
queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/mapa-por-municipio?categoriaId=${selectedCategoriaId}`)).data,
|
||||
@@ -80,21 +103,16 @@ const MapaBsAs = () => {
|
||||
queryFn: async () => (await axios.get(`${API_BASE_URL}/Catalogos/agrupaciones`)).data,
|
||||
});
|
||||
|
||||
const { nombresAgrupaciones, resultadosPorDepartamento } = useMemo<{
|
||||
nombresAgrupaciones: Map<string, string>;
|
||||
resultadosPorDepartamento: Map<string, ResultadoMapa>;
|
||||
}>(() => {
|
||||
const { nombresAgrupaciones, resultadosPorDepartamento } = useMemo(() => {
|
||||
const nombresMap = new Map<string, string>();
|
||||
const resultadosMap = new Map<string, ResultadoMapa>();
|
||||
if (agrupacionesData) {
|
||||
agrupacionesData.forEach((agrupacion) => {
|
||||
nombresMap.set(agrupacion.id, agrupacion.nombre);
|
||||
});
|
||||
agrupacionesData.forEach((a) => nombresMap.set(a.id, a.nombre));
|
||||
}
|
||||
if (resultadosData) {
|
||||
resultadosData.forEach(r => {
|
||||
if (r.departamentoNombre) {
|
||||
resultadosMap.set(r.departamentoNombre.toUpperCase(), r)
|
||||
resultadosMap.set(normalizarTexto(r.departamentoNombre), r);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -108,20 +126,105 @@ const MapaBsAs = () => {
|
||||
setPosition(INITIAL_POSITION);
|
||||
}, []);
|
||||
|
||||
// --- LÓGICA DE CLIC Y FOCO ---
|
||||
const handleGeographyClick = useCallback((geo: PartidoGeography) => {
|
||||
const departamentoNombre = geo.properties.departamento.toUpperCase();
|
||||
const resultado = resultadosPorDepartamento.get(departamentoNombre);
|
||||
const departamentoNombreNormalizado = normalizarTexto(geo.properties.departamento);
|
||||
const resultado = resultadosPorDepartamento.get(departamentoNombreNormalizado);
|
||||
if (!resultado) return;
|
||||
const ambitoIdParaSeleccionar = resultado.ambitoId;
|
||||
if (selectedAmbitoId === ambitoIdParaSeleccionar) {
|
||||
|
||||
if (selectedAmbitoId === resultado.ambitoId) {
|
||||
handleReset();
|
||||
} else {
|
||||
const centroid = geoCentroid(geo) as PointTuple;
|
||||
setPosition({ center: centroid, zoom: 5 });
|
||||
setSelectedAmbitoId(ambitoIdParaSeleccionar);
|
||||
setSelectedAmbitoId(resultado.ambitoId);
|
||||
}
|
||||
}, [selectedAmbitoId, handleReset, resultadosPorDepartamento]);
|
||||
|
||||
// --- useEffect DE INICIALIZACIÓN (Ligeramente refinado) ---
|
||||
useEffect(() => {
|
||||
if (isLoading || !focoMunicipio || selectedAmbitoId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const geometries = geoData?.objects?.['departamentos-buenos_aires']?.geometries;
|
||||
if (!geometries) return;
|
||||
|
||||
const nombreFocoNormalizado = normalizarTexto(focoMunicipio);
|
||||
const geoTargetTopo = geometries.find(
|
||||
(g: any) => normalizarTexto(g.properties.departamento) === nombreFocoNormalizado
|
||||
);
|
||||
|
||||
if (geoTargetTopo) {
|
||||
if (resultadosPorDepartamento.has(nombreFocoNormalizado)) {
|
||||
const geoTargetGeoJSON = feature(geoData, geoTargetTopo);
|
||||
handleGeographyClick(geoTargetGeoJSON as unknown as PartidoGeography);
|
||||
}
|
||||
}
|
||||
// Deshabilitamos la regla para que solo se ejecute cuando isLoading cambia a false.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (categoriaInicial !== selectedCategoriaId) {
|
||||
setSelectedCategoriaId(categoriaInicial);
|
||||
handleReset();
|
||||
}
|
||||
}, [categoriaInicial, selectedCategoriaId, handleReset]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => e.key === 'Escape' && handleReset();
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleReset]);
|
||||
|
||||
const renderGeography = useCallback((geo: PartidoGeography, isSelectedGeo: boolean = false) => {
|
||||
const departamentoNombreNormalizado = normalizarTexto(geo.properties.departamento);
|
||||
const resultado = resultadosPorDepartamento.get(departamentoNombreNormalizado);
|
||||
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';
|
||||
|
||||
return (
|
||||
<Geography
|
||||
key={geo.rsmKey + (isSelectedGeo ? '-selected' : '')}
|
||||
geography={geo}
|
||||
className={`rsm-geography ${isSelected ? 'selected' : ''} ${isFaded ? 'faded' : ''} ${!isClickable ? 'no-results' : ''}`}
|
||||
fill={resultado?.colorGanador || DEFAULT_MAP_COLOR}
|
||||
onClick={isClickable ? () => handleGeographyClick(geo) : undefined}
|
||||
onMouseEnter={() => {
|
||||
if (isClickable) {
|
||||
setTooltipContent(`${geo.properties.departamento}: ${nombreAgrupacionGanadora}`);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => setTooltipContent("")}
|
||||
/>
|
||||
);
|
||||
}, [resultadosPorDepartamento, selectedAmbitoId, nombresAgrupaciones, handleGeographyClick]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading || !focoMunicipio || selectedAmbitoId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const geometries = geoData?.objects?.['departamentos-buenos_aires']?.geometries;
|
||||
if (!geometries) return;
|
||||
|
||||
const nombreFocoNormalizado = normalizarTexto(focoMunicipio);
|
||||
const geoTargetTopo = geometries.find(
|
||||
(g: any) => normalizarTexto(g.properties.departamento) === nombreFocoNormalizado
|
||||
);
|
||||
|
||||
if (geoTargetTopo && resultadosPorDepartamento.has(nombreFocoNormalizado)) {
|
||||
const geoTargetGeoJSON = feature(geoData, geoTargetTopo);
|
||||
handleGeographyClick(geoTargetGeoJSON as unknown as PartidoGeography);
|
||||
}
|
||||
}, [isLoading, focoMunicipio, selectedCategoriaId]);
|
||||
|
||||
const handleMoveEnd = (newPosition: { coordinates: PointTuple; zoom: number }) => {
|
||||
if (newPosition.zoom <= MIN_ZOOM) {
|
||||
if (position.zoom > MIN_ZOOM || selectedAmbitoId !== null) {
|
||||
@@ -147,35 +250,6 @@ const MapaBsAs = () => {
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleReset]);
|
||||
|
||||
const getPartyFillColor = (departamentoNombre: string) => {
|
||||
const resultado = resultadosPorDepartamento.get(departamentoNombre.toUpperCase());
|
||||
return resultado?.colorGanador || DEFAULT_MAP_COLOR;
|
||||
};
|
||||
|
||||
// --- 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';
|
||||
|
||||
return (
|
||||
<Geography
|
||||
key={geo.rsmKey + (isSelectedGeo ? '-selected' : '')}
|
||||
geography={geo}
|
||||
data-tooltip-id="partido-tooltip"
|
||||
data-tooltip-content={`${geo.properties.departamento}: ${nombreAgrupacionGanadora}`}
|
||||
className={`rsm-geography ${isSelected ? 'selected' : ''} ${isFaded ? 'faded' : ''} ${!isClickable ? 'no-results' : ''}`}
|
||||
fill={getPartyFillColor(geo.properties.departamento)}
|
||||
onClick={isClickable ? () => handleGeographyClick(geo) : undefined}
|
||||
onMouseEnter={() => setTooltipContent(`${geo.properties.departamento}: ${nombreAgrupacionGanadora}`)}
|
||||
onMouseLeave={() => setTooltipContent("")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mapa-wrapper">
|
||||
<div className="mapa-container">
|
||||
@@ -213,7 +287,7 @@ const MapaBsAs = () => {
|
||||
{({ geographies }: { geographies: PartidoGeography[] }) => {
|
||||
const selectedGeo = selectedAmbitoId
|
||||
? geographies.find(geo => {
|
||||
const resultado = resultadosPorDepartamento.get(geo.properties.departamento.toUpperCase());
|
||||
const resultado = resultadosPorDepartamento.get(normalizarTexto(geo.properties.departamento));
|
||||
return resultado?.ambitoId === selectedAmbitoId;
|
||||
})
|
||||
: null;
|
||||
@@ -239,14 +313,15 @@ const MapaBsAs = () => {
|
||||
className="mapa-categoria-combobox"
|
||||
value={selectedCategoriaId}
|
||||
onChange={(e) => {
|
||||
// --- LÓGICA DE CAMBIO DE CATEGORÍA ---
|
||||
// Limpiamos el foco de municipio al cambiar de categoría
|
||||
setSelectedAmbitoId(null);
|
||||
setPosition(INITIAL_POSITION);
|
||||
setSelectedCategoriaId(Number(e.target.value));
|
||||
handleReset();
|
||||
}}
|
||||
>
|
||||
{CATEGORIAS.map(cat => (
|
||||
<option key={cat.id} value={cat.id}>
|
||||
{cat.nombre}
|
||||
</option>
|
||||
<option key={cat.id} value={cat.id}>{cat.nombre}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -259,6 +334,7 @@ const MapaBsAs = () => {
|
||||
|
||||
// --- Sub-componentes ---
|
||||
const ControlesMapa = ({ onReset }: { onReset: () => void }) => (
|
||||
|
||||
<div className="map-controls">
|
||||
<button onClick={onReset}>← VOLVER</button>
|
||||
</div>
|
||||
@@ -299,10 +375,10 @@ const DetalleMunicipio = ({ ambitoId, onReset, categoriaId }: { ambitoId: number
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</li>
|
||||
</li >
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</ul >
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
@@ -321,6 +397,7 @@ const Legend = ({ resultados, nombresAgrupaciones }: { resultados: Map<string, R
|
||||
});
|
||||
|
||||
return Array.from(ganadoresUnicos.values());
|
||||
|
||||
}, [resultados, nombresAgrupaciones]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -63,32 +63,29 @@ if (import.meta.env.DEV) {
|
||||
} else {
|
||||
// --- MODO PRODUCCIÓN ---
|
||||
// Exponemos la función de renderizado para el bootstrap.js
|
||||
const renderWidgets = () => {
|
||||
const widgetContainers = document.querySelectorAll('[data-elecciones-widget]');
|
||||
// La función de renderizado acepta el contenedor y las props
|
||||
const renderWidgets = (container: HTMLElement, props: DOMStringMap) => {
|
||||
const widgetName = props.eleccionesWidget;
|
||||
|
||||
if (widgetContainers.length === 0) {
|
||||
console.warn('React: ADVERTENCIA - No se encontró ningún elemento en el DOM para renderizar un widget. Verifica que el HTML contenga <div data-elecciones-widget="...">.');
|
||||
if (widgetName && WIDGET_MAP[widgetName]) {
|
||||
const WidgetComponent = WIDGET_MAP[widgetName];
|
||||
const root = ReactDOM.createRoot(container);
|
||||
|
||||
// Pasamos todas las props (ej. { eleccionesWidget: '...', focoMunicipio: '...' })
|
||||
// al componente que se va a renderizar.
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<WidgetComponent {...props} />
|
||||
</QueryClientProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
} else {
|
||||
console.error(`React: ERROR - No se encontró un componente para el nombre de widget: "${widgetName}"`);
|
||||
}
|
||||
|
||||
widgetContainers.forEach(container => {
|
||||
const widgetName = (container as HTMLElement).dataset.eleccionesWidget;
|
||||
|
||||
if (widgetName && WIDGET_MAP[widgetName]) {
|
||||
const WidgetComponent = WIDGET_MAP[widgetName];
|
||||
const root = ReactDOM.createRoot(container);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<WidgetComponent />
|
||||
</QueryClientProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
} else {
|
||||
console.error(`React: ERROR - No se encontró un componente para el nombre de widget: "${widgetName}"`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// La función expuesta ahora se llamará por cada widget, no una sola vez.
|
||||
(window as any).EleccionesWidgets = {
|
||||
render: renderWidgets
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d5168e1d175a8b1944d6009066d13f9b99cba1d0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6309003536b4278315dbe89abb0a8fe79e3320a9")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DJ3OEGlGm0XqehqAshsJPdHddx35xQrKqTYPlJ\u002BmgK0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","VC4RrsY3drpYvDr8ENTx68n\u002BklPRBIIs4arkXFsPT3E="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","/f\u002B\u002BdIRysg7dipW05N4RtxXuPBXZZIhhi3aMiCZ\u002BB2w="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DJ3OEGlGm0XqehqAshsJPdHddx35xQrKqTYPlJ\u002BmgK0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","VC4RrsY3drpYvDr8ENTx68n\u002BklPRBIIs4arkXFsPT3E="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","/f\u002B\u002BdIRysg7dipW05N4RtxXuPBXZZIhhi3aMiCZ\u002BB2w="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d5168e1d175a8b1944d6009066d13f9b99cba1d0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6309003536b4278315dbe89abb0a8fe79e3320a9")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d5168e1d175a8b1944d6009066d13f9b99cba1d0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6309003536b4278315dbe89abb0a8fe79e3320a9")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d5168e1d175a8b1944d6009066d13f9b99cba1d0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6309003536b4278315dbe89abb0a8fe79e3320a9")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
Reference in New Issue
Block a user