Feat Widgets 1930
This commit is contained in:
		| @@ -140,8 +140,8 @@ export const getResultadosConcejales = async (seccionId: string): Promise<Result | ||||
| }; | ||||
|  | ||||
| export const getDetalleSeccion = async (seccionId: string, categoriaId: number): Promise<ResultadoDetalleSeccion[]> => { | ||||
|     const response = await apiClient.get(`/resultados/seccion/${seccionId}?categoriaId=${categoriaId}`); | ||||
|     return response.data; | ||||
|   const response = await apiClient.get(`/resultados/seccion/${seccionId}?categoriaId=${categoriaId}`); | ||||
|   return response.data; | ||||
| }; | ||||
|  | ||||
| export const getResultadosPorMunicipioYCategoria = async (municipioId: string, categoriaId: number): Promise<ResultadoTicker[]> => { | ||||
| @@ -149,7 +149,18 @@ export const getResultadosPorMunicipioYCategoria = async (municipioId: string, c | ||||
|   return response.data.resultados; | ||||
| }; | ||||
|  | ||||
| export const getMunicipios = async (): Promise<MunicipioSimple[]> => { | ||||
|   const response = await apiClient.get('/catalogos/municipios'); | ||||
|   return response.data; | ||||
| export const getResultadosPorMunicipio = async (municipioId: string, categoriaId: number): Promise<ResultadoTicker[]> => { | ||||
|   const response = await apiClient.get(`/resultados/partido/${municipioId}?categoriaId=${categoriaId}`); | ||||
|   // 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 | ||||
| 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 { getBancasPorSeccion, getSeccionesElectorales } from '../apiService'; | ||||
| import type { ProyeccionBancas, MunicipioSimple } from '../types/types'; | ||||
| 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"]; | ||||
|  | ||||
| export const BancasWidget = () => { | ||||
|     const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); | ||||
|     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(() => { | ||||
|         const fetchSecciones = async () => { | ||||
|             try { | ||||
|                 const seccionesData = await getSeccionesElectorales(); | ||||
|                 if (seccionesData && seccionesData.length > 0) { | ||||
|                      | ||||
|                     // --- INICIO DE LA LÓGICA DE ORDENAMIENTO --- | ||||
|                     const orden = new Map([ | ||||
|                         ['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3], | ||||
|                         ['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7] | ||||
|                     ]); | ||||
|  | ||||
|                     const getOrden = (nombre: string) => { | ||||
|                         const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/); | ||||
|                         return match ? orden.get(match[0]) ?? 99 : 99; | ||||
|                     }; | ||||
|  | ||||
|                     seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre)); | ||||
|                     // --- FIN DE LA LÓGICA DE ORDENAMIENTO --- | ||||
|  | ||||
|                     setSecciones(seccionesData); | ||||
|                     setSeccionActual(seccionesData[0].id); | ||||
|                 } else { | ||||
|                     setError("No se encontraron secciones electorales."); | ||||
|                 } | ||||
|             } catch (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(); | ||||
|     }, []); | ||||
|  | ||||
|     // useEffect para cargar los datos de bancas cuando cambia la sección | ||||
|     useEffect(() => { | ||||
|         if (!seccionActual) return; | ||||
|     // --- CÓDIGO REFACTORIZADO --- | ||||
|     // Eliminamos los useState para data, loading y error | ||||
|  | ||||
|         const fetchData = async () => { | ||||
|             setLoading(true); | ||||
|             setError(null); | ||||
|             try { | ||||
|                 const result = await getBancasPorSeccion(seccionActual); | ||||
|                 setData(result); | ||||
|             } catch (err) { | ||||
|                 console.error(`Error cargando datos de bancas para sección ${seccionActual}:`, err); | ||||
|                 setData(null); | ||||
|                 setError("No hay datos de bancas disponibles para esta sección."); | ||||
|             } finally { | ||||
|                 setLoading(false); | ||||
|             } | ||||
|         }; | ||||
|         fetchData(); | ||||
|     }, [seccionActual]); | ||||
|     // useQuery ahora es la única fuente de verdad para los datos de bancas | ||||
|     const {  | ||||
|         data,  | ||||
|         isLoading,  | ||||
|         error  | ||||
|     } = useQuery<ProyeccionBancas, Error>({ | ||||
|         queryKey: ['bancasPorSeccion', seccionActual], | ||||
|         queryFn: () => getBancasPorSeccion(seccionActual), | ||||
|         enabled: !!seccionActual, | ||||
|         retry: (failureCount, error: any) => { | ||||
|             if (error.response?.status === 404) return false; | ||||
|             return failureCount < 3; | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
|     // 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 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 ( | ||||
|         <div className="bancas-widget-container"> | ||||
|             <div className="bancas-header"> | ||||
| @@ -84,8 +79,7 @@ export const BancasWidget = () => { | ||||
|                 </select> | ||||
|             </div> | ||||
|             <div className="waffle-chart-container"> | ||||
|                 {loading ? <p>Cargando...</p> : error ? <p>{error}</p> : | ||||
|                 // --- SE REEMPLAZA EL GRÁFICO WAFFLE POR EL GRÁFICO DE BARRAS --- | ||||
|                 {isLoading ? <p>Cargando...</p> : errorMessage ? <p>{errorMessage}</p> : | ||||
|                 <ResponsiveBar | ||||
|                     data={barData} | ||||
|                     keys={['bancas']} | ||||
| @@ -106,7 +100,6 @@ export const BancasWidget = () => { | ||||
|                         legend: 'Cantidad de Bancas', | ||||
|                         legendPosition: 'middle', | ||||
|                         legendOffset: 20, | ||||
|                         // Asegura que los ticks del eje sean números enteros | ||||
|                         format: (value) => Math.floor(value) === value ? value : '' | ||||
|                     }} | ||||
|                     axisLeft={{ | ||||
| @@ -118,7 +111,6 @@ export const BancasWidget = () => { | ||||
|                     labelSkipHeight={12} | ||||
|                     labelTextColor={{ from: 'color', modifiers: [['darker', 3]] }} | ||||
|                     animate={true} | ||||
|                     // Se elimina la leyenda, ya que las etiquetas en el eje son suficientes | ||||
|                     legends={[]} | ||||
|                 />} | ||||
|             </div> | ||||
|   | ||||
| @@ -1,68 +1,62 @@ | ||||
| // src/components/ConcejalesWidget.tsx | ||||
| import { useState, useMemo, useEffect } from 'react'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| import Select from 'react-select'; // <-- 1. Importar react-select | ||||
| import { getMunicipios, getResultadosPorMunicipioYCategoria, getConfiguracionPublica } from '../apiService'; | ||||
| import Select from 'react-select'; | ||||
| import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica } from '../apiService'; | ||||
| import type { MunicipioSimple, ResultadoTicker } from '../types/types'; | ||||
| import { ImageWithFallback } from './ImageWithFallback'; | ||||
| import './TickerWidget.css'; | ||||
|  | ||||
| const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | ||||
|  | ||||
| // Estilos personalizados para que el selector se vea bien | ||||
| const customSelectStyles = { | ||||
|   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 = () => { | ||||
|   // 2. Cambiamos el estado para que se adapte a react-select | ||||
|   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({ | ||||
|     queryKey: ['configuracionPublica'], | ||||
|     queryFn: getConfiguracionPublica, | ||||
|     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); | ||||
|  | ||||
|   // 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(() => { | ||||
|     // Se ejecuta solo si tenemos la lista de municipios y aún no hemos seleccionado nada | ||||
|     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'); | ||||
|  | ||||
|       // Si lo encontramos, lo establecemos como el municipio seleccionado | ||||
|       if (laPlata) { | ||||
|         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(() => | ||||
|     municipios | ||||
|       .map(m => ({ value: m.id, label: m.nombre })) | ||||
|       .sort((a, b) => a.label.localeCompare(b.label)), // Orden alfabético | ||||
|     [municipios]); | ||||
|       .sort((a, b) => a.label.localeCompare(b.label)), | ||||
|   [municipios]); | ||||
|  | ||||
|   // 5. Query para obtener los resultados del MUNICIPIO seleccionado | ||||
|   const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({ | ||||
|     queryKey: ['resultadosConcejalesPorMunicipio', selectedMunicipio?.value], | ||||
|     queryFn: () => getResultadosPorMunicipioYCategoria(selectedMunicipio!.value, 7), | ||||
|     queryKey: ['resultadosPorMunicipio', selectedMunicipio?.value, CATEGORIA_ID], | ||||
|     queryFn: () => getResultadosPorMunicipio(selectedMunicipio!.value, CATEGORIA_ID), | ||||
|     enabled: !!selectedMunicipio, | ||||
|   }); | ||||
|  | ||||
|   // 6. Lógica para "Otros" (sin cambios funcionales) | ||||
|   // Lógica para "Otros" | ||||
|   let displayResults: ResultadoTicker[] = resultados || []; | ||||
|   if (resultados && resultados.length > cantidadAMostrar) { | ||||
|     const topParties = resultados.slice(0, cantidadAMostrar - 1); | ||||
| @@ -92,6 +86,7 @@ export const ConcejalesWidget = () => { | ||||
|           onChange={(option) => setSelectedMunicipio(option)} | ||||
|           isLoading={isLoadingMunicipios} | ||||
|           placeholder="Buscar y seleccionar un municipio..." | ||||
|           isClearable | ||||
|           styles={customSelectStyles} | ||||
|         /> | ||||
|       </div> | ||||
|   | ||||
| @@ -1,19 +1,23 @@ | ||||
| // src/components/DiputadosWidget.tsx | ||||
| import { useMemo } from 'react'; | ||||
| import { useState, useMemo, useEffect } from 'react'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| import { getResumenProvincial, getConfiguracionPublica } from '../apiService'; | ||||
| import type { CategoriaResumen, ResultadoTicker } from '../types/types'; | ||||
| import Select from 'react-select'; | ||||
| import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica } from '../apiService'; | ||||
| import type { MunicipioSimple, ResultadoTicker } from '../types/types'; | ||||
| import { ImageWithFallback } from './ImageWithFallback'; | ||||
| import './TickerWidget.css'; | ||||
|  | ||||
| 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 = () => { | ||||
|   const { data: categorias, isLoading, error } = useQuery<CategoriaResumen[]>({ | ||||
|     queryKey: ['resumenProvincial'], | ||||
|     queryFn: getResumenProvincial, | ||||
|     refetchInterval: 30000, | ||||
|   }); | ||||
|   const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null); | ||||
|  | ||||
|   const { data: configData } = useQuery({ | ||||
|     queryKey: ['configuracionPublica'], | ||||
| @@ -21,24 +25,47 @@ export const DiputadosWidget = () => { | ||||
|     staleTime: 0, | ||||
|   }); | ||||
|  | ||||
|   // Usamos la clave de configuración del Ticker, ya que es para Senadores/Diputados | ||||
|   const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10); | ||||
|  | ||||
|   // Usamos useMemo para encontrar los datos específicos de Diputados (ID 6) | ||||
|   const diputadosData = useMemo(() => { | ||||
|     return categorias?.find(c => c.categoriaId === 6); | ||||
|   }, [categorias]); | ||||
|   const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({ | ||||
|     queryKey: ['municipios', CATEGORIA_ID], // Key única para la lista de municipios de diputados | ||||
|     queryFn: () => getMunicipios(CATEGORIA_ID), // Pide solo los municipios que votan diputados | ||||
|   }); | ||||
|  | ||||
|   if (isLoading) return <div className="ticker-card loading">Cargando...</div>; | ||||
|   if (error || !diputadosData) return <div className="ticker-card error">Datos de Diputados no disponibles.</div>; | ||||
|   // useEffect para establecer "LA PLATA" por defecto | ||||
|   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 | ||||
|   let displayResults: ResultadoTicker[] = diputadosData.resultados; | ||||
|   if (diputadosData.resultados.length > cantidadAMostrar) { | ||||
|     const topParties = diputadosData.resultados.slice(0, cantidadAMostrar - 1); | ||||
|     const otherParties = diputadosData.resultados.slice(cantidadAMostrar - 1); | ||||
|     const otrosPorcentaje = otherParties.reduce((sum, party) => sum + party.porcentaje, 0); | ||||
|   const municipioOptions = useMemo(() => | ||||
|     municipios | ||||
|       .map(m => ({ value: m.id, label: m.nombre })) | ||||
|       .sort((a, b) => a.label.localeCompare(b.label)), | ||||
|   [municipios]); | ||||
|  | ||||
|   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 = { | ||||
|       id: `otros-diputados`, | ||||
|       id: `otros-diputados-${selectedMunicipio?.value}`, | ||||
|       nombre: 'Otros', | ||||
|       nombreCorto: 'Otros', | ||||
|       color: '#888888', | ||||
| @@ -47,20 +74,27 @@ export const DiputadosWidget = () => { | ||||
|       porcentaje: otrosPorcentaje, | ||||
|     }; | ||||
|     displayResults = [...topParties, otrosEntry]; | ||||
|   } else { | ||||
|     displayResults = diputadosData.resultados.slice(0, cantidadAMostrar); | ||||
|   } else if (resultados) { | ||||
|     displayResults = resultados.slice(0, cantidadAMostrar); | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div className="ticker-card"> | ||||
|       <div className="ticker-header"> | ||||
|         <h3>{diputadosData.categoriaNombre.replace(' PROVINCIALES', '')}</h3> | ||||
|         <div className="ticker-stats"> | ||||
|           <span>Mesas: <strong>{formatPercent(diputadosData.estadoRecuento?.mesasTotalizadasPorcentaje || 0)}</strong></span> | ||||
|           <span>Part: <strong>{formatPercent(diputadosData.estadoRecuento?.participacionPorcentaje || 0)}</strong></span> | ||||
|         </div> | ||||
|         <h3>DIPUTADOS POR MUNICIPIO</h3> | ||||
|         <Select | ||||
|           options={municipioOptions} | ||||
|           value={selectedMunicipio} | ||||
|           onChange={(option) => setSelectedMunicipio(option)} | ||||
|           isLoading={isLoadingMunicipios} | ||||
|           placeholder="Buscar municipio..." | ||||
|           isClearable | ||||
|           styles={customSelectStyles} | ||||
|         /> | ||||
|       </div> | ||||
|       <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 => ( | ||||
|           <div key={partido.id} className="ticker-party"> | ||||
|             <div className="party-logo"> | ||||
|   | ||||
| @@ -1,19 +1,25 @@ | ||||
| // src/components/SenadoresWidget.tsx | ||||
| import { useMemo } from 'react'; | ||||
| import { useState, useEffect, useMemo } from 'react'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| import { getResumenProvincial, getConfiguracionPublica } from '../apiService'; | ||||
| import type { CategoriaResumen, ResultadoTicker } from '../types/types'; | ||||
| import Select from 'react-select'; // Importamos react-select | ||||
| import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica } from '../apiService'; // Usamos las funciones genéricas | ||||
| import type { MunicipioSimple, ResultadoTicker } from '../types/types'; | ||||
| import { ImageWithFallback } from './ImageWithFallback'; | ||||
| import './TickerWidget.css'; | ||||
|  | ||||
| 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 = () => { | ||||
|   const { data: categorias, isLoading, error } = useQuery<CategoriaResumen[]>({ | ||||
|     queryKey: ['resumenProvincial'], | ||||
|     queryFn: getResumenProvincial, | ||||
|     refetchInterval: 30000, | ||||
|   }); | ||||
|   const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null); | ||||
|  | ||||
|   const { data: configData } = useQuery({ | ||||
|     queryKey: ['configuracionPublica'], | ||||
| @@ -21,24 +27,44 @@ export const SenadoresWidget = () => { | ||||
|     staleTime: 0, | ||||
|   }); | ||||
|  | ||||
|   // Usamos la clave de configuración del Ticker, ya que es para Senadores/Diputados | ||||
|   const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10); | ||||
|  | ||||
|   // Usamos useMemo para encontrar los datos específicos de Senadores (ID 5) | ||||
|   const senadoresData = useMemo(() => { | ||||
|     return categorias?.find(c => c.categoriaId === 5); | ||||
|   }, [categorias]); | ||||
|   const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({ | ||||
|     queryKey: ['municipios', CATEGORIA_ID], // Key única para la caché | ||||
|     queryFn: () => getMunicipios(CATEGORIA_ID), // Pasamos el ID de la categoría | ||||
|   }); | ||||
|  | ||||
|   if (isLoading) return <div className="ticker-card loading">Cargando...</div>; | ||||
|   if (error || !senadoresData) return <div className="ticker-card error">Datos de Senadores no disponibles.</div>; | ||||
|   // useEffect para establecer "LA PLATA" por defecto | ||||
|   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 | ||||
|   let displayResults: ResultadoTicker[] = senadoresData.resultados; | ||||
|   if (senadoresData.resultados.length > cantidadAMostrar) { | ||||
|     const topParties = senadoresData.resultados.slice(0, cantidadAMostrar - 1); | ||||
|     const otherParties = senadoresData.resultados.slice(cantidadAMostrar - 1); | ||||
|     const otrosPorcentaje = otherParties.reduce((sum, party) => sum + party.porcentaje, 0); | ||||
|   const municipioOptions = useMemo(() => | ||||
|     municipios | ||||
|       .map(m => ({ value: m.id, label: m.nombre })) | ||||
|       .sort((a, b) => a.label.localeCompare(b.label)), | ||||
|     [municipios]); | ||||
|  | ||||
|   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 = { | ||||
|       id: `otros-senadores`, | ||||
|       id: `otros-senadores-${selectedMunicipio?.value}`, | ||||
|       nombre: 'Otros', | ||||
|       nombreCorto: 'Otros', | ||||
|       color: '#888888', | ||||
| @@ -47,20 +73,27 @@ export const SenadoresWidget = () => { | ||||
|       porcentaje: otrosPorcentaje, | ||||
|     }; | ||||
|     displayResults = [...topParties, otrosEntry]; | ||||
|   } else { | ||||
|     displayResults = senadoresData.resultados.slice(0, cantidadAMostrar); | ||||
|   } else if (resultados) { | ||||
|     displayResults = resultados.slice(0, cantidadAMostrar); | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div className="ticker-card"> | ||||
|       <div className="ticker-header"> | ||||
|         <h3>{senadoresData.categoriaNombre.replace(' PROVINCIALES', '')}</h3> | ||||
|         <div className="ticker-stats"> | ||||
|           <span>Mesas: <strong>{formatPercent(senadoresData.estadoRecuento?.mesasTotalizadasPorcentaje || 0)}</strong></span> | ||||
|           <span>Part: <strong>{formatPercent(senadoresData.estadoRecuento?.participacionPorcentaje || 0)}</strong></span> | ||||
|         </div> | ||||
|         <h3>SENADORES POR MUNICIPIO</h3> | ||||
|         <Select | ||||
|           options={municipioOptions} | ||||
|           value={selectedMunicipio} | ||||
|           onChange={(option) => setSelectedMunicipio(option)} | ||||
|           isLoading={isLoadingMunicipios} | ||||
|           placeholder="Buscar municipio..." | ||||
|           isClearable | ||||
|           styles={customSelectStyles} | ||||
|         /> | ||||
|       </div> | ||||
|       <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 => ( | ||||
|           <div key={partido.id} className="ticker-party"> | ||||
|             <div className="party-logo"> | ||||
|   | ||||
| @@ -17,20 +17,39 @@ public class CatalogosController : ControllerBase | ||||
|     } | ||||
|  | ||||
|     [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() | ||||
|             .Where(a => a.NivelId == 30 && a.SeccionId != null) | ||||
|             .Select(a => new MunicipioSimpleDto | ||||
|             { | ||||
|                 Id = a.SeccionId!, | ||||
|                 Nombre = a.Nombre | ||||
|             }) | ||||
|             .OrderBy(m => m.Nombre) | ||||
|             .Where(a => a.NivelId == 30); | ||||
|  | ||||
|         // Si NO se proporciona una categoriaId, devolvemos todos (para Concejales). | ||||
|         if (categoriaId == null) | ||||
|         { | ||||
|             var todosLosMunicipios = await municipiosQuery | ||||
|                 .OrderBy(a => a.Nombre) | ||||
|                 .Select(a => new { a.SeccionId, a.Nombre }) // Usamos SeccionId como el ID electoral | ||||
|                 .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(); | ||||
|  | ||||
|         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")] | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "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": { | ||||
|     "LogLevel": { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "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": { | ||||
|     "LogLevel": { | ||||
|   | ||||
| @@ -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+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")] | ||||
| [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":["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.AssemblyConfigurationAttribute("Debug")] | ||||
| [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.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+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")] | ||||
| [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+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")] | ||||
| [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