Feat BancasWidget
This commit is contained in:
		
							
								
								
									
										28
									
								
								Elecciones-Web/frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								Elecciones-Web/frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -9,6 +9,7 @@ | ||||
|       "version": "0.0.0", | ||||
|       "dependencies": { | ||||
|         "@nivo/bar": "^0.99.0", | ||||
|         "@nivo/waffle": "^0.99.0", | ||||
|         "@tanstack/react-query": "^5.85.5", | ||||
|         "@types/d3-geo": "^3.1.0", | ||||
|         "@types/d3-shape": "^3.1.7", | ||||
| @@ -1491,6 +1492,12 @@ | ||||
|         "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@nivo/grid": { | ||||
|       "version": "0.99.0", | ||||
|       "resolved": "https://registry.npmjs.org/@nivo/grid/-/grid-0.99.0.tgz", | ||||
|       "integrity": "sha512-mZiCa8PJ5j8Z7I9lwQvtBvSy++Oyaa/1ztYWQeTaoLxuoUmrpFpc4WK38lETLPzU32EwukZKFNkFp700itmaWw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@nivo/legends": { | ||||
|       "version": "0.99.0", | ||||
|       "resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.99.0.tgz", | ||||
| @@ -1571,6 +1578,27 @@ | ||||
|         "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@nivo/waffle": { | ||||
|       "version": "0.99.0", | ||||
|       "resolved": "https://registry.npmjs.org/@nivo/waffle/-/waffle-0.99.0.tgz", | ||||
|       "integrity": "sha512-3JyC0O/dLjLVqYCigsCfreAgAqFkEFEdSq4MGVdSRnw2Huha6kx8/U3XTF6fRQemvTYnNpXybHd8rlddjtVE/Q==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@nivo/canvas": "0.99.0", | ||||
|         "@nivo/colors": "0.99.0", | ||||
|         "@nivo/core": "0.99.0", | ||||
|         "@nivo/grid": "0.99.0", | ||||
|         "@nivo/legends": "0.99.0", | ||||
|         "@nivo/theming": "0.99.0", | ||||
|         "@nivo/tooltip": "0.99.0", | ||||
|         "@react-spring/web": "9.4.5 || ^9.7.2 || ^10.0", | ||||
|         "@types/d3-shape": "^3.1.6", | ||||
|         "d3-shape": "^3.2.0" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@nodelib/fs.scandir": { | ||||
|       "version": "2.1.5", | ||||
|       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@nivo/bar": "^0.99.0", | ||||
|     "@nivo/waffle": "^0.99.0", | ||||
|     "@tanstack/react-query": "^5.85.5", | ||||
|     "@types/d3-geo": "^3.1.0", | ||||
|     "@types/d3-shape": "^3.1.7", | ||||
|   | ||||
| @@ -89,9 +89,9 @@ export const getResumenProvincial = async (): Promise<CategoriaResumen[]> => { | ||||
|   return response.data; | ||||
| }; | ||||
|  | ||||
| export const getBancasPorSeccion = async (seccionId: string): Promise<ProyeccionBancas> => { | ||||
|   const response = await apiClient.get(`/resultados/bancas/${seccionId}`); | ||||
|   return response.data; | ||||
| export const getBancasPorSeccion = async (seccionId: string, camara: 'diputados' | 'senadores'): Promise<ProyeccionBancas> => { | ||||
|     const { data } = await apiClient.get(`/resultados/bancas-por-seccion/${seccionId}/${camara}`); | ||||
|     return data; | ||||
| }; | ||||
|  | ||||
| /** | ||||
| @@ -184,4 +184,10 @@ export const getMunicipios = async (categoriaId?: number): Promise<MunicipioSimp | ||||
|   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 })); | ||||
| }; | ||||
|  | ||||
| export const getSeccionesElectoralesConCargos = async (): Promise<MunicipioSimple[]> => { | ||||
|   // Hacemos la petición al nuevo endpoint del backend | ||||
|   const { data } = await apiClient.get<MunicipioSimple[]>('/resultados/secciones-electorales-con-cargos'); | ||||
|   return data; | ||||
| }; | ||||
| @@ -1,41 +1,168 @@ | ||||
| /* src/components/BancasWidget.css */ | ||||
| /* src/components/BancasWidget.css | ||||
|  | ||||
| /* Contenedor principal del widget */ | ||||
| .bancas-widget-container { | ||||
|     /* Mismo estilo de tarjeta que el Ticker */ | ||||
|     font-family: 'Roboto', sans-serif; | ||||
|     gap: 1rem; | ||||
|     background-color: #ffffff; | ||||
|     border: 1px solid #e0e0e0; | ||||
|     box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); | ||||
|     padding: 15px 20px; | ||||
|     padding: 1rem; | ||||
|     border-radius: 8px; | ||||
|     max-width: 800px; | ||||
|     margin: 20px auto; | ||||
|     font-family: "Public Sans", system-ui, sans-serif; | ||||
|     color: #333333; | ||||
| } | ||||
|  | ||||
| /* Cabecera */ | ||||
| .bancas-header { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     margin-bottom: 10px; | ||||
|     margin-bottom: 12px; | ||||
| } | ||||
|  | ||||
| .bancas-header h4 { | ||||
|     margin: 0; | ||||
|     color: #212529; | ||||
|     font-size: 1.2em; | ||||
|     font-size: 1.1rem; | ||||
|     font-weight: 700; | ||||
|     color: #212529; | ||||
| } | ||||
|  | ||||
| .bancas-header select { | ||||
|     background-color: #ffffff; | ||||
|     color: #333333; | ||||
|     border: 1px solid #ced4da; /* Borde estándar para inputs */ | ||||
|     border-radius: 4px; | ||||
|     padding: 5px 10px; | ||||
|     font-family: inherit; | ||||
|     font-size: 0.9em; | ||||
| /* Pestañas de Cámara */ | ||||
| .chamber-tabs-bancas { | ||||
|     display: flex; | ||||
|     margin-bottom: 16px; | ||||
| } | ||||
|  | ||||
| .chamber-tabs-bancas button { | ||||
|     flex: 1; | ||||
|     padding: 10px; | ||||
|     font-size: 0.95rem; | ||||
|     font-weight: 500; | ||||
|     text-align: center; | ||||
|     border: none; | ||||
|     background-color: #e9ecef; | ||||
|     color: #495057; | ||||
|     cursor: pointer; | ||||
|     transition: all 0.2s ease-in-out; | ||||
|     border-bottom: 3px solid transparent; | ||||
| } | ||||
|  | ||||
| .chamber-tabs-bancas button:disabled { | ||||
|     color: #adb5bd; | ||||
|     background-color: #f8f9fa; | ||||
|     cursor: not-allowed; | ||||
| } | ||||
|  | ||||
| .chamber-tabs-bancas button:not(:disabled):hover { | ||||
|     background-color: #dee2e6; | ||||
| } | ||||
|  | ||||
| .chamber-tabs-bancas button.active { | ||||
|     background-color: #fff; | ||||
|     color: #007bff; | ||||
|     font-weight: 700; | ||||
|     border-bottom: 3px solid #007bff; | ||||
| } | ||||
|  | ||||
| /* Contenedor del contenido principal */ | ||||
| .bancas-content-container { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     gap: 24px; | ||||
|     /* Un poco más de espacio */ | ||||
|     background-color: #fff; | ||||
|     padding: 16px; | ||||
|     border-radius: 6px; | ||||
| } | ||||
|  | ||||
| /* Contenedor del gráfico de Waffle */ | ||||
| .waffle-chart-container { | ||||
|     height: 300px; | ||||
|     font-family: "Public Sans", system-ui, sans-serif; | ||||
|     flex: 3; | ||||
|     min-width: 250px; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
| } | ||||
|  | ||||
| /* --- NUEVOS ESTILOS PARA AGRUPAR --- */ | ||||
| .waffle-grid-container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     /* Apila los bloques de partido verticalmente */ | ||||
|     gap: 12px; | ||||
|     /* Espacio entre los bloques de cada partido */ | ||||
|     width: 100%; | ||||
| } | ||||
|  | ||||
| .partido-bloque { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     gap: 4px; | ||||
|     /* Espacio entre las celdas de un mismo partido */ | ||||
| } | ||||
|  | ||||
| /* Celdas individuales del Waffle (Bancas) */ | ||||
| .waffle-cell { | ||||
|     width: 20px; | ||||
|     /* Tamaño fijo para las celdas */ | ||||
|     height: 20px; | ||||
|     border-radius: 4px; | ||||
|     border: 1px solid rgba(0, 0, 0, 0.15); | ||||
|     box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.2); | ||||
| } | ||||
|  | ||||
| /* Leyenda de partidos */ | ||||
| .leyenda-container { | ||||
|     flex: 2; | ||||
|     min-width: 200px; | ||||
|     border-left: 1px solid #e9ecef; | ||||
|     padding-left: 20px; | ||||
| } | ||||
|  | ||||
| /* Estilos de la lista de partidos */ | ||||
| .partido-lista-bancas { | ||||
|     list-style: none; | ||||
|     padding: 0; | ||||
|     margin: 0; | ||||
| } | ||||
|  | ||||
| .partido-lista-bancas li { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     margin-bottom: 10px; | ||||
|     font-size: 0.9rem; | ||||
| } | ||||
|  | ||||
| .partido-color-box { | ||||
|     width: 16px; | ||||
|     height: 16px; | ||||
|     border-radius: 3px; | ||||
|     margin-right: 8px; | ||||
|     flex-shrink: 0; | ||||
|     border: 1px solid rgba(0, 0, 0, 0.1); | ||||
| } | ||||
|  | ||||
| .partido-nombre { | ||||
|     flex-grow: 1; | ||||
|     margin-right: 8px; | ||||
|     color: #495057; | ||||
| } | ||||
|  | ||||
| .partido-bancas { | ||||
|     font-weight: 700; | ||||
|     font-size: 1rem; | ||||
|     color: #212529; | ||||
| } | ||||
|  | ||||
| /* Mensajes de carga y error */ | ||||
| .loading-text, | ||||
| .error-text { | ||||
|     width: 100%; | ||||
|     text-align: center; | ||||
|     color: #6c757d; | ||||
|     padding: 20px; | ||||
|     font-style: italic; | ||||
| } | ||||
| @@ -1,119 +1,198 @@ | ||||
| // src/components/BancasWidget.tsx | ||||
| import { useState, useEffect } from 'react'; | ||||
| // src/components/BancasWidget.tsx (Corregido) | ||||
| import { useState, useEffect, useMemo } from 'react'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| import { ResponsiveBar } from '@nivo/bar'; | ||||
| import { getBancasPorSeccion, getSeccionesElectorales } from '../apiService'; | ||||
| import Select from 'react-select'; // --- CAMBIO: Importar react-select --- | ||||
| import { getBancasPorSeccion, getSeccionesElectoralesConCargos } from '../apiService'; | ||||
| import type { ProyeccionBancas, MunicipioSimple } from '../types/types'; | ||||
| import { Tooltip } from 'react-tooltip'; | ||||
| import './BancasWidget.css'; | ||||
| import type { Property } from 'csstype'; | ||||
|  | ||||
| const NIVO_COLORS = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]; | ||||
| type CamaraType = 'diputados' | 'senadores'; | ||||
|  | ||||
| // --- CAMBIO: Estilos para el nuevo selector --- | ||||
| const customSelectStyles = { | ||||
|     control: (base: any) => ({ ...base, minWidth: '200px', border: '1px solid #ced4da', boxShadow: 'none', '&:hover': { borderColor: '#86b7fe' } }), | ||||
|     menu: (base: any) => ({ ...base, zIndex: 10 }), | ||||
| }; | ||||
|  | ||||
| const WaffleDisplay = ({ data }: { data: ProyeccionBancas['proyeccion'] }) => { | ||||
|     // El componente WaffleDisplay no necesita cambios en su lógica | ||||
|     return ( | ||||
|         <div className="waffle-grid-container"> | ||||
|             {data.map(partido => ( | ||||
|                 partido.bancas > 0 && ( | ||||
|                     <div | ||||
|                         key={partido.agrupacionId} | ||||
|                         className="partido-bloque" | ||||
|                         data-tooltip-id="banca-tooltip" | ||||
|                         data-tooltip-content={`${partido.nombreCorto || partido.agrupacionNombre}: ${partido.bancas} bancas`} | ||||
|                     > | ||||
|                         {Array.from({ length: partido.bancas }).map((_, index) => ( | ||||
|                             <div | ||||
|                                 key={index} | ||||
|                                 className="waffle-cell" | ||||
|                                 style={{ backgroundColor: partido.color as Property.BackgroundColor || '#cccccc' }} | ||||
|                             /> | ||||
|                         ))} | ||||
|                     </div> | ||||
|                 ) | ||||
|             ))} | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| export const BancasWidget = () => { | ||||
|     const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); | ||||
|     const [seccionActual, setSeccionActual] = useState<string>(''); | ||||
|     // --- CAMBIO: Adaptar el estado para react-select --- | ||||
|     const [selectedSeccion, setSelectedSeccion] = useState<{ value: string; label: string } | null>(null); | ||||
|     const [camaraActiva, setCamaraActiva] = useState<CamaraType>('diputados'); | ||||
|  | ||||
|     // useEffect para cargar la lista de secciones una sola vez (esto está bien) | ||||
|     useEffect(() => { | ||||
|         const fetchSecciones = async () => { | ||||
|             try { | ||||
|                 const seccionesData = await getSeccionesElectorales(); | ||||
|                 const seccionesData = await getSeccionesElectoralesConCargos(); | ||||
|                 if (seccionesData && seccionesData.length > 0) { | ||||
|                     // --- 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; | ||||
|                     }; | ||||
|                      | ||||
|                     // Ordenamos el array de datos ANTES de guardarlo en el estado | ||||
|                     seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre)); | ||||
|  | ||||
|                     setSecciones(seccionesData); | ||||
|                     setSeccionActual(seccionesData[0].id); | ||||
|  | ||||
|                     if (!selectedSeccion) { | ||||
|                         setSelectedSeccion({ value: seccionesData[0].id, label: seccionesData[0].nombre }); | ||||
|                     } | ||||
|                 } | ||||
|             } catch (err) { | ||||
|                 console.error("Error cargando secciones electorales:", err); | ||||
|                 // Opcional: manejar el error en la UI si la carga de secciones falla | ||||
|             } | ||||
|         }; | ||||
|         fetchSecciones(); | ||||
|     }, []); | ||||
|     }, [selectedSeccion]); | ||||
|  | ||||
|     // --- CÓDIGO REFACTORIZADO --- | ||||
|     // Eliminamos los useState para data, loading y error | ||||
|     // --- CAMBIO: Formatear opciones para react-select --- | ||||
|     const seccionOptions = useMemo(() => | ||||
|         secciones.map(s => ({ value: s.id, label: s.nombre })), | ||||
|         [secciones]); | ||||
|  | ||||
|     // useQuery ahora es la única fuente de verdad para los datos de bancas | ||||
|     const {  | ||||
|         data,  | ||||
|         isLoading,  | ||||
|         error  | ||||
|     const seccionSeleccionada = useMemo(() => | ||||
|         secciones.find(s => s.id === selectedSeccion?.value), | ||||
|         [secciones, selectedSeccion]); | ||||
|  | ||||
|     const camarasDisponibles = useMemo(() => | ||||
|         seccionSeleccionada?.camarasDisponibles || [], | ||||
|         [seccionSeleccionada]); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         if (seccionSeleccionada && camarasDisponibles.length > 0) { | ||||
|             if (!camarasDisponibles.includes(camaraActiva)) { | ||||
|                 setCamaraActiva(camarasDisponibles[0]); | ||||
|             } | ||||
|         } | ||||
|     }, [seccionSeleccionada, camarasDisponibles, camaraActiva]); | ||||
|  | ||||
|     const { | ||||
|         data, | ||||
|         isLoading, | ||||
|         error | ||||
|     } = useQuery<ProyeccionBancas, Error>({ | ||||
|         queryKey: ['bancasPorSeccion', seccionActual], | ||||
|         queryFn: () => getBancasPorSeccion(seccionActual), | ||||
|         enabled: !!seccionActual, | ||||
|         queryKey: ['bancasPorSeccion', selectedSeccion?.value, camaraActiva], | ||||
|         queryFn: () => getBancasPorSeccion(selectedSeccion!.value, camaraActiva), | ||||
|         enabled: !!selectedSeccion && camarasDisponibles.includes(camaraActiva), | ||||
|         retry: (failureCount, error: any) => { | ||||
|             if (error.response?.status === 404) return false; | ||||
|             return failureCount < 3; | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
|     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 `La proyección para ${camaraActiva} en esta sección aún no está disponible.`; | ||||
|             } | ||||
|             return "No se pudo conectar para obtener los datos de bancas."; | ||||
|             return "No se pudo conectar para obtener los datos."; | ||||
|         } | ||||
|         return null; | ||||
|     }; | ||||
|  | ||||
|     const errorMessage = getErrorMessage(); | ||||
|  | ||||
|     // --- CAMBIO: Ordenar la leyenda (y por lo tanto el gráfico) de más a menos bancas --- | ||||
|     const leyendaData = useMemo(() => | ||||
|         data?.proyeccion | ||||
|             .filter(p => p.bancas > 0) | ||||
|             .sort((a, b) => b.bancas - a.bancas) // Ordena de mayor a menor | ||||
|         || [], | ||||
|         [data]); | ||||
|  | ||||
|     const totalBancasEnJuego = useMemo(() => | ||||
|         data?.proyeccion.reduce((sum, p) => sum + p.bancas, 0) || 0, | ||||
|         [data]); | ||||
|  | ||||
|     return ( | ||||
|         <div className="bancas-widget-container"> | ||||
|             <div className="bancas-header"> | ||||
|                 <h4>Distribución de Bancas</h4> | ||||
|                 <select value={seccionActual} onChange={e => setSeccionActual(e.target.value)} disabled={secciones.length === 0}> | ||||
|                     {secciones.map(s => <option key={s.id} value={s.id}>{s.nombre}</option>)} | ||||
|                 </select> | ||||
|                 <h4>Bancas Proyectadas: {totalBancasEnJuego}</h4> | ||||
|                 <Select | ||||
|                     options={seccionOptions} | ||||
|                     value={selectedSeccion} | ||||
|                     onChange={(option) => setSelectedSeccion(option)} | ||||
|                     isLoading={secciones.length === 0} | ||||
|                     placeholder="Seleccionar..." | ||||
|                     styles={customSelectStyles} | ||||
|                     isSearchable={false} | ||||
|                 /> | ||||
|             </div> | ||||
|             <div className="waffle-chart-container"> | ||||
|                 {isLoading ? <p>Cargando...</p> : errorMessage ? <p>{errorMessage}</p> : | ||||
|                 <ResponsiveBar | ||||
|                     data={barData} | ||||
|                     keys={['bancas']} | ||||
|                     indexBy="agrupacionNombre" | ||||
|                     layout="horizontal" | ||||
|                     margin={{ top: 10, right: 30, bottom: 25, left: 160 }} | ||||
|                     padding={0.3} | ||||
|                     valueScale={{ type: 'linear' }} | ||||
|                     indexScale={{ type: 'band', round: true }} | ||||
|                     colors={({ index }) => NIVO_COLORS[index % NIVO_COLORS.length]} | ||||
|                     borderColor={{ from: 'color', modifiers: [['darker', 1.6]] }} | ||||
|                     axisTop={null} | ||||
|                     axisRight={null} | ||||
|                     axisBottom={{ | ||||
|                         tickSize: 5, | ||||
|                         tickPadding: 5, | ||||
|                         tickRotation: 0, | ||||
|                         legend: 'Cantidad de Bancas', | ||||
|                         legendPosition: 'middle', | ||||
|                         legendOffset: 20, | ||||
|                         format: (value) => Math.floor(value) === value ? value : '' | ||||
|                     }} | ||||
|                     axisLeft={{ | ||||
|                         tickSize: 5, | ||||
|                         tickPadding: 5, | ||||
|                         tickRotation: 0, | ||||
|                     }} | ||||
|                     labelSkipWidth={12} | ||||
|                     labelSkipHeight={12} | ||||
|                     labelTextColor={{ from: 'color', modifiers: [['darker', 3]] }} | ||||
|                     animate={true} | ||||
|                     legends={[]} | ||||
|                 />} | ||||
|  | ||||
|             <div className="chamber-tabs-bancas"> | ||||
|                 <button | ||||
|                     className={camaraActiva === 'diputados' ? 'active' : ''} | ||||
|                     onClick={() => setCamaraActiva('diputados')} | ||||
|                     disabled={seccionSeleccionada && !camarasDisponibles.includes('diputados')} | ||||
|                 > | ||||
|                     Diputados | ||||
|                 </button> | ||||
|                 <button | ||||
|                     className={camaraActiva === 'senadores' ? 'active' : ''} | ||||
|                     onClick={() => setCamaraActiva('senadores')} | ||||
|                     disabled={seccionSeleccionada && !camarasDisponibles.includes('senadores')} | ||||
|                 > | ||||
|                     Senadores | ||||
|                 </button> | ||||
|             </div> | ||||
|  | ||||
|             <div className="bancas-content-container"> | ||||
|                 <div className="waffle-chart-container"> | ||||
|                     {isLoading ? <p className="loading-text">Cargando...</p> : | ||||
|                         errorMessage ? <p className="error-text">{errorMessage}</p> : | ||||
|                             totalBancasEnJuego > 0 ? <WaffleDisplay data={leyendaData} /> : | ||||
|                                 <p>No hay bancas proyectadas para mostrar.</p> | ||||
|                     } | ||||
|                 </div> | ||||
|                 <div className="leyenda-container"> | ||||
|                     <ul className="partido-lista-bancas"> | ||||
|                         {leyendaData.map(partido => ( | ||||
|                             <li key={partido.agrupacionId}> | ||||
|                                 <span className="partido-color-box" style={{ backgroundColor: partido.color as Property.BackgroundColor || '#cccccc' }}></span> | ||||
|                                 <span className="partido-nombre"> | ||||
|                                     {partido.nombreCorto || partido.agrupacionNombre} | ||||
|                                 </span> | ||||
|                                 <strong className="partido-bancas">{partido.bancas}</strong> | ||||
|                             </li> | ||||
|                         ))} | ||||
|                     </ul> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <Tooltip id="banca-tooltip" /> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| @@ -39,7 +39,7 @@ export interface GeographyObject { | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export interface MunicipioSimple { id: string; nombre: string; } | ||||
| export interface MunicipioSimple { id: string; nombre: string; camarasDisponibles?: ('diputados' | 'senadores')[];} | ||||
|  | ||||
| export interface ResultadoTicker { | ||||
|   id: string; | ||||
|   | ||||
| @@ -160,15 +160,29 @@ public class ResultadosController : ControllerBase | ||||
|     } | ||||
|  | ||||
|  | ||||
|     [HttpGet("bancas/{seccionId}")] | ||||
|     public async Task<IActionResult> GetBancasPorSeccion(string seccionId) | ||||
|     [HttpGet("bancas-por-seccion/{seccionId}/{camara}")] // <-- CAMBIO 1: Modificar la ruta | ||||
|     public async Task<IActionResult> GetBancasPorSeccion(string seccionId, string camara) // <-- CAMBIO 2: Añadir el nuevo parámetro | ||||
|     { | ||||
|         // 1. Buscamos el ámbito usando 'SeccionProvincialId'. | ||||
|         // La API oficial usa este campo para las secciones electorales. | ||||
|         // Además, el worker guarda estas secciones con NivelId = 20, por lo que lo usamos aquí para consistencia. | ||||
|         // Convertimos el string de la cámara a un enum o un valor numérico para la base de datos | ||||
|         // 0 = Diputados, 1 = Senadores. Esto debe coincidir con cómo lo guardas en la DB. | ||||
|         // O puedes usar un enum si lo tienes definido. | ||||
|         int CategoriaId; | ||||
|         if (camara.Equals("diputados", StringComparison.OrdinalIgnoreCase)) | ||||
|         { | ||||
|             CategoriaId = 6; // Asume que 5 es el CategoriaId para Diputados Provinciales | ||||
|         } | ||||
|         else if (camara.Equals("senadores", StringComparison.OrdinalIgnoreCase)) | ||||
|         { | ||||
|             CategoriaId = 5; // Asume que 6 es el CategoriaId para Senadores Provinciales | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return BadRequest(new { message = "El tipo de cámara especificado no es válido. Use 'diputados' o 'senadores'." }); | ||||
|         } | ||||
|  | ||||
|         var seccion = await _dbContext.AmbitosGeograficos | ||||
|             .AsNoTracking() | ||||
|             .FirstOrDefaultAsync(a => a.SeccionProvincialId == seccionId && a.NivelId == 20); // Nivel 20 = Sección Electoral Provincial | ||||
|             .FirstOrDefaultAsync(a => a.SeccionProvincialId == seccionId && a.NivelId == 20); | ||||
|  | ||||
|         if (seccion == null) | ||||
|         { | ||||
| @@ -176,14 +190,17 @@ public class ResultadosController : ControllerBase | ||||
|             return NotFound(new { message = $"No se encontró la sección electoral con ID {seccionId}" }); | ||||
|         } | ||||
|  | ||||
|         // 2. Buscamos todas las proyecciones para ese ámbito (usando su clave primaria 'Id') | ||||
|         // --- CAMBIO 3: Filtrar también por el cargo (cámara) --- | ||||
|         var proyecciones = await _dbContext.ProyeccionesBancas | ||||
|             .AsNoTracking() | ||||
|             .Include(p => p.AgrupacionPolitica) | ||||
|             .Where(p => p.AmbitoGeograficoId == seccion.Id) | ||||
|             .Where(p => p.AmbitoGeograficoId == seccion.Id && p.CategoriaId == CategoriaId) // <-- AÑADIDO EL FILTRO | ||||
|             .Select(p => new | ||||
|             { | ||||
|                 AgrupacionId = p.AgrupacionPolitica.Id, // Añadir para el 'key' en React | ||||
|                 AgrupacionNombre = p.AgrupacionPolitica.Nombre, | ||||
|                 NombreCorto = p.AgrupacionPolitica.NombreCorto, // Añadir para el frontend | ||||
|                 Color = p.AgrupacionPolitica.Color, // Añadir para el frontend | ||||
|                 Bancas = p.NroBancas | ||||
|             }) | ||||
|             .OrderByDescending(p => p.Bancas) | ||||
| @@ -191,12 +208,10 @@ public class ResultadosController : ControllerBase | ||||
|  | ||||
|         if (!proyecciones.Any()) | ||||
|         { | ||||
|             // Este caso es posible si aún no hay proyecciones calculadas para esta sección. | ||||
|             _logger.LogWarning("No se encontraron proyecciones de bancas para la sección: {SeccionNombre}", seccion.Nombre); | ||||
|             return NotFound(new { message = $"No se han encontrado proyecciones de bancas para la sección {seccion.Nombre}" }); | ||||
|             _logger.LogWarning("No se encontraron proyecciones de bancas para la sección: {SeccionNombre} y cámara: {Camara}", seccion.Nombre, camara); | ||||
|             return NotFound(new { message = $"No se han encontrado proyecciones de bancas para la sección {seccion.Nombre} ({camara})" }); | ||||
|         } | ||||
|  | ||||
|         // 3. Devolvemos la respuesta | ||||
|         return Ok(new | ||||
|         { | ||||
|             SeccionNombre = seccion.Nombre, | ||||
| @@ -721,4 +736,41 @@ public class ResultadosController : ControllerBase | ||||
|  | ||||
|         return Ok(resultadoFinal); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("secciones-electorales-con-cargos")] | ||||
|     public async Task<IActionResult> GetSeccionesElectoralesConCargos() | ||||
|     { | ||||
|         var secciones = await _dbContext.AmbitosGeograficos | ||||
|             .AsNoTracking() | ||||
|             .Where(a => a.NivelId == 20) // Nivel 20 = Sección Electoral Provincial | ||||
|             .Select(a => new | ||||
|             { | ||||
|                 Id = a.SeccionProvincialId, // Usamos el ID que el frontend espera | ||||
|                 Nombre = a.Nombre, | ||||
|                 // Obtenemos los CategoriaId de las relaciones que tiene esta sección. | ||||
|                 // Esto asume que tienes una tabla que relaciona Ámbitos con Cargos. | ||||
|                 // Por ejemplo, a través de la tabla de Proyecciones o Resultados. | ||||
|                 Cargos = _dbContext.ProyeccionesBancas | ||||
|                             .Where(p => p.AmbitoGeograficoId == a.Id) | ||||
|                             .Select(p => p.CategoriaId) | ||||
|                             .Distinct() | ||||
|                             .ToList() | ||||
|             }) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         // Mapeamos los CategoriaId a los nombres que usa el frontend | ||||
|         var resultado = secciones.Select(s => new | ||||
|         { | ||||
|             s.Id, | ||||
|             s.Nombre, | ||||
|             // Convertimos la lista de IDs de cargo a una lista de strings ("diputados", "senadores") | ||||
|             CamarasDisponibles = s.Cargos.Select(CategoriaId => | ||||
|                 CategoriaId == 6 ? "diputados" : // Asume 5 = Diputados | ||||
|                 CategoriaId == 5 ? "senadores" : // Asume 6 = Senadores | ||||
|                 null | ||||
|             ).Where(c => c != null).ToList() | ||||
|         }); | ||||
|  | ||||
|         return Ok(resultado); | ||||
|     } | ||||
| } | ||||
| @@ -8,32 +8,6 @@ | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   "ComposicionCongreso": { | ||||
|     "Diputados": { | ||||
|       "CamaraNombre": "Cámara de Diputados", | ||||
|       "TotalBancas": 92, | ||||
|       "BancasEnJuego": 46, | ||||
|       "Partidos": [ | ||||
|         { "Id": "501", "Nombre": "CANDIDATURA 501", "BancasTotales": 2, "BancasEnJuego": 1, "Color": "#d62728" }, | ||||
|         { "Id": "513", "Nombre": "CANDIDATURA 513", "BancasTotales": 37, "BancasEnJuego": 19, "Color": "#1f77b4" }, | ||||
|         { "Id": "516", "Nombre": "CANDIDATURA 516", "BancasTotales": 18, "BancasEnJuego": 8, "Color": "#2ca02c" }, | ||||
|         { "Id": "511", "Nombre": "CANDIDATURA 511", "BancasTotales": 22, "BancasEnJuego": 12, "Color": "#ff7f0e" }, | ||||
|         { "Id": "507", "Nombre": "CANDIDATURA 507", "BancasTotales": 13, "BancasEnJuego": 6, "Color": "#9467bd" } | ||||
|       ] | ||||
|     }, | ||||
|     "Senadores": { | ||||
|       "CamaraNombre": "Cámara de Senadores", | ||||
|       "TotalBancas": 46, | ||||
|       "BancasEnJuego": 23, | ||||
|       "Partidos": [ | ||||
|         { "Id": "513_S", "Nombre": "CANDIDATURA 513", "BancasTotales": 21, "BancasEnJuego": 10, "Color": "#1f77b4" }, | ||||
|         { "Id": "516_S", "Nombre": "CANDIDATURA 516", "BancasTotales": 9, "BancasEnJuego": 5, "Color": "#2ca02c" }, | ||||
|         { "Id": "511_S", "Nombre": "CANDIDATURA 511", "BancasTotales": 11, "BancasEnJuego": 6, "Color": "#ff7f0e" }, | ||||
|         { "Id": "507_S", "Nombre": "CANDIDATURA 507", "BancasTotales": 5, "BancasEnJuego": 2, "Color": "#9467bd" } | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   "ApiKey": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2", | ||||
|   "Jwt": { | ||||
|     "Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2", | ||||
|   | ||||
| @@ -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+32e85b9b9ddc7cd6422bfa52ff3c17fc6eff930b")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8ce48b3a46c65b51238a04238228d86568680ac2")] | ||||
| [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":["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=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","6UbV83RDqihxiPJRSqBBce97kpcbB7parfVbqy/JDrA=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","4a8hgDNqfLAz7U5813iwPnUccUabxTlRH1USsCHCuGA="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| {"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","B5JL8yyHqNgMsFpbZP0qsuF4OLsILfK8gQJe8eGu3B0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","NNlKPM7/7JjN5EwDTVhjwjUKTnDOuh1gXx0T83PZFzk="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| @@ -1 +1 @@ | ||||
| {"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=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","6UbV83RDqihxiPJRSqBBce97kpcbB7parfVbqy/JDrA=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","4a8hgDNqfLAz7U5813iwPnUccUabxTlRH1USsCHCuGA="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| {"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","B5JL8yyHqNgMsFpbZP0qsuF4OLsILfK8gQJe8eGu3B0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","NNlKPM7/7JjN5EwDTVhjwjUKTnDOuh1gXx0T83PZFzk="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| @@ -1 +1 @@ | ||||
| {"GlobalPropertiesHash":"O7YawHw32G/Fh2bs+snZgm9O7okI0WYgTQmXM931znY=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| {"GlobalPropertiesHash":"O7YawHw32G/Fh2bs+snZgm9O7okI0WYgTQmXM931znY=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","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+32e85b9b9ddc7cd6422bfa52ff3c17fc6eff930b")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8ce48b3a46c65b51238a04238228d86568680ac2")] | ||||
| [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+32e85b9b9ddc7cd6422bfa52ff3c17fc6eff930b")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8ce48b3a46c65b51238a04238228d86568680ac2")] | ||||
| [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+32e85b9b9ddc7cd6422bfa52ff3c17fc6eff930b")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8ce48b3a46c65b51238a04238228d86568680ac2")] | ||||
| [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