Feat Widgets 1930
This commit is contained in:
		| @@ -149,7 +149,18 @@ export const getResultadosPorMunicipioYCategoria = async (municipioId: string, c | |||||||
|   return response.data.resultados; |   return response.data.resultados; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const getMunicipios = async (): Promise<MunicipioSimple[]> => { | export const getResultadosPorMunicipio = async (municipioId: string, categoriaId: number): Promise<ResultadoTicker[]> => { | ||||||
|   const response = await apiClient.get('/catalogos/municipios'); |   const response = await apiClient.get(`/resultados/partido/${municipioId}?categoriaId=${categoriaId}`); | ||||||
|   return response.data; |   // La respuesta es un objeto, nosotros extraemos el array de resultados | ||||||
|  |   return response.data.resultados; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const getMunicipios = async (categoriaId?: number): Promise<MunicipioSimple[]> => { | ||||||
|  |   let url = '/catalogos/municipios'; | ||||||
|  |   if (categoriaId) { | ||||||
|  |     url += `?categoriaId=${categoriaId}`; | ||||||
|  |   } | ||||||
|  |   const response = await apiClient.get(url); | ||||||
|  |   // La API ahora devuelve { seccionId, nombre }, lo mapeamos a { id, nombre } | ||||||
|  |   return response.data.map((m: any) => ({ id: m.seccionId, nombre: m.nombre })); | ||||||
| }; | }; | ||||||
| @@ -1,80 +1,75 @@ | |||||||
| // src/components/BancasWidget.tsx | // src/components/BancasWidget.tsx | ||||||
| import { useState, useEffect } from 'react'; | import { useState, useEffect } from 'react'; | ||||||
| // Se cambia la importación: de ResponsiveWaffle a ResponsiveBar | import { useQuery } from '@tanstack/react-query'; | ||||||
| import { ResponsiveBar } from '@nivo/bar'; | import { ResponsiveBar } from '@nivo/bar'; | ||||||
| import { getBancasPorSeccion, getSeccionesElectorales } from '../apiService'; | import { getBancasPorSeccion, getSeccionesElectorales } from '../apiService'; | ||||||
| import type { ProyeccionBancas, MunicipioSimple } from '../types/types'; | import type { ProyeccionBancas, MunicipioSimple } from '../types/types'; | ||||||
| import './BancasWidget.css'; | import './BancasWidget.css'; | ||||||
|  |  | ||||||
| // La paleta de colores se mantiene para consistencia visual |  | ||||||
| const NIVO_COLORS = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]; | const NIVO_COLORS = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]; | ||||||
|  |  | ||||||
| export const BancasWidget = () => { | export const BancasWidget = () => { | ||||||
|     const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); |     const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); | ||||||
|     const [seccionActual, setSeccionActual] = useState<string>(''); |     const [seccionActual, setSeccionActual] = useState<string>(''); | ||||||
|     const [data, setData] = useState<ProyeccionBancas | null>(null); |  | ||||||
|     const [loading, setLoading] = useState(true); |  | ||||||
|     const [error, setError] = useState<string | null>(null); |  | ||||||
|  |  | ||||||
|     // useEffect para cargar la lista de secciones una sola vez |     // useEffect para cargar la lista de secciones una sola vez (esto está bien) | ||||||
|     useEffect(() => { |     useEffect(() => { | ||||||
|         const fetchSecciones = async () => { |         const fetchSecciones = async () => { | ||||||
|             try { |             try { | ||||||
|                 const seccionesData = await getSeccionesElectorales(); |                 const seccionesData = await getSeccionesElectorales(); | ||||||
|                 if (seccionesData && seccionesData.length > 0) { |                 if (seccionesData && seccionesData.length > 0) { | ||||||
|                      |  | ||||||
|                     // --- INICIO DE LA LÓGICA DE ORDENAMIENTO --- |  | ||||||
|                     const orden = new Map([ |                     const orden = new Map([ | ||||||
|                         ['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3], |                         ['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3], | ||||||
|                         ['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7] |                         ['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7] | ||||||
|                     ]); |                     ]); | ||||||
|  |  | ||||||
|                     const getOrden = (nombre: string) => { |                     const getOrden = (nombre: string) => { | ||||||
|                         const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/); |                         const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/); | ||||||
|                         return match ? orden.get(match[0]) ?? 99 : 99; |                         return match ? orden.get(match[0]) ?? 99 : 99; | ||||||
|                     }; |                     }; | ||||||
|  |  | ||||||
|                     seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre)); |                     seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre)); | ||||||
|                     // --- FIN DE LA LÓGICA DE ORDENAMIENTO --- |  | ||||||
|  |  | ||||||
|                     setSecciones(seccionesData); |                     setSecciones(seccionesData); | ||||||
|                     setSeccionActual(seccionesData[0].id); |                     setSeccionActual(seccionesData[0].id); | ||||||
|                 } else { |  | ||||||
|                     setError("No se encontraron secciones electorales."); |  | ||||||
|                 } |                 } | ||||||
|             } catch (err) { |             } catch (err) { | ||||||
|                 console.error("Error cargando secciones electorales:", err); |                 console.error("Error cargando secciones electorales:", err); | ||||||
|                 setError("No se pudo cargar la lista de secciones."); |                 // Opcional: manejar el error en la UI si la carga de secciones falla | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|         fetchSecciones(); |         fetchSecciones(); | ||||||
|     }, []); |     }, []); | ||||||
|  |  | ||||||
|     // useEffect para cargar los datos de bancas cuando cambia la sección |     // --- CÓDIGO REFACTORIZADO --- | ||||||
|     useEffect(() => { |     // Eliminamos los useState para data, loading y error | ||||||
|         if (!seccionActual) return; |  | ||||||
|  |  | ||||||
|         const fetchData = async () => { |     // useQuery ahora es la única fuente de verdad para los datos de bancas | ||||||
|             setLoading(true); |     const {  | ||||||
|             setError(null); |         data,  | ||||||
|             try { |         isLoading,  | ||||||
|                 const result = await getBancasPorSeccion(seccionActual); |         error  | ||||||
|                 setData(result); |     } = useQuery<ProyeccionBancas, Error>({ | ||||||
|             } catch (err) { |         queryKey: ['bancasPorSeccion', seccionActual], | ||||||
|                 console.error(`Error cargando datos de bancas para sección ${seccionActual}:`, err); |         queryFn: () => getBancasPorSeccion(seccionActual), | ||||||
|                 setData(null); |         enabled: !!seccionActual, | ||||||
|                 setError("No hay datos de bancas disponibles para esta sección."); |         retry: (failureCount, error: any) => { | ||||||
|             } finally { |             if (error.response?.status === 404) return false; | ||||||
|                 setLoading(false); |             return failureCount < 3; | ||||||
|             } |         }, | ||||||
|         }; |     }); | ||||||
|         fetchData(); |  | ||||||
|     }, [seccionActual]); |  | ||||||
|  |  | ||||||
|     // Se preparan los datos para el gráfico de barras. |  | ||||||
|     // Se invierte el array para que el partido con más bancas aparezca arriba. |  | ||||||
|     const barData = data ? [...data.proyeccion].reverse() : []; |     const barData = data ? [...data.proyeccion].reverse() : []; | ||||||
|  |  | ||||||
|  |     const getErrorMessage = () => { | ||||||
|  |         if (error) { | ||||||
|  |             if ((error as any).response?.status === 404) { | ||||||
|  |                 return "La proyección de bancas para esta sección aún no está disponible."; | ||||||
|  |             } | ||||||
|  |             return "No se pudo conectar para obtener los datos de bancas."; | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const errorMessage = getErrorMessage(); | ||||||
|  |  | ||||||
|     return ( |     return ( | ||||||
|         <div className="bancas-widget-container"> |         <div className="bancas-widget-container"> | ||||||
|             <div className="bancas-header"> |             <div className="bancas-header"> | ||||||
| @@ -84,8 +79,7 @@ export const BancasWidget = () => { | |||||||
|                 </select> |                 </select> | ||||||
|             </div> |             </div> | ||||||
|             <div className="waffle-chart-container"> |             <div className="waffle-chart-container"> | ||||||
|                 {loading ? <p>Cargando...</p> : error ? <p>{error}</p> : |                 {isLoading ? <p>Cargando...</p> : errorMessage ? <p>{errorMessage}</p> : | ||||||
|                 // --- SE REEMPLAZA EL GRÁFICO WAFFLE POR EL GRÁFICO DE BARRAS --- |  | ||||||
|                 <ResponsiveBar |                 <ResponsiveBar | ||||||
|                     data={barData} |                     data={barData} | ||||||
|                     keys={['bancas']} |                     keys={['bancas']} | ||||||
| @@ -106,7 +100,6 @@ export const BancasWidget = () => { | |||||||
|                         legend: 'Cantidad de Bancas', |                         legend: 'Cantidad de Bancas', | ||||||
|                         legendPosition: 'middle', |                         legendPosition: 'middle', | ||||||
|                         legendOffset: 20, |                         legendOffset: 20, | ||||||
|                         // Asegura que los ticks del eje sean números enteros |  | ||||||
|                         format: (value) => Math.floor(value) === value ? value : '' |                         format: (value) => Math.floor(value) === value ? value : '' | ||||||
|                     }} |                     }} | ||||||
|                     axisLeft={{ |                     axisLeft={{ | ||||||
| @@ -118,7 +111,6 @@ export const BancasWidget = () => { | |||||||
|                     labelSkipHeight={12} |                     labelSkipHeight={12} | ||||||
|                     labelTextColor={{ from: 'color', modifiers: [['darker', 3]] }} |                     labelTextColor={{ from: 'color', modifiers: [['darker', 3]] }} | ||||||
|                     animate={true} |                     animate={true} | ||||||
|                     // Se elimina la leyenda, ya que las etiquetas en el eje son suficientes |  | ||||||
|                     legends={[]} |                     legends={[]} | ||||||
|                 />} |                 />} | ||||||
|             </div> |             </div> | ||||||
|   | |||||||
| @@ -1,68 +1,62 @@ | |||||||
| // src/components/ConcejalesWidget.tsx | // src/components/ConcejalesWidget.tsx | ||||||
| import { useState, useMemo, useEffect } from 'react'; | import { useState, useMemo, useEffect } from 'react'; | ||||||
| import { useQuery } from '@tanstack/react-query'; | import { useQuery } from '@tanstack/react-query'; | ||||||
| import Select from 'react-select'; // <-- 1. Importar react-select | import Select from 'react-select'; | ||||||
| import { getMunicipios, getResultadosPorMunicipioYCategoria, getConfiguracionPublica } from '../apiService'; | import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica } from '../apiService'; | ||||||
| import type { MunicipioSimple, ResultadoTicker } from '../types/types'; | import type { MunicipioSimple, ResultadoTicker } from '../types/types'; | ||||||
| import { ImageWithFallback } from './ImageWithFallback'; | import { ImageWithFallback } from './ImageWithFallback'; | ||||||
| import './TickerWidget.css'; | import './TickerWidget.css'; | ||||||
|  |  | ||||||
| const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | ||||||
|  |  | ||||||
| // Estilos personalizados para que el selector se vea bien |  | ||||||
| const customSelectStyles = { | const customSelectStyles = { | ||||||
|   control: (base: any) => ({ ...base, minWidth: '220px', border: '1px solid #ced4da' }), |   control: (base: any) => ({ ...base, minWidth: '220px', border: '1px solid #ced4da' }), | ||||||
|   menu: (base: any) => ({ ...base, zIndex: 10 }), // Para que el menú se superponga |   menu: (base: any) => ({ ...base, zIndex: 10 }), | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | const CATEGORIA_ID = 7; // ID para Concejales | ||||||
|  |  | ||||||
| export const ConcejalesWidget = () => { | export const ConcejalesWidget = () => { | ||||||
|   // 2. Cambiamos el estado para que se adapte a react-select |  | ||||||
|   const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null); |   const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null); | ||||||
|  |  | ||||||
|  |   // 1. Query para la configuración (se había eliminado por error) | ||||||
|   const { data: configData } = useQuery({ |   const { data: configData } = useQuery({ | ||||||
|     queryKey: ['configuracionPublica'], |     queryKey: ['configuracionPublica'], | ||||||
|     queryFn: getConfiguracionPublica, |     queryFn: getConfiguracionPublica, | ||||||
|     staleTime: 0, |     staleTime: 0, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   // Usamos la clave de configuración correcta |   // 2. Query para la lista de municipios | ||||||
|  |   const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({ | ||||||
|  |   // Usamos una clave genérica porque siempre pedimos la lista completa. | ||||||
|  |   queryKey: ['municipios'],  | ||||||
|  |   // Llamamos a la función sin argumentos para obtener todos los municipios. | ||||||
|  |   queryFn: () => getMunicipios(),  | ||||||
|  | }); | ||||||
|  |  | ||||||
|   const cantidadAMostrar = parseInt(configData?.ConcejalesResultadosCantidad || '5', 10); |   const cantidadAMostrar = parseInt(configData?.ConcejalesResultadosCantidad || '5', 10); | ||||||
|  |  | ||||||
|   // 3. Query para obtener la lista de MUNICIPIOS |  | ||||||
|   const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({ |  | ||||||
|     queryKey: ['municipios'], |  | ||||||
|     queryFn: getMunicipios, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   // Este useEffect se encarga de establecer el valor por defecto |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     // Se ejecuta solo si tenemos la lista de municipios y aún no hemos seleccionado nada |  | ||||||
|     if (municipios.length > 0 && !selectedMunicipio) { |     if (municipios.length > 0 && !selectedMunicipio) { | ||||||
|       // Buscamos "LA PLATA" en la lista (insensible a mayúsculas/minúsculas) |  | ||||||
|       const laPlata = municipios.find(m => m.nombre.toUpperCase() === 'LA PLATA'); |       const laPlata = municipios.find(m => m.nombre.toUpperCase() === 'LA PLATA'); | ||||||
|  |  | ||||||
|       // Si lo encontramos, lo establecemos como el municipio seleccionado |  | ||||||
|       if (laPlata) { |       if (laPlata) { | ||||||
|         setSelectedMunicipio({ value: laPlata.id, label: laPlata.nombre }); |         setSelectedMunicipio({ value: laPlata.id, label: laPlata.nombre }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   }, [municipios, selectedMunicipio]); // Se ejecuta cuando 'municipios' o 'selectedMunicipio' cambian |   }, [municipios, selectedMunicipio]); | ||||||
|  |  | ||||||
|   // 4. Transformamos los datos para react-select |  | ||||||
|   const municipioOptions = useMemo(() => |   const municipioOptions = useMemo(() => | ||||||
|     municipios |     municipios | ||||||
|       .map(m => ({ value: m.id, label: m.nombre })) |       .map(m => ({ value: m.id, label: m.nombre })) | ||||||
|       .sort((a, b) => a.label.localeCompare(b.label)), // Orden alfabético |       .sort((a, b) => a.label.localeCompare(b.label)), | ||||||
|   [municipios]); |   [municipios]); | ||||||
|  |  | ||||||
|   // 5. Query para obtener los resultados del MUNICIPIO seleccionado |  | ||||||
|   const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({ |   const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({ | ||||||
|     queryKey: ['resultadosConcejalesPorMunicipio', selectedMunicipio?.value], |     queryKey: ['resultadosPorMunicipio', selectedMunicipio?.value, CATEGORIA_ID], | ||||||
|     queryFn: () => getResultadosPorMunicipioYCategoria(selectedMunicipio!.value, 7), |     queryFn: () => getResultadosPorMunicipio(selectedMunicipio!.value, CATEGORIA_ID), | ||||||
|     enabled: !!selectedMunicipio, |     enabled: !!selectedMunicipio, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   // 6. Lógica para "Otros" (sin cambios funcionales) |   // Lógica para "Otros" | ||||||
|   let displayResults: ResultadoTicker[] = resultados || []; |   let displayResults: ResultadoTicker[] = resultados || []; | ||||||
|   if (resultados && resultados.length > cantidadAMostrar) { |   if (resultados && resultados.length > cantidadAMostrar) { | ||||||
|     const topParties = resultados.slice(0, cantidadAMostrar - 1); |     const topParties = resultados.slice(0, cantidadAMostrar - 1); | ||||||
| @@ -92,6 +86,7 @@ export const ConcejalesWidget = () => { | |||||||
|           onChange={(option) => setSelectedMunicipio(option)} |           onChange={(option) => setSelectedMunicipio(option)} | ||||||
|           isLoading={isLoadingMunicipios} |           isLoading={isLoadingMunicipios} | ||||||
|           placeholder="Buscar y seleccionar un municipio..." |           placeholder="Buscar y seleccionar un municipio..." | ||||||
|  |           isClearable | ||||||
|           styles={customSelectStyles} |           styles={customSelectStyles} | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
|   | |||||||
| @@ -1,19 +1,23 @@ | |||||||
| // src/components/DiputadosWidget.tsx | // src/components/DiputadosWidget.tsx | ||||||
| import { useMemo } from 'react'; | import { useState, useMemo, useEffect } from 'react'; | ||||||
| import { useQuery } from '@tanstack/react-query'; | import { useQuery } from '@tanstack/react-query'; | ||||||
| import { getResumenProvincial, getConfiguracionPublica } from '../apiService'; | import Select from 'react-select'; | ||||||
| import type { CategoriaResumen, ResultadoTicker } from '../types/types'; | import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica } from '../apiService'; | ||||||
|  | import type { MunicipioSimple, ResultadoTicker } from '../types/types'; | ||||||
| import { ImageWithFallback } from './ImageWithFallback'; | import { ImageWithFallback } from './ImageWithFallback'; | ||||||
| import './TickerWidget.css'; | import './TickerWidget.css'; | ||||||
|  |  | ||||||
| const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | ||||||
|  | const customSelectStyles = { | ||||||
|  |   control: (base: any) => ({ ...base, minWidth: '220px', border: '1px solid #ced4da' }), | ||||||
|  |   menu: (base: any) => ({ ...base, zIndex: 10 }), | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Constante para la categoría de este widget | ||||||
|  | const CATEGORIA_ID = 6; // Diputados | ||||||
|  |  | ||||||
| export const DiputadosWidget = () => { | export const DiputadosWidget = () => { | ||||||
|   const { data: categorias, isLoading, error } = useQuery<CategoriaResumen[]>({ |   const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null); | ||||||
|     queryKey: ['resumenProvincial'], |  | ||||||
|     queryFn: getResumenProvincial, |  | ||||||
|     refetchInterval: 30000, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const { data: configData } = useQuery({ |   const { data: configData } = useQuery({ | ||||||
|     queryKey: ['configuracionPublica'], |     queryKey: ['configuracionPublica'], | ||||||
| @@ -21,24 +25,47 @@ export const DiputadosWidget = () => { | |||||||
|     staleTime: 0, |     staleTime: 0, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   // Usamos la clave de configuración del Ticker, ya que es para Senadores/Diputados | ||||||
|   const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10); |   const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10); | ||||||
|  |  | ||||||
|   // Usamos useMemo para encontrar los datos específicos de Diputados (ID 6) |   const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({ | ||||||
|   const diputadosData = useMemo(() => { |     queryKey: ['municipios', CATEGORIA_ID], // Key única para la lista de municipios de diputados | ||||||
|     return categorias?.find(c => c.categoriaId === 6); |     queryFn: () => getMunicipios(CATEGORIA_ID), // Pide solo los municipios que votan diputados | ||||||
|   }, [categorias]); |   }); | ||||||
|  |  | ||||||
|   if (isLoading) return <div className="ticker-card loading">Cargando...</div>; |   // useEffect para establecer "LA PLATA" por defecto | ||||||
|   if (error || !diputadosData) return <div className="ticker-card error">Datos de Diputados no disponibles.</div>; |   useEffect(() => { | ||||||
|  |     if (municipios.length > 0 && !selectedMunicipio) { | ||||||
|  |       const laPlata = municipios.find(m => m.nombre.toUpperCase() === 'LA PLATA'); | ||||||
|  |       if (laPlata) { | ||||||
|  |         setSelectedMunicipio({ value: laPlata.id, label: laPlata.nombre }); | ||||||
|  |       } else if (municipios.length > 0) { | ||||||
|  |         // Si no está La Plata, seleccionamos el primero de la lista | ||||||
|  |         setSelectedMunicipio({ value: municipios[0].id, label: municipios[0].nombre }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, [municipios, selectedMunicipio]); | ||||||
|  |  | ||||||
|   // Lógica para "Otros" aplicada solo a los resultados de Diputados |   const municipioOptions = useMemo(() => | ||||||
|   let displayResults: ResultadoTicker[] = diputadosData.resultados; |     municipios | ||||||
|   if (diputadosData.resultados.length > cantidadAMostrar) { |       .map(m => ({ value: m.id, label: m.nombre })) | ||||||
|     const topParties = diputadosData.resultados.slice(0, cantidadAMostrar - 1); |       .sort((a, b) => a.label.localeCompare(b.label)), | ||||||
|     const otherParties = diputadosData.resultados.slice(cantidadAMostrar - 1); |   [municipios]); | ||||||
|     const otrosPorcentaje = otherParties.reduce((sum, party) => sum + party.porcentaje, 0); |  | ||||||
|  |   const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({ | ||||||
|  |     queryKey: ['resultadosMunicipio', selectedMunicipio?.value, CATEGORIA_ID], | ||||||
|  |     queryFn: () => getResultadosPorMunicipio(selectedMunicipio!.value, CATEGORIA_ID), | ||||||
|  |     enabled: !!selectedMunicipio, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   // Lógica para "Otros" | ||||||
|  |   let displayResults: ResultadoTicker[] = resultados || []; | ||||||
|  |   if (resultados && resultados.length > cantidadAMostrar) { | ||||||
|  |     const topParties = resultados.slice(0, cantidadAMostrar - 1); | ||||||
|  |     const otherParties = resultados.slice(cantidadAMostrar - 1); | ||||||
|  |     const otrosPorcentaje = otherParties.reduce((sum, party) => sum + (party.porcentaje || 0), 0); | ||||||
|     const otrosEntry: ResultadoTicker = { |     const otrosEntry: ResultadoTicker = { | ||||||
|       id: `otros-diputados`, |       id: `otros-diputados-${selectedMunicipio?.value}`, | ||||||
|       nombre: 'Otros', |       nombre: 'Otros', | ||||||
|       nombreCorto: 'Otros', |       nombreCorto: 'Otros', | ||||||
|       color: '#888888', |       color: '#888888', | ||||||
| @@ -47,20 +74,27 @@ export const DiputadosWidget = () => { | |||||||
|       porcentaje: otrosPorcentaje, |       porcentaje: otrosPorcentaje, | ||||||
|     }; |     }; | ||||||
|     displayResults = [...topParties, otrosEntry]; |     displayResults = [...topParties, otrosEntry]; | ||||||
|   } else { |   } else if (resultados) { | ||||||
|     displayResults = diputadosData.resultados.slice(0, cantidadAMostrar); |     displayResults = resultados.slice(0, cantidadAMostrar); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <div className="ticker-card"> |     <div className="ticker-card"> | ||||||
|       <div className="ticker-header"> |       <div className="ticker-header"> | ||||||
|         <h3>{diputadosData.categoriaNombre.replace(' PROVINCIALES', '')}</h3> |         <h3>DIPUTADOS POR MUNICIPIO</h3> | ||||||
|         <div className="ticker-stats"> |         <Select | ||||||
|           <span>Mesas: <strong>{formatPercent(diputadosData.estadoRecuento?.mesasTotalizadasPorcentaje || 0)}</strong></span> |           options={municipioOptions} | ||||||
|           <span>Part: <strong>{formatPercent(diputadosData.estadoRecuento?.participacionPorcentaje || 0)}</strong></span> |           value={selectedMunicipio} | ||||||
|         </div> |           onChange={(option) => setSelectedMunicipio(option)} | ||||||
|  |           isLoading={isLoadingMunicipios} | ||||||
|  |           placeholder="Buscar municipio..." | ||||||
|  |           isClearable | ||||||
|  |           styles={customSelectStyles} | ||||||
|  |         /> | ||||||
|       </div> |       </div> | ||||||
|       <div className="ticker-results"> |       <div className="ticker-results"> | ||||||
|  |         {(isLoadingMunicipios || (isLoadingResultados && selectedMunicipio)) && <p>Cargando...</p>} | ||||||
|  |         {!selectedMunicipio && !isLoadingMunicipios && <p style={{textAlign: 'center', color: '#666'}}>Seleccione un municipio.</p>} | ||||||
|         {displayResults.map(partido => ( |         {displayResults.map(partido => ( | ||||||
|           <div key={partido.id} className="ticker-party"> |           <div key={partido.id} className="ticker-party"> | ||||||
|             <div className="party-logo"> |             <div className="party-logo"> | ||||||
|   | |||||||
| @@ -1,19 +1,25 @@ | |||||||
| // src/components/SenadoresWidget.tsx | // src/components/SenadoresWidget.tsx | ||||||
| import { useMemo } from 'react'; | import { useState, useEffect, useMemo } from 'react'; | ||||||
| import { useQuery } from '@tanstack/react-query'; | import { useQuery } from '@tanstack/react-query'; | ||||||
| import { getResumenProvincial, getConfiguracionPublica } from '../apiService'; | import Select from 'react-select'; // Importamos react-select | ||||||
| import type { CategoriaResumen, ResultadoTicker } from '../types/types'; | import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica } from '../apiService'; // Usamos las funciones genéricas | ||||||
|  | import type { MunicipioSimple, ResultadoTicker } from '../types/types'; | ||||||
| import { ImageWithFallback } from './ImageWithFallback'; | import { ImageWithFallback } from './ImageWithFallback'; | ||||||
| import './TickerWidget.css'; | import './TickerWidget.css'; | ||||||
|  |  | ||||||
| const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | ||||||
|  |  | ||||||
|  | // Estilos para el selector, podemos moverlos a un archivo común más adelante | ||||||
|  | const customSelectStyles = { | ||||||
|  |   control: (base: any) => ({ ...base, minWidth: '220px', border: '1px solid #ced4da' }), | ||||||
|  |   menu: (base: any) => ({ ...base, zIndex: 10 }), | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Constante para la categoría de este widget | ||||||
|  | const CATEGORIA_ID = 5; // Senadores | ||||||
|  |  | ||||||
| export const SenadoresWidget = () => { | export const SenadoresWidget = () => { | ||||||
|   const { data: categorias, isLoading, error } = useQuery<CategoriaResumen[]>({ |   const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null); | ||||||
|     queryKey: ['resumenProvincial'], |  | ||||||
|     queryFn: getResumenProvincial, |  | ||||||
|     refetchInterval: 30000, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const { data: configData } = useQuery({ |   const { data: configData } = useQuery({ | ||||||
|     queryKey: ['configuracionPublica'], |     queryKey: ['configuracionPublica'], | ||||||
| @@ -21,24 +27,44 @@ export const SenadoresWidget = () => { | |||||||
|     staleTime: 0, |     staleTime: 0, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   // Usamos la clave de configuración del Ticker, ya que es para Senadores/Diputados | ||||||
|   const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10); |   const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10); | ||||||
|  |  | ||||||
|   // Usamos useMemo para encontrar los datos específicos de Senadores (ID 5) |   const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({ | ||||||
|   const senadoresData = useMemo(() => { |     queryKey: ['municipios', CATEGORIA_ID], // Key única para la caché | ||||||
|     return categorias?.find(c => c.categoriaId === 5); |     queryFn: () => getMunicipios(CATEGORIA_ID), // Pasamos el ID de la categoría | ||||||
|   }, [categorias]); |   }); | ||||||
|  |  | ||||||
|   if (isLoading) return <div className="ticker-card loading">Cargando...</div>; |   // useEffect para establecer "LA PLATA" por defecto | ||||||
|   if (error || !senadoresData) return <div className="ticker-card error">Datos de Senadores no disponibles.</div>; |   useEffect(() => { | ||||||
|  |     if (municipios.length > 0 && !selectedMunicipio) { | ||||||
|  |       const laPlata = municipios.find(m => m.nombre.toUpperCase() === 'LA PLATA'); | ||||||
|  |       if (laPlata) { | ||||||
|  |         setSelectedMunicipio({ value: laPlata.id, label: laPlata.nombre }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, [municipios, selectedMunicipio]); | ||||||
|  |  | ||||||
|   // Lógica para "Otros" aplicada solo a los resultados de Senadores |   const municipioOptions = useMemo(() => | ||||||
|   let displayResults: ResultadoTicker[] = senadoresData.resultados; |     municipios | ||||||
|   if (senadoresData.resultados.length > cantidadAMostrar) { |       .map(m => ({ value: m.id, label: m.nombre })) | ||||||
|     const topParties = senadoresData.resultados.slice(0, cantidadAMostrar - 1); |       .sort((a, b) => a.label.localeCompare(b.label)), | ||||||
|     const otherParties = senadoresData.resultados.slice(cantidadAMostrar - 1); |     [municipios]); | ||||||
|     const otrosPorcentaje = otherParties.reduce((sum, party) => sum + party.porcentaje, 0); |  | ||||||
|  |   const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({ | ||||||
|  |     queryKey: ['resultadosMunicipio', selectedMunicipio?.value, CATEGORIA_ID], | ||||||
|  |     queryFn: () => getResultadosPorMunicipio(selectedMunicipio!.value, CATEGORIA_ID), | ||||||
|  |     enabled: !!selectedMunicipio, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   // Lógica para "Otros" | ||||||
|  |   let displayResults: ResultadoTicker[] = resultados || []; | ||||||
|  |   if (resultados && resultados.length > cantidadAMostrar) { | ||||||
|  |     const topParties = resultados.slice(0, cantidadAMostrar - 1); | ||||||
|  |     const otherParties = resultados.slice(cantidadAMostrar - 1); | ||||||
|  |     const otrosPorcentaje = otherParties.reduce((sum, party) => sum + (party.porcentaje || 0), 0); | ||||||
|     const otrosEntry: ResultadoTicker = { |     const otrosEntry: ResultadoTicker = { | ||||||
|       id: `otros-senadores`, |       id: `otros-senadores-${selectedMunicipio?.value}`, | ||||||
|       nombre: 'Otros', |       nombre: 'Otros', | ||||||
|       nombreCorto: 'Otros', |       nombreCorto: 'Otros', | ||||||
|       color: '#888888', |       color: '#888888', | ||||||
| @@ -47,20 +73,27 @@ export const SenadoresWidget = () => { | |||||||
|       porcentaje: otrosPorcentaje, |       porcentaje: otrosPorcentaje, | ||||||
|     }; |     }; | ||||||
|     displayResults = [...topParties, otrosEntry]; |     displayResults = [...topParties, otrosEntry]; | ||||||
|   } else { |   } else if (resultados) { | ||||||
|     displayResults = senadoresData.resultados.slice(0, cantidadAMostrar); |     displayResults = resultados.slice(0, cantidadAMostrar); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <div className="ticker-card"> |     <div className="ticker-card"> | ||||||
|       <div className="ticker-header"> |       <div className="ticker-header"> | ||||||
|         <h3>{senadoresData.categoriaNombre.replace(' PROVINCIALES', '')}</h3> |         <h3>SENADORES POR MUNICIPIO</h3> | ||||||
|         <div className="ticker-stats"> |         <Select | ||||||
|           <span>Mesas: <strong>{formatPercent(senadoresData.estadoRecuento?.mesasTotalizadasPorcentaje || 0)}</strong></span> |           options={municipioOptions} | ||||||
|           <span>Part: <strong>{formatPercent(senadoresData.estadoRecuento?.participacionPorcentaje || 0)}</strong></span> |           value={selectedMunicipio} | ||||||
|         </div> |           onChange={(option) => setSelectedMunicipio(option)} | ||||||
|  |           isLoading={isLoadingMunicipios} | ||||||
|  |           placeholder="Buscar municipio..." | ||||||
|  |           isClearable | ||||||
|  |           styles={customSelectStyles} | ||||||
|  |         /> | ||||||
|       </div> |       </div> | ||||||
|       <div className="ticker-results"> |       <div className="ticker-results"> | ||||||
|  |         {(isLoadingMunicipios || (isLoadingResultados && selectedMunicipio)) && <p>Cargando...</p>} | ||||||
|  |         {!selectedMunicipio && !isLoadingMunicipios && <p style={{ textAlign: 'center', color: '#666' }}>Seleccione un municipio.</p>} | ||||||
|         {displayResults.map(partido => ( |         {displayResults.map(partido => ( | ||||||
|           <div key={partido.id} className="ticker-party"> |           <div key={partido.id} className="ticker-party"> | ||||||
|             <div className="party-logo"> |             <div className="party-logo"> | ||||||
|   | |||||||
| @@ -17,20 +17,39 @@ public class CatalogosController : ControllerBase | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     [HttpGet("municipios")] |     [HttpGet("municipios")] | ||||||
|     public async Task<IActionResult> GetMunicipios() |     public async Task<IActionResult> GetMunicipios([FromQuery] int? categoriaId) | ||||||
|     { |     { | ||||||
|         var municipios = await _dbContext.AmbitosGeograficos |         var municipiosQuery = _dbContext.AmbitosGeograficos | ||||||
|             .AsNoTracking() |             .AsNoTracking() | ||||||
|             .Where(a => a.NivelId == 30 && a.SeccionId != null) |             .Where(a => a.NivelId == 30); | ||||||
|             .Select(a => new MunicipioSimpleDto |  | ||||||
|  |         // Si NO se proporciona una categoriaId, devolvemos todos (para Concejales). | ||||||
|  |         if (categoriaId == null) | ||||||
|         { |         { | ||||||
|                 Id = a.SeccionId!, |             var todosLosMunicipios = await municipiosQuery | ||||||
|                 Nombre = a.Nombre |                 .OrderBy(a => a.Nombre) | ||||||
|             }) |                 .Select(a => new { a.SeccionId, a.Nombre }) // Usamos SeccionId como el ID electoral | ||||||
|             .OrderBy(m => m.Nombre) |                 .ToListAsync(); | ||||||
|  |             return Ok(todosLosMunicipios); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // --- LÓGICA DE FILTRADO PARA DIPUTADOS Y SENADORES --- | ||||||
|  |         // 1. Encontrar las Secciones Electorales que SÍ tienen resultados para la categoría. | ||||||
|  |         var seccionesActivasIds = await _dbContext.ProyeccionesBancas | ||||||
|  |             .AsNoTracking() | ||||||
|  |             .Where(p => p.CategoriaId == categoriaId) | ||||||
|  |             .Select(p => p.AmbitoGeografico.SeccionProvincialId) | ||||||
|  |             .Distinct() | ||||||
|             .ToListAsync(); |             .ToListAsync(); | ||||||
|  |  | ||||||
|         return Ok(municipios); |         // 2. Filtramos la lista de municipios. | ||||||
|  |         var municipiosFiltrados = await municipiosQuery | ||||||
|  |             .Where(m => seccionesActivasIds.Contains(m.SeccionProvincialId)) | ||||||
|  |             .OrderBy(a => a.Nombre) | ||||||
|  |             .Select(a => new { a.SeccionId, a.Nombre }) | ||||||
|  |             .ToListAsync(); | ||||||
|  |  | ||||||
|  |         return Ok(municipiosFiltrados); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     [HttpGet("agrupaciones")] |     [HttpGet("agrupaciones")] | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "ConnectionStrings": { |   "ConnectionStrings": { | ||||||
|     "DefaultConnection": "Server=TECNICA3;Database=Elecciones2025;User Id=apielecciones2025;Password=PTP847Elecciones2025;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;" |     "DefaultConnection": "Server=192.168.5.128;Database=Elecciones2025;User Id=apielecciones2025;Password=PTP847Elecciones2025;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;" | ||||||
|   }, |   }, | ||||||
|   "Logging": { |   "Logging": { | ||||||
|     "LogLevel": { |     "LogLevel": { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "ConnectionStrings": { |   "ConnectionStrings": { | ||||||
|     "DefaultConnection": "Server=TECNICA3;Database=Elecciones2025;User Id=apielecciones2025;Password=PTP847Elecciones2025;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;" |     "DefaultConnection": "Server=192.168.5.128;Database=Elecciones2025;User Id=apielecciones2025;Password=PTP847Elecciones2025;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;" | ||||||
|   }, |   }, | ||||||
|   "Logging": { |   "Logging": { | ||||||
|     "LogLevel": { |     "LogLevel": { | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ using System.Reflection; | |||||||
| [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")] | [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")] | ||||||
| [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | ||||||
| [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | ||||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")] | [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")] | ||||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")] | [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")] | ||||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")] | [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")] | ||||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| {"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","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=","quzFFdCjq28RUvzP4QEPqUv1aG3JErbxco/IZLvopsc=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","IXt64lxLeDS4dW1rszsenQT32xUD\u002BLsm/9zmNzXoV/c="],"CachedAssets":{},"CachedCopyCandidates":{}} | {"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","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=","KaNgYB2ifCIE3p/Tay5fVAWfGAbZ/FRwD44afnqRoKI=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","iL0B6pwfSYZpOYzq7AuHcEbBAAVseMon4HovdUC\u002BTcU="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||||
| @@ -1 +1 @@ | |||||||
| {"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","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=","quzFFdCjq28RUvzP4QEPqUv1aG3JErbxco/IZLvopsc=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","IXt64lxLeDS4dW1rszsenQT32xUD\u002BLsm/9zmNzXoV/c="],"CachedAssets":{},"CachedCopyCandidates":{}} | {"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","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=","KaNgYB2ifCIE3p/Tay5fVAWfGAbZ/FRwD44afnqRoKI=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","iL0B6pwfSYZpOYzq7AuHcEbBAAVseMon4HovdUC\u002BTcU="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||||
| @@ -1 +1 @@ | |||||||
| {"GlobalPropertiesHash":"O7YawHw32G/Fh2bs+snZgm9O7okI0WYgTQmXM931znY=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM="],"CachedAssets":{},"CachedCopyCandidates":{}} | {"GlobalPropertiesHash":"O7YawHw32G/Fh2bs+snZgm9O7okI0WYgTQmXM931znY=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||||
| @@ -13,7 +13,7 @@ using System.Reflection; | |||||||
| [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")] | [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")] | ||||||
| [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | ||||||
| [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | ||||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")] | [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")] | ||||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")] | [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")] | ||||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")] | [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")] | ||||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ using System.Reflection; | |||||||
| [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")] | [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")] | ||||||
| [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | ||||||
| [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | ||||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")] | [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")] | ||||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")] | [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")] | ||||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")] | [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")] | ||||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ using System.Reflection; | |||||||
| [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")] | [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")] | ||||||
| [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | ||||||
| [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | ||||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")] | [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")] | ||||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] | [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] | ||||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] | [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] | ||||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user