Feat Widgets
This commit is contained in:
		| @@ -1,113 +1,133 @@ | ||||
| // src/components/AgrupacionesManager.tsx | ||||
| import { useState, useEffect } from 'react'; | ||||
| import { getAgrupaciones, updateAgrupacion } from '../services/apiService'; | ||||
| import type { AgrupacionPolitica, UpdateAgrupacionData } from '../types'; | ||||
| import { useQuery, useQueryClient } from '@tanstack/react-query'; | ||||
| import { getAgrupaciones, updateAgrupacion, getLogos, updateLogos } from '../services/apiService'; | ||||
| import type { AgrupacionPolitica, LogoAgrupacionCategoria } from '../types'; | ||||
| import './AgrupacionesManager.css'; | ||||
|  | ||||
| const SENADORES_ID = 5; | ||||
| const DIPUTADOS_ID = 6; | ||||
| const CONCEJALES_ID = 7; | ||||
|  | ||||
| export const AgrupacionesManager = () => { | ||||
|     const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]); | ||||
|     const [loading, setLoading] = useState(true); | ||||
|     const [error, setError] = useState<string | null>(null); | ||||
|     const [editingId, setEditingId] = useState<string | null>(null); | ||||
|     const [formData, setFormData] = useState<UpdateAgrupacionData>({ | ||||
|         nombreCorto: '', | ||||
|         color: '#000000', | ||||
|         logoUrl: '', | ||||
|     const queryClient = useQueryClient(); | ||||
|      | ||||
|     const [editedAgrupaciones, setEditedAgrupaciones] = useState<Record<string, Partial<AgrupacionPolitica>>>({}); | ||||
|     const [editedLogos, setEditedLogos] = useState<LogoAgrupacionCategoria[]>([]); | ||||
|  | ||||
|     // Query 1: Obtener agrupaciones | ||||
|     const { data: agrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery<AgrupacionPolitica[]>({ | ||||
|         queryKey: ['agrupaciones'], | ||||
|         queryFn: getAgrupaciones, | ||||
|     }); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         fetchAgrupaciones(); | ||||
|     }, []); | ||||
|     // Query 2: Obtener logos | ||||
|     const { data: logos = [], isLoading: isLoadingLogos } = useQuery<LogoAgrupacionCategoria[]>({ | ||||
|         queryKey: ['logos'], | ||||
|         queryFn: getLogos, | ||||
|     }); | ||||
|  | ||||
|     const fetchAgrupaciones = async () => { | ||||
|         try { | ||||
|             setLoading(true); | ||||
|             const data = await getAgrupaciones(); | ||||
|             setAgrupaciones(data); | ||||
|         } catch (err) { | ||||
|             setError('No se pudieron cargar las agrupaciones.'); | ||||
|         } finally { | ||||
|             setLoading(false); | ||||
|     // Usamos useEffect para reaccionar cuando los datos de 'logos' se cargan o cambian. | ||||
|     useEffect(() => { | ||||
|         if (logos) { | ||||
|             setEditedLogos(logos); | ||||
|         } | ||||
|     }, [logos]); | ||||
|  | ||||
|     // Usamos otro useEffect para reaccionar a los datos de 'agrupaciones'. | ||||
|     useEffect(() => { | ||||
|         if (agrupaciones) { | ||||
|             const initialEdits = Object.fromEntries(agrupaciones.map(a => [a.id, {}])); | ||||
|             setEditedAgrupaciones(initialEdits); | ||||
|         } | ||||
|     }, [agrupaciones]); | ||||
|  | ||||
|     const handleInputChange = (id: string, field: 'nombreCorto' | 'color', value: string) => { | ||||
|         setEditedAgrupaciones(prev => ({ | ||||
|             ...prev, | ||||
|             [id]: { ...prev[id], [field]: value } | ||||
|         })); | ||||
|     }; | ||||
|  | ||||
|     const handleEdit = (agrupacion: AgrupacionPolitica) => { | ||||
|         setEditingId(agrupacion.id); | ||||
|         setFormData({ | ||||
|             nombreCorto: agrupacion.nombreCorto || '', | ||||
|             color: agrupacion.color || '#000000', | ||||
|             logoUrl: agrupacion.logoUrl || '', | ||||
|     const handleLogoChange = (agrupacionId: string, categoriaId: number, value: string) => { | ||||
|         setEditedLogos(prev => { | ||||
|             const newLogos = [...prev]; | ||||
|             const existing = newLogos.find(l => l.agrupacionPoliticaId === agrupacionId && l.categoriaId === categoriaId); | ||||
|             if (existing) { | ||||
|                 existing.logoUrl = value; | ||||
|             } else { | ||||
|                 newLogos.push({ id: 0, agrupacionPoliticaId: agrupacionId, categoriaId, logoUrl: value }); | ||||
|             } | ||||
|             return newLogos; | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     const handleCancel = () => { | ||||
|         setEditingId(null); | ||||
|     }; | ||||
|  | ||||
|     const handleSave = async (id: string) => { | ||||
|     const handleSaveAll = async () => { | ||||
|         try { | ||||
|             await updateAgrupacion(id, formData); | ||||
|             setEditingId(null); | ||||
|             fetchAgrupaciones(); // Recargar datos para ver los cambios | ||||
|             const agrupacionPromises = Object.entries(editedAgrupaciones).map(([id, changes]) => { | ||||
|                 if (Object.keys(changes).length > 0) { | ||||
|                     const original = agrupaciones.find(a => a.id === id); | ||||
|                     if (original) { // Chequeo de seguridad | ||||
|                         return updateAgrupacion(id, { ...original, ...changes }); | ||||
|                     } | ||||
|                 } | ||||
|                 return Promise.resolve(); | ||||
|             }); | ||||
|  | ||||
|             const logoPromise = updateLogos(editedLogos); | ||||
|              | ||||
|             await Promise.all([...agrupacionPromises, logoPromise]); | ||||
|              | ||||
|             queryClient.invalidateQueries({ queryKey: ['agrupaciones'] }); | ||||
|             queryClient.invalidateQueries({ queryKey: ['logos'] }); | ||||
|  | ||||
|             alert('¡Todos los cambios han sido guardados!'); | ||||
|         } catch (err) { | ||||
|             alert('Error al guardar los cambios.'); | ||||
|             console.error("Error al guardar todo:", err); | ||||
|             alert("Ocurrió un error al guardar los cambios."); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||
|         setFormData({ ...formData, [e.target.name]: e.target.value }); | ||||
|     const isLoading = isLoadingAgrupaciones || isLoadingLogos; | ||||
|  | ||||
|     const getLogoUrl = (agrupacionId: string, categoriaId: number) => { | ||||
|         return editedLogos.find(l => l.agrupacionPoliticaId === agrupacionId && l.categoriaId === categoriaId)?.logoUrl || ''; | ||||
|     }; | ||||
|      | ||||
|     if (loading) return <p>Cargando agrupaciones...</p>; | ||||
|     if (error) return <p style={{ color: 'red' }}>{error}</p>; | ||||
|  | ||||
|     return ( | ||||
|         <div className="admin-module"> | ||||
|             <h3>Gestión de Agrupaciones Políticas</h3> | ||||
|             <table> | ||||
|                 <thead> | ||||
|                     <tr> | ||||
|                         <th>Nombre</th> | ||||
|                         <th>Nombre Corto</th> | ||||
|                         <th>Color</th> | ||||
|                         <th>Logo URL</th> | ||||
|                         <th>Acciones</th> | ||||
|                     </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                     {agrupaciones.map((agrupacion) => ( | ||||
|                         <tr key={agrupacion.id}> | ||||
|                             {editingId === agrupacion.id ? ( | ||||
|                                 <> | ||||
|             <h3>Gestión de Agrupaciones y Logos</h3> | ||||
|             {isLoading ? <p>Cargando...</p> : ( | ||||
|                 <> | ||||
|                     <table> | ||||
|                         <thead> | ||||
|                             <tr> | ||||
|                                 <th>Nombre</th> | ||||
|                                 <th>Nombre Corto</th> | ||||
|                                 <th>Color</th> | ||||
|                                 <th>Logo Senadores</th> | ||||
|                                 <th>Logo Diputados</th> | ||||
|                                 <th>Logo Concejales</th> | ||||
|                             </tr> | ||||
|                         </thead> | ||||
|                         <tbody> | ||||
|                             {agrupaciones.map(agrupacion => ( | ||||
|                                 <tr key={agrupacion.id}> | ||||
|                                     <td>{agrupacion.nombre}</td> | ||||
|                                     <td><input type="text" name="nombreCorto" value={formData.nombreCorto || ''} onChange={handleChange} /></td> | ||||
|                                     <td><input type="color" name="color" value={formData.color || '#000000'} onChange={handleChange} /></td> | ||||
|                                     <td><input type="text" name="logoUrl" value={formData.logoUrl || ''} onChange={handleChange} /></td> | ||||
|                                     <td> | ||||
|                                         <button onClick={() => handleSave(agrupacion.id)}>Guardar</button> | ||||
|                                         <button onClick={handleCancel}>Cancelar</button> | ||||
|                                     </td> | ||||
|                                 </> | ||||
|                             ) : ( | ||||
|                                 <> | ||||
|                                     <td>{agrupacion.nombre}</td> | ||||
|                                     <td>{agrupacion.nombreCorto}</td> | ||||
|                                     <td> | ||||
|                                         <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> | ||||
|                                             <div style={{ width: '20px', height: '20px', backgroundColor: agrupacion.color || 'transparent', border: '1px solid #ccc' }}></div> | ||||
|                                             {agrupacion.color} | ||||
|                                         </div> | ||||
|                                     </td> | ||||
|                                     <td>{agrupacion.logoUrl}</td> | ||||
|                                     <td> | ||||
|                                         <button onClick={() => handleEdit(agrupacion)}>Editar</button> | ||||
|                                     </td> | ||||
|                                 </> | ||||
|                             )} | ||||
|                         </tr> | ||||
|                     ))} | ||||
|                 </tbody> | ||||
|             </table> | ||||
|                                     <td><input type="text" value={editedAgrupaciones[agrupacion.id]?.nombreCorto ?? agrupacion.nombreCorto ?? ''} onChange={(e) => handleInputChange(agrupacion.id, 'nombreCorto', e.target.value)} /></td> | ||||
|                                     <td><input type="color" value={editedAgrupaciones[agrupacion.id]?.color ?? agrupacion.color ?? '#000000'} onChange={(e) => handleInputChange(agrupacion.id, 'color', e.target.value)} /></td> | ||||
|                                     <td><input type="text" placeholder="URL de la imagen" value={getLogoUrl(agrupacion.id, SENADORES_ID)} onChange={(e) => handleLogoChange(agrupacion.id, SENADORES_ID, e.target.value)} /></td> | ||||
|                                     <td><input type="text" placeholder="URL de la imagen" value={getLogoUrl(agrupacion.id, DIPUTADOS_ID)} onChange={(e) => handleLogoChange(agrupacion.id, DIPUTADOS_ID, e.target.value)} /></td> | ||||
|                                     <td><input type="text" placeholder="URL de la imagen" value={getLogoUrl(agrupacion.id, CONCEJALES_ID)} onChange={(e) => handleLogoChange(agrupacion.id, CONCEJALES_ID, e.target.value)} /></td> | ||||
|                                 </tr> | ||||
|                             ))} | ||||
|                         </tbody> | ||||
|                     </table> | ||||
|                     <button onClick={handleSaveAll} style={{ marginTop: '1rem' }}> | ||||
|                         Guardar Todos los Cambios | ||||
|                     </button> | ||||
|                 </> | ||||
|             )} | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| @@ -10,6 +10,8 @@ export const ConfiguracionGeneral = () => { | ||||
|     const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]); | ||||
|     const [loading, setLoading] = useState(true); | ||||
|     const [error, setError] = useState<string | null>(null); | ||||
|     const [tickerCantidad, setTickerCantidad] = useState('3'); | ||||
|     const [concejalesCantidad, setConcejalesCantidad] = useState('5'); | ||||
|  | ||||
|     const [presidenciaSenadoId, setPresidenciaSenadoId] = useState<string>(''); | ||||
|     // Renombramos el estado para mayor claridad | ||||
| @@ -24,6 +26,8 @@ export const ConfiguracionGeneral = () => { | ||||
|                 setAgrupaciones(agrupacionesData); | ||||
|                 setPresidenciaSenadoId(configData.PresidenciaSenadores || ''); | ||||
|                 setModoOficialActivo(configData.UsarDatosDeBancadasOficiales === 'true'); | ||||
|                 setTickerCantidad(configData.TickerResultadosCantidad || '3'); | ||||
|                 setConcejalesCantidad(configData.ConcejalesResultadosCantidad || '5'); | ||||
|             } catch (err) { | ||||
|                 console.error("Error al cargar datos de configuración:", err); | ||||
|                 setError("No se pudieron cargar los datos necesarios para la configuración."); | ||||
| @@ -36,7 +40,9 @@ export const ConfiguracionGeneral = () => { | ||||
|         try { | ||||
|             await updateConfiguracion({ | ||||
|                 "PresidenciaSenadores": presidenciaSenadoId, | ||||
|                 "UsarDatosDeBancadasOficiales": modoOficialActivo.toString() | ||||
|                 "UsarDatosDeBancadasOficiales": modoOficialActivo.toString(), | ||||
|                 "TickerResultadosCantidad": tickerCantidad, | ||||
|                 "ConcejalesResultadosCantidad": concejalesCantidad | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ queryKey: ['composicionCongreso'] }); | ||||
|             await queryClient.invalidateQueries({ queryKey: ['bancadasDetalle'] }); | ||||
| @@ -89,7 +95,7 @@ export const ConfiguracionGeneral = () => { | ||||
|                     Seleccione el partido político al que pertenece el Vicegobernador. El asiento presidencial del Senado se pintará con el color de este partido. | ||||
|                 </p> | ||||
|             </div> | ||||
|             <div style={{ marginTop: '1rem' }}> | ||||
|             <div style={{ marginTop: '1rem', borderBottom: '2px solid #eee' }}> | ||||
|                 <p style={{ fontWeight: 'bold', margin: 0 }}> | ||||
|                     Presidencia Cámara de Diputados | ||||
|                 </p> | ||||
| @@ -97,6 +103,19 @@ export const ConfiguracionGeneral = () => { | ||||
|                     Esta banca se asigna y colorea automáticamente según la agrupación política con la mayoría de bancas totales en la cámara. | ||||
|                 </p> | ||||
|             </div> | ||||
|             <div className="form-group" style={{ marginTop: '2rem' }}> | ||||
|                 <label htmlFor="ticker-cantidad">Cantidad en Ticker (Dip/Sen)</label> | ||||
|                 <input id="ticker-cantidad" type="number" value={tickerCantidad} onChange={e => setTickerCantidad(e.target.value)} /> | ||||
|             </div> | ||||
|             <div className="form-group" style={{ marginTop: '2rem' }}> | ||||
|                 <label htmlFor="concejales-cantidad">Cantidad en Widget Concejales</label> | ||||
|                 <input  | ||||
|                     id="concejales-cantidad" | ||||
|                     type="number"  | ||||
|                     value={concejalesCantidad} | ||||
|                     onChange={e => setConcejalesCantidad(e.target.value)} | ||||
|                 /> | ||||
|             </div> | ||||
|             <button onClick={handleSave} style={{ marginTop: '1.5rem' }}> | ||||
|                 Guardar Configuración | ||||
|             </button> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // src/services/apiService.ts | ||||
| import axios from 'axios'; | ||||
| import { triggerLogout } from '../context/authUtils'; | ||||
| import type { AgrupacionPolitica, UpdateAgrupacionData, Bancada } from '../types'; | ||||
| import type { AgrupacionPolitica, UpdateAgrupacionData, Bancada, LogoAgrupacionCategoria } from '../types'; | ||||
|  | ||||
| const AUTH_API_URL = 'http://localhost:5217/api/auth'; | ||||
| const ADMIN_API_URL = 'http://localhost:5217/api/admin'; | ||||
| @@ -94,4 +94,13 @@ export const getConfiguracion = async (): Promise<ConfiguracionResponse> => { | ||||
|  | ||||
| export const updateConfiguracion = async (data: Record<string, string>): Promise<void> => { | ||||
|     await adminApiClient.put('/configuracion', data); | ||||
| }; | ||||
|  | ||||
| export const getLogos = async (): Promise<LogoAgrupacionCategoria[]> => { | ||||
|     const response = await adminApiClient.get('/logos'); | ||||
|     return response.data; | ||||
| }; | ||||
|  | ||||
| export const updateLogos = async (data: LogoAgrupacionCategoria[]): Promise<void> => { | ||||
|     await adminApiClient.put('/logos', data); | ||||
| }; | ||||
| @@ -6,7 +6,6 @@ export interface AgrupacionPolitica { | ||||
|   nombre: string; | ||||
|   nombreCorto: string | null; | ||||
|   color: string | null; | ||||
|   logoUrl: string | null; | ||||
|   ordenDiputados: number | null; | ||||
|   ordenSenadores: number | null; | ||||
| } | ||||
| @@ -14,7 +13,6 @@ export interface AgrupacionPolitica { | ||||
| export interface UpdateAgrupacionData { | ||||
|     nombreCorto: string | null; | ||||
|     color: string | null; | ||||
|     logoUrl: string | null; | ||||
| } | ||||
|  | ||||
| export const TipoCamara = { | ||||
| @@ -40,4 +38,11 @@ export interface Bancada { | ||||
|   agrupacionPoliticaId: string | null; | ||||
|   agrupacionPolitica: AgrupacionPolitica | null; | ||||
|   ocupante: OcupanteBanca | null; | ||||
| } | ||||
|  | ||||
| export interface LogoAgrupacionCategoria { | ||||
|     id: number; | ||||
|     agrupacionPoliticaId: string; | ||||
|     categoriaId: number; | ||||
|     logoUrl: string | null; | ||||
| } | ||||
| @@ -5,6 +5,7 @@ import { CongresoWidget } from './components/CongresoWidget' | ||||
| import MapaBsAs from './components/MapaBsAs' | ||||
| import { TickerWidget } from './components/TickerWidget' | ||||
| import { TelegramaWidget } from './components/TelegramaWidget' | ||||
| import { ConcejalesWidget } from './components/ConcejalesWidget' | ||||
|  | ||||
| function App() { | ||||
|   return ( | ||||
| @@ -12,6 +13,7 @@ function App() { | ||||
|       <h1>Resultados Electorales - Provincia de Buenos Aires</h1> | ||||
|       <main> | ||||
|         <TickerWidget /> | ||||
|         <ConcejalesWidget /> | ||||
|         <CongresoWidget /> | ||||
|         <BancasWidget /> | ||||
|         <MapaBsAs /> | ||||
|   | ||||
| @@ -1,6 +1,13 @@ | ||||
| // src/apiService.ts | ||||
| import axios from 'axios'; | ||||
| import type { ResumenProvincial, ProyeccionBancas, MunicipioSimple, TelegramaData, CatalogoItem } from './types/types'; | ||||
| import type { ProyeccionBancas, MunicipioSimple, TelegramaData, CatalogoItem, CategoriaResumen, ResultadoTicker } from './types/types'; | ||||
|  | ||||
| const API_BASE_URL = 'http://localhost:5217/api'; | ||||
|  | ||||
| const apiClient = axios.create({ | ||||
|   baseURL: API_BASE_URL, | ||||
|   headers: { 'Content-Type': 'application/json' }, | ||||
| }); | ||||
|  | ||||
| interface PartidoData { | ||||
|   id: string; | ||||
| @@ -47,14 +54,13 @@ export interface BancadaDetalle { | ||||
|   ocupante: OcupanteBanca | null; | ||||
| } | ||||
|  | ||||
| const API_BASE_URL = 'http://localhost:5217/api'; | ||||
| export interface ConfiguracionPublica { | ||||
|   TickerResultadosCantidad?: string; | ||||
|   ConcejalesResultadosCantidad?: string; | ||||
|   // ... otras claves públicas que pueda añadir en el futuro | ||||
| } | ||||
|  | ||||
| const apiClient = axios.create({ | ||||
|   baseURL: API_BASE_URL, | ||||
|   headers: { 'Content-Type': 'application/json' }, | ||||
| }); | ||||
|  | ||||
| export const getResumenProvincial = async (): Promise<ResumenProvincial> => { | ||||
| export const getResumenProvincial = async (): Promise<CategoriaResumen[]> => { | ||||
|   const response = await apiClient.get('/resultados/provincia/02'); | ||||
|   return response.data; | ||||
| }; | ||||
| @@ -113,4 +119,14 @@ export const getComposicionCongreso = async (): Promise<ComposicionData> => { | ||||
| export const getBancadasDetalle = async (): Promise<BancadaDetalle[]> => { | ||||
|   const response = await apiClient.get('/resultados/bancadas-detalle'); | ||||
|   return response.data; | ||||
| }; | ||||
|  | ||||
| export const getConfiguracionPublica = async (): Promise<ConfiguracionPublica> => { | ||||
|   const response = await apiClient.get('/resultados/configuracion-publica'); | ||||
|   return response.data; | ||||
| }; | ||||
|  | ||||
| export const getResultadosConcejales = async (seccionId: string): Promise<ResultadoTicker[]> => { | ||||
|   const response = await apiClient.get(`/resultados/concejales/${seccionId}`); | ||||
|   return response.data; | ||||
| }; | ||||
							
								
								
									
										103
									
								
								Elecciones-Web/frontend/src/components/ConcejalesWidget.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								Elecciones-Web/frontend/src/components/ConcejalesWidget.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| // src/components/ConcejalesWidget.tsx | ||||
| import { useState, useEffect } from 'react'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| import { getSeccionesElectorales, getResultadosConcejales, getConfiguracionPublica } from '../apiService'; | ||||
| import type { MunicipioSimple, ResultadoTicker } from '../types/types'; | ||||
| import { ImageWithFallback } from './ImageWithFallback'; | ||||
| import './TickerWidget.css'; // Reutilizamos los estilos del ticker | ||||
|  | ||||
| const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | ||||
|  | ||||
| export const ConcejalesWidget = () => { | ||||
|   const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); | ||||
|   const [seccionActualId, setSeccionActualId] = useState<string>(''); | ||||
|  | ||||
|   // Query para la configuración (para saber cuántos resultados mostrar) | ||||
|   const { data: configData } = useQuery({ | ||||
|     queryKey: ['configuracionPublica'], | ||||
|     queryFn: getConfiguracionPublica, | ||||
|     staleTime: 0, | ||||
|   }); | ||||
|  | ||||
|   // Calculamos la cantidad a mostrar desde la configuración | ||||
|   const cantidadAMostrar = parseInt(configData?.ConcejalesResultadosCantidad || '5', 10) + 1; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     getSeccionesElectorales().then(seccionesData => { | ||||
|       if (seccionesData && seccionesData.length > 0) { | ||||
|         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)); | ||||
|         setSecciones(seccionesData); | ||||
|         // Al estar los datos ya ordenados, el [0] será "Sección Capital" | ||||
|         setSeccionActualId(seccionesData[0].id); | ||||
|       } | ||||
|     }); | ||||
|   }, []); // El array de dependencias vacío asegura que esto solo se ejecute una vez | ||||
|  | ||||
|   // Query para obtener los resultados de la sección seleccionada | ||||
|   const { data: resultados, isLoading } = useQuery<ResultadoTicker[]>({ | ||||
|     queryKey: ['resultadosConcejales', seccionActualId], | ||||
|     queryFn: () => getResultadosConcejales(seccionActualId), | ||||
|     enabled: !!seccionActualId, | ||||
|   }); | ||||
|  | ||||
|   // --- INICIO DE LA LÓGICA DE PROCESAMIENTO "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.votosPorcentaje || 0), 0); | ||||
|  | ||||
|     const otrosEntry: ResultadoTicker = { | ||||
|       id: `otros-concejales-${seccionActualId}`, | ||||
|       nombre: 'Otros', | ||||
|       nombreCorto: 'Otros', | ||||
|       color: '#888888', | ||||
|       logoUrl: null, | ||||
|       votos: 0, // No es relevante para la visualización del porcentaje | ||||
|       votosPorcentaje: otrosPorcentaje, | ||||
|     }; | ||||
|     displayResults = [...topParties, otrosEntry]; | ||||
|   } else if (resultados) { | ||||
|     displayResults = resultados.slice(0, cantidadAMostrar); | ||||
|   } | ||||
|   // --- FIN DE LA LÓGICA DE PROCESAMIENTO "OTROS" --- | ||||
|  | ||||
|   return ( | ||||
|     <div className="ticker-card" style={{ gridColumn: '1 / -1' }}> | ||||
|       <div className="ticker-header"> | ||||
|         <h3>CONCEJALES - LA PLATA</h3> | ||||
|         <select value={seccionActualId} onChange={e => setSeccionActualId(e.target.value)} disabled={secciones.length === 0}> | ||||
|           {secciones.map(s => <option key={s.id} value={s.id}>{s.nombre}</option>)} | ||||
|         </select> | ||||
|       </div> | ||||
|       <div className="ticker-results"> | ||||
|         {isLoading ? <p>Cargando...</p> : | ||||
|           displayResults.map(partido => ( | ||||
|             <div key={partido.id} className="ticker-party"> | ||||
|               <div className="party-logo"> | ||||
|                 <ImageWithFallback src={partido.logoUrl || undefined} fallbackSrc="/default-avatar.png" alt={`Logo de ${partido.nombre}`} /> | ||||
|               </div> | ||||
|               <div className="party-details"> | ||||
|                 <div className="party-info"> | ||||
|                   <span className="party-name">{partido.nombreCorto || partido.nombre}</span> | ||||
|                   <span className="party-percent">{formatPercent(partido.votosPorcentaje)}</span> | ||||
|                 </div> | ||||
|                 <div className="party-bar-background"> | ||||
|                   <div className="party-bar-foreground" style={{ width: `${partido.votosPorcentaje}%`, backgroundColor: partido.color || '#888' }}></div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           )) | ||||
|         } | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| @@ -80,17 +80,13 @@ export const CongresoWidget = () => { | ||||
|         {camaraActiva === 'diputados' ? ( | ||||
|           <ParliamentLayout | ||||
|             seatData={seatFillData} | ||||
|             // --- INICIO DE LA CORRECCIÓN --- | ||||
|             // Solo pasamos la prop 'presidenteBancada' si NO estamos en modo oficial | ||||
|             presidenteBancada={!esModoOficial ? datosCamaraActual.presidenteBancada : undefined} | ||||
|           // --- FIN DE LA CORRECCIÓN --- | ||||
|           /> | ||||
|         ) : ( | ||||
|           <SenateLayout | ||||
|             seatData={seatFillData} | ||||
|             // --- INICIO DE LA CORRECCIÓN --- | ||||
|             presidenteBancada={!esModoOficial ? datosCamaraActual.presidenteBancada : undefined} | ||||
|           // --- FIN DE LA CORRECCIÓN --- | ||||
|           /> | ||||
|         )} | ||||
|       </div> | ||||
|   | ||||
							
								
								
									
										28
									
								
								Elecciones-Web/frontend/src/components/ImageWithFallback.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Elecciones-Web/frontend/src/components/ImageWithFallback.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| // src/components/ImageWithFallback.tsx | ||||
| import { useState, useEffect } from 'react'; | ||||
|  | ||||
| interface Props extends React.ImgHTMLAttributes<HTMLImageElement> { | ||||
|   fallbackSrc: string; | ||||
| } | ||||
|  | ||||
| export const ImageWithFallback = ({ src, fallbackSrc, ...props }: Props) => { | ||||
|   const [imgSrc, setImgSrc] = useState(src); | ||||
|   const [error, setError] = useState(false); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setError(false); | ||||
|     setImgSrc(src); | ||||
|   }, [src]); | ||||
|  | ||||
|   const handleError = () => { | ||||
|     setError(true); | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <img | ||||
|       src={error || !imgSrc ? fallbackSrc : imgSrc} | ||||
|       onError={handleError} | ||||
|       {...props} | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
| @@ -87,7 +87,7 @@ export const TelegramaWidget = () => { | ||||
|         .finally(() => setLoading(false)); | ||||
|     } | ||||
|   }, [selectedMesa]); | ||||
|  | ||||
|          | ||||
|   return ( | ||||
|     <div className="telegrama-container"> | ||||
|       <h4>Consulta de Telegramas por Ubicación</h4> | ||||
|   | ||||
| @@ -1,22 +1,20 @@ | ||||
| /* src/components/TickerWidget.css */ | ||||
| .ticker-container { | ||||
|   /* Se cambia a un fondo claro con borde y sombra sutil */ | ||||
|   background-color: #ffffff; | ||||
|   border: 1px solid #e0e0e0; | ||||
|   box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); | ||||
|   padding: 15px 20px; | ||||
|   border-radius: 8px; | ||||
|   max-width: 800px; | ||||
|   margin: 20px auto; | ||||
|   font-family: "Public Sans", system-ui, Avenir, Helvetica, Arial, sans-serif; | ||||
|   color: #333333; /* Color de texto por defecto */ | ||||
| .ticker-wrapper { | ||||
|     display: grid; | ||||
|     grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); | ||||
|     gap: 1.5rem; | ||||
|     width: 100%; | ||||
|     max-width: 1280px; | ||||
|     margin: 20px auto; | ||||
| } | ||||
|  | ||||
| .ticker-container.loading, .ticker-container.error { | ||||
|     text-align: center; | ||||
|     padding: 30px; | ||||
|     font-style: italic; | ||||
|     color: #757575; /* Color de texto atenuado */ | ||||
| .ticker-card { | ||||
|     background-color: #ffffff; | ||||
|     border: 1px solid #e0e0e0; | ||||
|     box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); | ||||
|     padding: 15px 20px; | ||||
|     border-radius: 8px; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
| } | ||||
|  | ||||
| .ticker-header { | ||||
| @@ -48,9 +46,9 @@ | ||||
| } | ||||
|  | ||||
| .ticker-results { | ||||
|   display: grid; | ||||
|   grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | ||||
|   gap: 20px; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     gap: 12px; /* Espacio entre partidos */ | ||||
| } | ||||
|  | ||||
| .ticker-party .party-info { | ||||
| @@ -84,4 +82,31 @@ | ||||
|   border-radius: 4px; | ||||
|   transition: width 0.5s ease-in-out; | ||||
|   /* El color de fondo se sigue aplicando desde el componente, esto es correcto */ | ||||
| } | ||||
|  | ||||
| .ticker-results { | ||||
|     display: grid; | ||||
|     grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); /* Aumentamos el tamaño mínimo */ | ||||
|     gap: 20px; | ||||
| } | ||||
| .ticker-party { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     gap: 10px; /* Espacio entre logo y detalles */ | ||||
| } | ||||
| .party-logo { | ||||
|     flex-shrink: 0; | ||||
|     width: 50px; | ||||
|     height: 50px; | ||||
| } | ||||
| .party-logo img { | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     object-fit: cover; | ||||
|     border-radius: 4px; | ||||
|     border: 1px solid #ddd; | ||||
| } | ||||
| .party-details { | ||||
|     flex-grow: 1; | ||||
|     min-width: 0; /* Previene que el flex item se desborde */ | ||||
| } | ||||
| @@ -1,78 +1,92 @@ | ||||
| // src/components/TickerWidget.tsx | ||||
| import { useState, useEffect } from 'react'; | ||||
| import { getResumenProvincial } from '../apiService'; | ||||
| import type { ResumenProvincial } from '../types/types'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| import { getResumenProvincial, getConfiguracionPublica } from '../apiService'; | ||||
| import type { CategoriaResumen, ResultadoTicker } from '../types/types'; | ||||
| import { ImageWithFallback } from './ImageWithFallback'; | ||||
| import './TickerWidget.css'; | ||||
|  | ||||
| const formatPercent = (num: number) => `${num.toFixed(2).replace('.', ',')}%`; | ||||
| const COLORS = [ | ||||
|     "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", | ||||
|     "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" | ||||
| ]; | ||||
| const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | ||||
|  | ||||
| export const TickerWidget = () => { | ||||
|   const [data, setData] = useState<ResumenProvincial | null>(null); | ||||
|   const [loading, setLoading] = useState(true); | ||||
|   // Se añade un nuevo estado para manejar errores de forma explícita | ||||
|   const [error, setError] = useState<string | null>(null); | ||||
|   const { data: categorias, isLoading, error } = useQuery<CategoriaResumen[]>({ | ||||
|     queryKey: ['resumenProvincial'], | ||||
|     queryFn: getResumenProvincial, | ||||
|     refetchInterval: 30000, | ||||
|   }); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     const fetchData = async () => { | ||||
|       // Se resetea el error en cada intento de carga | ||||
|       setError(null); | ||||
|       try { | ||||
|         const result = await getResumenProvincial(); | ||||
|         setData(result); | ||||
|       } catch (err) { | ||||
|         console.error("Error cargando resumen provincial:", err); | ||||
|         // Se guarda el mensaje de error para mostrarlo en la UI | ||||
|         setError("No se pudo conectar con el servidor para obtener el resumen provincial."); | ||||
|       } finally { | ||||
|         setLoading(false); | ||||
|       } | ||||
|     }; | ||||
|   const { data: configData } = useQuery({ | ||||
|     queryKey: ['configuracionPublica'], | ||||
|     queryFn: getConfiguracionPublica, | ||||
|     staleTime: 0, | ||||
|   }); | ||||
|  | ||||
|     fetchData(); // Carga inicial | ||||
|     const intervalId = setInterval(fetchData, 30000); // Actualiza cada 30 segundos | ||||
|   const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10) + 1; | ||||
|  | ||||
|     return () => clearInterval(intervalId); // Limpia el intervalo al desmontar el componente | ||||
|   }, []); | ||||
|   if (isLoading) return <div className="ticker-wrapper loading">Cargando resumen...</div>; | ||||
|   if (error || !categorias) return <div className="ticker-wrapper error">No hay datos disponibles.</div>; | ||||
|  | ||||
|   if (loading) { | ||||
|     return <div className="ticker-container loading">Cargando resultados provinciales...</div>; | ||||
|   } | ||||
|  | ||||
|   // Si hay un error, se muestra el mensaje correspondiente | ||||
|   if (error) { | ||||
|     return <div className="ticker-container error">{error}</div>; | ||||
|   } | ||||
|    | ||||
|   if (!data) { | ||||
|     return <div className="ticker-container error">No hay datos disponibles.</div>; | ||||
|   } | ||||
|   const categoriasFiltradas = categorias.filter(c => c.categoriaId !== 7); | ||||
|  | ||||
|   return ( | ||||
|     <div className="ticker-container"> | ||||
|       <div className="ticker-header"> | ||||
|         <h3>PROVINCIA BS. AS.</h3> | ||||
|         <div className="ticker-stats"> | ||||
|           <span>Mesas Escrutadas: <strong>{formatPercent(data.porcentajeEscrutado)}</strong></span> | ||||
|           <span>Participación Total: <strong>{formatPercent(data.porcentajeParticipacion)}</strong></span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div className="ticker-results"> | ||||
|         {data.resultados.slice(0, 3).map((partido, index) => ( | ||||
|           <div key={`${partido.nombre}-${index}`} className="ticker-party"> | ||||
|             <div className="party-info"> | ||||
|               <span className="party-name">{partido.nombre}</span> | ||||
|               <span className="party-percent">{formatPercent(partido.porcentaje)}</span> | ||||
|     <div className="ticker-wrapper"> | ||||
|       {categoriasFiltradas.map(categoria => { | ||||
|  | ||||
|         let displayResults: ResultadoTicker[] = categoria.resultados; | ||||
|  | ||||
|         if (categoria.resultados.length > cantidadAMostrar) { | ||||
|           const topParties = categoria.resultados.slice(0, cantidadAMostrar - 1); | ||||
|           const otherParties = categoria.resultados.slice(cantidadAMostrar - 1); | ||||
|           const otrosPorcentaje = otherParties.reduce((sum, party) => sum + party.votosPorcentaje, 0); | ||||
|  | ||||
|           const otrosEntry: ResultadoTicker = { | ||||
|             id: `otros-${categoria.categoriaId}`, | ||||
|             nombre: 'Otros', | ||||
|             nombreCorto: 'Otros', | ||||
|             color: '#888888', | ||||
|             logoUrl: null, | ||||
|             votos: 0, | ||||
|             votosPorcentaje: otrosPorcentaje, | ||||
|           }; | ||||
|  | ||||
|           displayResults = [...topParties, otrosEntry]; | ||||
|         } else { | ||||
|           displayResults = categoria.resultados.slice(0, cantidadAMostrar); | ||||
|         } | ||||
|  | ||||
|         return ( | ||||
|           <div key={categoria.categoriaId} className="ticker-card"> | ||||
|             <div className="ticker-header"> | ||||
|               <h3>{categoria.categoriaNombre}</h3> | ||||
|               <div className="ticker-stats"> | ||||
|                 <span>Mesas: <strong>{formatPercent(categoria.estadoRecuento?.mesasTotalizadasPorcentaje ?? 0)}</strong></span> | ||||
|                 <span>Part: <strong>{formatPercent(categoria.estadoRecuento?.participacionPorcentaje ?? 0)}</strong></span> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div className="party-bar-background"> | ||||
|               <div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor:COLORS[index % COLORS.length] }}></div> | ||||
|             <div className="ticker-results"> | ||||
|               {displayResults.map(partido => ( | ||||
|                 <div key={partido.id} className="ticker-party"> | ||||
|                   <div className="party-logo"> | ||||
|                     <ImageWithFallback | ||||
|                       src={partido.logoUrl || undefined} | ||||
|                       fallbackSrc="/default-avatar.png" | ||||
|                       alt={`Logo de ${partido.nombre}`} | ||||
|                     /> | ||||
|                   </div> | ||||
|                   <div className="party-details"> | ||||
|                     <div className="party-info"> | ||||
|                       <span className="party-name">{partido.nombreCorto || partido.nombre}</span> | ||||
|                       <span className="party-percent">{formatPercent(partido.votosPorcentaje)}</span> | ||||
|                     </div> | ||||
|                     <div className="party-bar-background"> | ||||
|                       <div className="party-bar-foreground" style={{ width: `${partido.votosPorcentaje}%`, backgroundColor: partido.color || '#888' }}></div> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               ))} | ||||
|             </div> | ||||
|           </div> | ||||
|         ))} | ||||
|       </div> | ||||
|         ); | ||||
|       })} | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| @@ -40,22 +40,46 @@ export interface GeographyObject { | ||||
| } | ||||
|  | ||||
| export interface MunicipioSimple { id: string; nombre: string; } | ||||
| export interface AgrupacionResultado { nombre: string; votos: number; porcentaje: number; } | ||||
|  | ||||
| export interface ResultadoTicker { | ||||
|   id: string; | ||||
|   nombre: string; | ||||
|   nombreCorto: string | null; | ||||
|   color: string | null; | ||||
|   logoUrl: string | null; | ||||
|   votos: number; | ||||
|   votosPorcentaje: number; | ||||
| } | ||||
|  | ||||
| export interface EstadoRecuentoTicker { | ||||
|     mesasTotalizadasPorcentaje: number; | ||||
|     participacionPorcentaje: number; | ||||
| } | ||||
|  | ||||
| export interface CategoriaResumen { | ||||
|   categoriaId: number; | ||||
|   categoriaNombre: string; | ||||
|   estadoRecuento: EstadoRecuentoTicker | null; | ||||
|   resultados: ResultadoTicker[]; | ||||
| } | ||||
|  | ||||
| export interface VotosAdicionales { enBlanco: number; nulos: number; recurridos: number; } | ||||
|  | ||||
| export interface MunicipioDetalle { | ||||
|   municipioNombre: string; | ||||
|   ultimaActualizacion: string; | ||||
|   porcentajeEscrutado: number; | ||||
|   porcentajeParticipacion: number; | ||||
|   resultados: AgrupacionResultado[]; | ||||
|   resultados: CategoriaResumen[]; | ||||
|   votosAdicionales: VotosAdicionales; | ||||
| } | ||||
|  | ||||
| export interface ResumenProvincial { | ||||
|   provinciaNombre: string; | ||||
|   ultimaActualizacion: string; | ||||
|   porcentajeEscrutado: number; | ||||
|   porcentajeParticipacion: number; | ||||
|   resultados: AgrupacionResultado[]; | ||||
|   resultados: CategoriaResumen[]; | ||||
|   votosAdicionales: VotosAdicionales; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -50,7 +50,6 @@ public class AdminController : ControllerBase | ||||
|     // Actualizamos las propiedades de la entidad con los valores del DTO. | ||||
|     agrupacion.NombreCorto = agrupacionDto.NombreCorto; | ||||
|     agrupacion.Color = agrupacionDto.Color; | ||||
|     agrupacion.LogoUrl = agrupacionDto.LogoUrl; | ||||
|  | ||||
|     // Guardamos los cambios en la base de datos. | ||||
|     await _dbContext.SaveChangesAsync(); | ||||
| @@ -178,4 +177,32 @@ public class AdminController : ControllerBase | ||||
|  | ||||
|     return NoContent(); | ||||
|   } | ||||
|  | ||||
|   [HttpGet("logos")] | ||||
|   public async Task<IActionResult> GetLogos() | ||||
|   { | ||||
|     return Ok(await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync()); | ||||
|   } | ||||
|  | ||||
|   [HttpPut("logos")] | ||||
|   public async Task<IActionResult> UpdateLogos([FromBody] List<LogoAgrupacionCategoria> logos) | ||||
|   { | ||||
|     // Lógica de "Upsert" | ||||
|     foreach (var logo in logos) | ||||
|     { | ||||
|       var logoExistente = await _dbContext.LogosAgrupacionesCategorias | ||||
|           .FirstOrDefaultAsync(l => l.AgrupacionPoliticaId == logo.AgrupacionPoliticaId && l.CategoriaId == logo.CategoriaId); | ||||
|  | ||||
|       if (logoExistente != null) | ||||
|       { | ||||
|         logoExistente.LogoUrl = logo.LogoUrl; | ||||
|       } | ||||
|       else if (!string.IsNullOrEmpty(logo.LogoUrl)) | ||||
|       { | ||||
|         _dbContext.LogosAgrupacionesCategorias.Add(logo); | ||||
|       } | ||||
|     } | ||||
|     await _dbContext.SaveChangesAsync(); | ||||
|     return NoContent(); | ||||
|   } | ||||
| } | ||||
| @@ -92,59 +92,69 @@ public class ResultadosController : ControllerBase | ||||
|     [HttpGet("provincia/{distritoId}")] | ||||
|     public async Task<IActionResult> GetResultadosProvinciales(string distritoId) | ||||
|     { | ||||
|         _logger.LogInformation("Solicitud de resultados para la provincia con distritoId: {DistritoId}", distritoId); | ||||
|  | ||||
|         // PASO 1: Encontrar el ámbito geográfico de la provincia. | ||||
|         var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking() | ||||
|             .FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10); | ||||
|  | ||||
|         if (provincia == null) | ||||
|         { | ||||
|             _logger.LogWarning("No se encontró la provincia con distritoId: {DistritoId}", distritoId); | ||||
|             return NotFound(new { message = $"No se encontró la provincia con distritoId {distritoId}" }); | ||||
|         } | ||||
|  | ||||
|         // PASO 2: Obtener el estado general del recuento para la provincia. | ||||
|         // Como las estadísticas generales (mesas, participación) son las mismas para todas las categorías, | ||||
|         // simplemente tomamos la primera que encontremos para este ámbito. | ||||
|         var estadoGeneral = await _dbContext.EstadosRecuentosGenerales.AsNoTracking() | ||||
|             .FirstOrDefaultAsync(e => e.AmbitoGeograficoId == provincia.Id); | ||||
|  | ||||
|         // PASO 3: Obtener el resumen de votos por agrupación para la provincia. | ||||
|         // Hacemos un JOIN manual entre ResumenesVotos y AgrupacionesPoliticas para obtener los nombres. | ||||
|         var resultados = await _dbContext.ResumenesVotos | ||||
|             .AsNoTracking() | ||||
|             .Where(r => r.AmbitoGeograficoId == provincia.Id) | ||||
|             .Join( | ||||
|                 _dbContext.AgrupacionesPoliticas.AsNoTracking(), | ||||
|                 resumen => resumen.AgrupacionPoliticaId, | ||||
|                 agrupacion => agrupacion.Id, | ||||
|                 (resumen, agrupacion) => new AgrupacionResultadoDto | ||||
|                 { | ||||
|                     Nombre = agrupacion.Nombre, | ||||
|                     Votos = resumen.Votos, | ||||
|                     Porcentaje = resumen.VotosPorcentaje | ||||
|                 }) | ||||
|             .OrderByDescending(r => r.Votos) | ||||
|         var todosLosResumenes = await _dbContext.ResumenesVotos.AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         // PASO 4: Construir el objeto de respuesta (DTO). | ||||
|         // Si no hay datos de recuento aún, usamos valores por defecto para evitar errores en el frontend. | ||||
|         var respuestaDto = new ResumenProvincialDto | ||||
|         { | ||||
|             ProvinciaNombre = provincia.Nombre, | ||||
|             UltimaActualizacion = estadoGeneral?.FechaTotalizacion ?? DateTime.UtcNow, | ||||
|             PorcentajeEscrutado = estadoGeneral?.MesasTotalizadasPorcentaje ?? 0, | ||||
|             PorcentajeParticipacion = estadoGeneral?.ParticipacionPorcentaje ?? 0, | ||||
|             Resultados = resultados, | ||||
|             // NOTA: Los votos adicionales (nulos, en blanco) no están en la tabla de resumen provincial. | ||||
|             // Esto es una mejora pendiente en el Worker. Por ahora, devolvemos 0. | ||||
|             VotosAdicionales = new VotosAdicionalesDto { EnBlanco = 0, Nulos = 0, Recurridos = 0 } | ||||
|         }; | ||||
|         // OBTENER TODOS LOS LOGOS EN UNA SOLA CONSULTA | ||||
|         var logosLookup = (await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync()) | ||||
|             .ToLookup(l => $"{l.AgrupacionPoliticaId}-{l.CategoriaId}"); | ||||
|  | ||||
|         _logger.LogInformation("Devolviendo {NumResultados} resultados de agrupaciones para la provincia.", respuestaDto.Resultados.Count); | ||||
|         if (provincia == null) return NotFound($"No se encontró la provincia con distritoId {distritoId}"); | ||||
|  | ||||
|         return Ok(respuestaDto); | ||||
|         var estadosPorCategoria = await _dbContext.EstadosRecuentosGenerales.AsNoTracking() | ||||
|             .Include(e => e.CategoriaElectoral) | ||||
|             .Where(e => e.AmbitoGeograficoId == provincia.Id) | ||||
|             .ToDictionaryAsync(e => e.CategoriaId); | ||||
|  | ||||
|         var resultadosPorMunicipio = await _dbContext.ResultadosVotos | ||||
|             .AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .Where(r => r.AmbitoGeografico.NivelId == 30) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         var resultadosAgrupados = resultadosPorMunicipio | ||||
|             .GroupBy(r => r.CategoriaId) | ||||
|             .Select(g => new | ||||
|             { | ||||
|                 CategoriaId = g.Key, | ||||
|                 TotalVotosCategoria = g.Sum(r => r.CantidadVotos), | ||||
|                 // Agrupamos por el ID de la agrupación, no por el objeto | ||||
|                 Resultados = g.GroupBy(r => r.AgrupacionPoliticaId) | ||||
|                               .Select(partidoGroup => new | ||||
|                               { | ||||
|                                   // El objeto Agrupacion lo tomamos del primer elemento del grupo | ||||
|                                   Agrupacion = partidoGroup.First().AgrupacionPolitica, | ||||
|                                   Votos = partidoGroup.Sum(r => r.CantidadVotos) | ||||
|                               }) | ||||
|                               .ToList() | ||||
|             }) | ||||
|             .Select(g => new | ||||
|             { | ||||
|                 g.CategoriaId, | ||||
|                 CategoriaNombre = estadosPorCategoria.ContainsKey(g.CategoriaId) ? estadosPorCategoria[g.CategoriaId].CategoriaElectoral.Nombre : "Desconocido", | ||||
|                 EstadoRecuento = estadosPorCategoria.GetValueOrDefault(g.CategoriaId), | ||||
|                 Resultados = g.Resultados | ||||
|                               .Select(r => new | ||||
|                               { | ||||
|                                   r.Agrupacion.Id, | ||||
|                                   r.Agrupacion.Nombre, | ||||
|                                   r.Agrupacion.NombreCorto, | ||||
|                                   r.Agrupacion.Color, | ||||
|                                   LogoUrl = logosLookup[$"{r.Agrupacion.Id}-{g.CategoriaId}"].FirstOrDefault()?.LogoUrl, | ||||
|                                   r.Votos, | ||||
|                                   VotosPorcentaje = g.TotalVotosCategoria > 0 ? ((decimal)r.Votos * 100 / g.TotalVotosCategoria) : 0 | ||||
|                               }) | ||||
|                               .OrderByDescending(r => r.Votos) | ||||
|                               .ToList() | ||||
|             }) | ||||
|             .OrderBy(c => c.CategoriaId) | ||||
|             .ToList(); | ||||
|  | ||||
|         return Ok(resultadosAgrupados); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @@ -503,4 +513,77 @@ public class ResultadosController : ControllerBase | ||||
|  | ||||
|         return Ok(bancadasConOcupantes); | ||||
|     } | ||||
|     [HttpGet("configuracion-publica")] | ||||
|     public async Task<IActionResult> GetConfiguracionPublica() | ||||
|     { | ||||
|         // Definimos una lista de las claves de configuración que son seguras para el público. | ||||
|         // De esta manera, si en el futuro añadimos claves sensibles (como contraseñas de API, etc.), | ||||
|         // nunca se expondrán accidentalmente. | ||||
|         var clavesPublicas = new List<string> | ||||
|     { | ||||
|         "TickerResultadosCantidad", | ||||
|         "ConcejalesResultadosCantidad" | ||||
|         // "OtraClavePublica"  | ||||
|         }; | ||||
|  | ||||
|         var configuracionPublica = await _dbContext.Configuraciones | ||||
|             .AsNoTracking() | ||||
|             .Where(c => clavesPublicas.Contains(c.Clave)) | ||||
|             .ToDictionaryAsync(c => c.Clave, c => c.Valor); | ||||
|  | ||||
|         return Ok(configuracionPublica); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("concejales/{seccionId}")] | ||||
|     public async Task<IActionResult> GetResultadosConcejalesPorSeccion(string seccionId) | ||||
|     { | ||||
|         // 1. Encontrar todos los municipios (Nivel 30) que pertenecen a la sección dada (Nivel 20) | ||||
|         var municipiosDeLaSeccion = await _dbContext.AmbitosGeograficos | ||||
|             .AsNoTracking() | ||||
|             .Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId) | ||||
|             .Select(a => a.Id) // Solo necesitamos sus IDs | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         if (!municipiosDeLaSeccion.Any()) | ||||
|         { | ||||
|             return Ok(new List<object>()); | ||||
|         } | ||||
|  | ||||
|         // 2. Obtener todos los resultados de la categoría Concejales (ID 7) para esos municipios | ||||
|         var resultadosMunicipales = await _dbContext.ResultadosVotos | ||||
|             .AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .Where(r => r.CategoriaId == 7 && municipiosDeLaSeccion.Contains(r.AmbitoGeograficoId)) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         var logosConcejales = await _dbContext.LogosAgrupacionesCategorias | ||||
|             .AsNoTracking() | ||||
|             .Where(l => l.CategoriaId == 7) | ||||
|             .ToDictionaryAsync(l => l.AgrupacionPoliticaId); | ||||
|  | ||||
|         // 3. Agrupar y sumar en memoria para obtener el total por partido para la sección | ||||
|         var totalVotosSeccion = resultadosMunicipales.Sum(r => r.CantidadVotos); | ||||
|  | ||||
|         var resultadosFinales = resultadosMunicipales | ||||
|             .GroupBy(r => r.AgrupacionPolitica) | ||||
|             .Select(g => new | ||||
|             { | ||||
|                 Agrupacion = g.Key, | ||||
|                 Votos = g.Sum(r => r.CantidadVotos) | ||||
|             }) | ||||
|             .OrderByDescending(r => r.Votos) | ||||
|             .Select(r => new | ||||
|             { | ||||
|                 r.Agrupacion.Id, | ||||
|                 r.Agrupacion.Nombre, | ||||
|                 r.Agrupacion.NombreCorto, | ||||
|                 r.Agrupacion.Color, | ||||
|                 LogoUrl = logosConcejales.GetValueOrDefault(r.Agrupacion.Id)?.LogoUrl, | ||||
|                 r.Votos, | ||||
|                 votosPorcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0 | ||||
|             }) | ||||
|             .ToList(); | ||||
|  | ||||
|         return Ok(resultadosFinales); | ||||
|     } | ||||
| } | ||||
| @@ -141,6 +141,13 @@ using (var scope = app.Services.CreateScope()) | ||||
|         context.SaveChanges(); | ||||
|         Console.WriteLine("--> Seeded default configuration 'MostrarOcupantes'."); | ||||
|     } | ||||
|     if (!context.Configuraciones.Any(c => c.Clave == "TickerResultadosCantidad")) | ||||
|     { | ||||
|         context.Configuraciones.Add(new Configuracion { Clave = "TickerResultadosCantidad", Valor = "3" }); | ||||
|         context.Configuraciones.Add(new Configuracion { Clave = "ConcejalesResultadosCantidad", Valor = "5" }); | ||||
|         context.SaveChanges(); | ||||
|         Console.WriteLine("--> Seeded default configuration 'TickerResultadosCantidad'."); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Configurar el pipeline de peticiones HTTP. | ||||
|   | ||||
| @@ -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+3b8c6bf754cff6ace486ae8fe850ed4d69233280")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+608ae655bedf6c59be5fec1e14fc308871d2fd62")] | ||||
| [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=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","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=","BlOQCaw/bt9UsCnDEIqO6LwzwEh4i0OxBfeIZgKDR4U=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","2x9HRdaMF3CjEHo\u002BFx\u002BfhG7CTomq/ExTkOKw2bUeHms="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| {"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","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=","khGrM2Rl22MsVh9N6\u002B7todRrMuJ6o3ljuHxZF/aubqE=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","6xYke/2SzNspypSwIgizeNUH7b\u002Bfoz3wYfKk6z1tMsw="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| @@ -1 +1 @@ | ||||
| {"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","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=","BlOQCaw/bt9UsCnDEIqO6LwzwEh4i0OxBfeIZgKDR4U=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","2x9HRdaMF3CjEHo\u002BFx\u002BfhG7CTomq/ExTkOKw2bUeHms="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| {"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","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=","khGrM2Rl22MsVh9N6\u002B7todRrMuJ6o3ljuHxZF/aubqE=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","6xYke/2SzNspypSwIgizeNUH7b\u002Bfoz3wYfKk6z1tMsw="],"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+3b8c6bf754cff6ace486ae8fe850ed4d69233280")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+608ae655bedf6c59be5fec1e14fc308871d2fd62")] | ||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")] | ||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")] | ||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||
|   | ||||
| @@ -19,6 +19,7 @@ public class EleccionesDbContext(DbContextOptions<EleccionesDbContext> options) | ||||
|     public DbSet<Configuracion> Configuraciones { get; set; } | ||||
|     public DbSet<Bancada> Bancadas { get; set; } | ||||
|     public DbSet<OcupanteBanca> OcupantesBancas { get; set; } | ||||
|     public DbSet<LogoAgrupacionCategoria> LogosAgrupacionesCategorias { get; set; } | ||||
|  | ||||
|     protected override void OnModelCreating(ModelBuilder modelBuilder) | ||||
|     { | ||||
| @@ -75,5 +76,9 @@ public class EleccionesDbContext(DbContextOptions<EleccionesDbContext> options) | ||||
|             // Opcional: puede definir un índice | ||||
|             entity.HasIndex(o => o.BancadaId).IsUnique(); | ||||
|         }); | ||||
|         modelBuilder.Entity<LogoAgrupacionCategoria>(entity => | ||||
|         { | ||||
|             entity.HasIndex(l => new { l.AgrupacionPoliticaId, l.CategoriaId }).IsUnique(); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -12,8 +12,6 @@ public class AgrupacionPolitica | ||||
|     public string Nombre { get; set; } = null!; | ||||
|     public string? NombreCorto { get; set; } // Para leyendas y gráficos | ||||
|     public string? Color { get; set; }       // Código hexadecimal, ej: "#1f77b4" | ||||
|     public string? LogoUrl { get; set; }     // URL a la imagen del logo | ||||
|     // Puede ser nulo si una agrupación no tiene una posición definida. | ||||
|     public int? OrdenDiputados { get; set; } | ||||
|     public int? OrdenSenadores { get; set; } | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| // src/Elecciones.Database/Entities/LogoAgrupacionCategoria.cs | ||||
| using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| namespace Elecciones.Database.Entities; | ||||
|  | ||||
| public class LogoAgrupacionCategoria | ||||
| { | ||||
|     [Key] | ||||
|     public int Id { get; set; } | ||||
|  | ||||
|     [Required] | ||||
|     public string AgrupacionPoliticaId { get; set; } = null!; | ||||
|      | ||||
|     [Required] | ||||
|     public int CategoriaId { get; set; } | ||||
|  | ||||
|     public string? LogoUrl { get; set; } | ||||
| } | ||||
| @@ -1,3 +1,4 @@ | ||||
| // src/Elecciones.Database/Entities/ResumenVoto.cs | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
|  | ||||
| @@ -9,11 +10,18 @@ public class ResumenVoto | ||||
|     [DatabaseGenerated(DatabaseGeneratedOption.Identity)] | ||||
|     public int Id { get; set; } | ||||
|  | ||||
|     // El ámbito donde se resume (siempre provincial en este caso) | ||||
|     [Required] | ||||
|     public int AmbitoGeograficoId { get; set; } | ||||
|  | ||||
|     [Required] | ||||
|     public string AgrupacionPoliticaId { get; set; } = null!; | ||||
|      | ||||
|     [ForeignKey("AgrupacionPoliticaId")] | ||||
|     public AgrupacionPolitica AgrupacionPolitica { get; set; } = null!; | ||||
|  | ||||
|     [Required] | ||||
|     public long Votos { get; set; } | ||||
|      | ||||
|     [Required] | ||||
|     public decimal VotosPorcentaje { get; set; } | ||||
| } | ||||
| @@ -0,0 +1,555 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using Elecciones.Database; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Metadata; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace Elecciones.Database.Migrations | ||||
| { | ||||
|     [DbContext(typeof(EleccionesDbContext))] | ||||
|     [Migration("20250901163255_AddLogoAgrupacionCategoriaTable")] | ||||
|     partial class AddLogoAgrupacionCategoriaTable | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .UseCollation("Modern_Spanish_CI_AS") | ||||
|                 .HasAnnotation("ProductVersion", "9.0.8") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 128); | ||||
|  | ||||
|             SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.AdminUser", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("PasswordHash") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("PasswordSalt") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Username") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(100) | ||||
|                         .HasColumnType("nvarchar(100)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("AdminUsers"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.AgrupacionPolitica", b => | ||||
|                 { | ||||
|                     b.Property<string>("Id") | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<string>("Color") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("IdTelegrama") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("LogoUrl") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("NombreCorto") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<int?>("OrdenDiputados") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int?>("OrdenSenadores") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("AgrupacionesPoliticas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.AmbitoGeografico", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("CircuitoId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("DistritoId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("EstablecimientoId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("MesaId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("MunicipioId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<int>("NivelId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("SeccionId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("SeccionProvincialId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("AmbitosGeograficos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("Camara") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("NumeroBanca") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.ToTable("Bancadas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.CategoriaElectoral", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<int>("Orden") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("CategoriasElectorales"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Configuracion", b => | ||||
|                 { | ||||
|                     b.Property<string>("Clave") | ||||
|                         .HasMaxLength(100) | ||||
|                         .HasColumnType("nvarchar(100)"); | ||||
|  | ||||
|                     b.Property<string>("Valor") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(100) | ||||
|                         .HasColumnType("nvarchar(100)"); | ||||
|  | ||||
|                     b.HasKey("Clave"); | ||||
|  | ||||
|                     b.ToTable("Configuraciones"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b => | ||||
|                 { | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadElectores") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadVotantes") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<int>("MesasEsperadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("MesasTotalizadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<decimal>("MesasTotalizadasPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.Property<decimal>("ParticipacionPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.Property<long>("VotosEnBlanco") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosEnBlancoPorcentaje") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.Property<long>("VotosNulos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosNulosPorcentaje") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.Property<long>("VotosRecurridos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosRecurridosPorcentaje") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.HasKey("AmbitoGeograficoId", "CategoriaId"); | ||||
|  | ||||
|                     b.ToTable("EstadosRecuentos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b => | ||||
|                 { | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadElectores") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadVotantes") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<int>("MesasEsperadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("MesasTotalizadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<decimal>("MesasTotalizadasPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.Property<decimal>("ParticipacionPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.HasKey("AmbitoGeograficoId", "CategoriaId"); | ||||
|  | ||||
|                     b.HasIndex("CategoriaId"); | ||||
|  | ||||
|                     b.ToTable("EstadosRecuentosGenerales"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.LogoAgrupacionCategoria", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("LogoUrl") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId", "CategoriaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("LogosAgrupacionesCategorias"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<int>("BancadaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("FotoUrl") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("NombreOcupante") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Periodo") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("BancadaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("OcupantesBancas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<int>("NroBancas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("ProyeccionesBancas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResultadoVoto", b => | ||||
|                 { | ||||
|                     b.Property<long>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<long>("CantidadVotos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<decimal>("PorcentajeVotos") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("ResultadosVotos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<long>("Votos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.ToTable("ResumenesVotos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Telegrama", b => | ||||
|                 { | ||||
|                     b.Property<string>("Id") | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("ContenidoBase64") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaEscaneo") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("Telegramas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AmbitoGeograficoId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AmbitoGeografico"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.CategoriaElectoral", "CategoriaElectoral") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("CategoriaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("CategoriaElectoral"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.Bancada", "Bancada") | ||||
|                         .WithOne("Ocupante") | ||||
|                         .HasForeignKey("Elecciones.Database.Entities.OcupanteBanca", "BancadaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("Bancada"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AmbitoGeograficoId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|  | ||||
|                     b.Navigation("AmbitoGeografico"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResultadoVoto", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AmbitoGeograficoId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|  | ||||
|                     b.Navigation("AmbitoGeografico"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => | ||||
|                 { | ||||
|                     b.Navigation("Ocupante"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,79 @@ | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace Elecciones.Database.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class AddLogoAgrupacionCategoriaTable : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AlterColumn<string>( | ||||
|                 name: "AgrupacionPoliticaId", | ||||
|                 table: "ResumenesVotos", | ||||
|                 type: "nvarchar(450)", | ||||
|                 nullable: false, | ||||
|                 oldClrType: typeof(string), | ||||
|                 oldType: "nvarchar(max)"); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "LogosAgrupacionesCategorias", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     Id = table.Column<int>(type: "int", nullable: false) | ||||
|                         .Annotation("SqlServer:Identity", "1, 1"), | ||||
|                     AgrupacionPoliticaId = table.Column<string>(type: "nvarchar(450)", nullable: false), | ||||
|                     CategoriaId = table.Column<int>(type: "int", nullable: false), | ||||
|                     LogoUrl = table.Column<string>(type: "nvarchar(max)", nullable: true) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("PK_LogosAgrupacionesCategorias", x => x.Id); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "IX_ResumenesVotos_AgrupacionPoliticaId", | ||||
|                 table: "ResumenesVotos", | ||||
|                 column: "AgrupacionPoliticaId"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "IX_LogosAgrupacionesCategorias_AgrupacionPoliticaId_CategoriaId", | ||||
|                 table: "LogosAgrupacionesCategorias", | ||||
|                 columns: new[] { "AgrupacionPoliticaId", "CategoriaId" }, | ||||
|                 unique: true); | ||||
|  | ||||
|             migrationBuilder.AddForeignKey( | ||||
|                 name: "FK_ResumenesVotos_AgrupacionesPoliticas_AgrupacionPoliticaId", | ||||
|                 table: "ResumenesVotos", | ||||
|                 column: "AgrupacionPoliticaId", | ||||
|                 principalTable: "AgrupacionesPoliticas", | ||||
|                 principalColumn: "Id", | ||||
|                 onDelete: ReferentialAction.Cascade); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropForeignKey( | ||||
|                 name: "FK_ResumenesVotos_AgrupacionesPoliticas_AgrupacionPoliticaId", | ||||
|                 table: "ResumenesVotos"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "LogosAgrupacionesCategorias"); | ||||
|  | ||||
|             migrationBuilder.DropIndex( | ||||
|                 name: "IX_ResumenesVotos_AgrupacionPoliticaId", | ||||
|                 table: "ResumenesVotos"); | ||||
|  | ||||
|             migrationBuilder.AlterColumn<string>( | ||||
|                 name: "AgrupacionPoliticaId", | ||||
|                 table: "ResumenesVotos", | ||||
|                 type: "nvarchar(max)", | ||||
|                 nullable: false, | ||||
|                 oldClrType: typeof(string), | ||||
|                 oldType: "nvarchar(450)"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,552 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using Elecciones.Database; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Metadata; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace Elecciones.Database.Migrations | ||||
| { | ||||
|     [DbContext(typeof(EleccionesDbContext))] | ||||
|     [Migration("20250901163521_RemoveLogoUrlFromAgrupacionPolitica")] | ||||
|     partial class RemoveLogoUrlFromAgrupacionPolitica | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .UseCollation("Modern_Spanish_CI_AS") | ||||
|                 .HasAnnotation("ProductVersion", "9.0.8") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 128); | ||||
|  | ||||
|             SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.AdminUser", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("PasswordHash") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("PasswordSalt") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Username") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(100) | ||||
|                         .HasColumnType("nvarchar(100)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("AdminUsers"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.AgrupacionPolitica", b => | ||||
|                 { | ||||
|                     b.Property<string>("Id") | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<string>("Color") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("IdTelegrama") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("NombreCorto") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<int?>("OrdenDiputados") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int?>("OrdenSenadores") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("AgrupacionesPoliticas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.AmbitoGeografico", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("CircuitoId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("DistritoId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("EstablecimientoId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("MesaId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("MunicipioId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<int>("NivelId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("SeccionId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("SeccionProvincialId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("AmbitosGeograficos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("Camara") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("NumeroBanca") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.ToTable("Bancadas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.CategoriaElectoral", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<int>("Orden") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("CategoriasElectorales"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Configuracion", b => | ||||
|                 { | ||||
|                     b.Property<string>("Clave") | ||||
|                         .HasMaxLength(100) | ||||
|                         .HasColumnType("nvarchar(100)"); | ||||
|  | ||||
|                     b.Property<string>("Valor") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(100) | ||||
|                         .HasColumnType("nvarchar(100)"); | ||||
|  | ||||
|                     b.HasKey("Clave"); | ||||
|  | ||||
|                     b.ToTable("Configuraciones"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b => | ||||
|                 { | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadElectores") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadVotantes") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<int>("MesasEsperadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("MesasTotalizadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<decimal>("MesasTotalizadasPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.Property<decimal>("ParticipacionPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.Property<long>("VotosEnBlanco") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosEnBlancoPorcentaje") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.Property<long>("VotosNulos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosNulosPorcentaje") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.Property<long>("VotosRecurridos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosRecurridosPorcentaje") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.HasKey("AmbitoGeograficoId", "CategoriaId"); | ||||
|  | ||||
|                     b.ToTable("EstadosRecuentos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b => | ||||
|                 { | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadElectores") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadVotantes") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<int>("MesasEsperadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("MesasTotalizadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<decimal>("MesasTotalizadasPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.Property<decimal>("ParticipacionPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.HasKey("AmbitoGeograficoId", "CategoriaId"); | ||||
|  | ||||
|                     b.HasIndex("CategoriaId"); | ||||
|  | ||||
|                     b.ToTable("EstadosRecuentosGenerales"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.LogoAgrupacionCategoria", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("LogoUrl") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId", "CategoriaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("LogosAgrupacionesCategorias"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<int>("BancadaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("FotoUrl") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("NombreOcupante") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Periodo") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("BancadaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("OcupantesBancas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<int>("NroBancas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("ProyeccionesBancas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResultadoVoto", b => | ||||
|                 { | ||||
|                     b.Property<long>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<long>("CantidadVotos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<decimal>("PorcentajeVotos") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("ResultadosVotos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<long>("Votos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.ToTable("ResumenesVotos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Telegrama", b => | ||||
|                 { | ||||
|                     b.Property<string>("Id") | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("ContenidoBase64") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaEscaneo") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("Telegramas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AmbitoGeograficoId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AmbitoGeografico"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.CategoriaElectoral", "CategoriaElectoral") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("CategoriaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("CategoriaElectoral"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.Bancada", "Bancada") | ||||
|                         .WithOne("Ocupante") | ||||
|                         .HasForeignKey("Elecciones.Database.Entities.OcupanteBanca", "BancadaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("Bancada"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AmbitoGeograficoId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|  | ||||
|                     b.Navigation("AmbitoGeografico"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResultadoVoto", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AmbitoGeograficoId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|  | ||||
|                     b.Navigation("AmbitoGeografico"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => | ||||
|                 { | ||||
|                     b.Navigation("Ocupante"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,28 @@ | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace Elecciones.Database.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class RemoveLogoUrlFromAgrupacionPolitica : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropColumn( | ||||
|                 name: "LogoUrl", | ||||
|                 table: "AgrupacionesPoliticas"); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AddColumn<string>( | ||||
|                 name: "LogoUrl", | ||||
|                 table: "AgrupacionesPoliticas", | ||||
|                 type: "nvarchar(max)", | ||||
|                 nullable: true); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -61,9 +61,6 @@ namespace Elecciones.Database.Migrations | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("LogoUrl") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
| @@ -275,6 +272,32 @@ namespace Elecciones.Database.Migrations | ||||
|                     b.ToTable("EstadosRecuentosGenerales"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.LogoAgrupacionCategoria", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("LogoUrl") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId", "CategoriaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("LogosAgrupacionesCategorias"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
| @@ -383,7 +406,7 @@ namespace Elecciones.Database.Migrations | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
| @@ -397,6 +420,8 @@ namespace Elecciones.Database.Migrations | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.ToTable("ResumenesVotos"); | ||||
|                 }); | ||||
|  | ||||
| @@ -503,6 +528,17 @@ namespace Elecciones.Database.Migrations | ||||
|                     b.Navigation("AmbitoGeografico"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => | ||||
|                 { | ||||
|                     b.Navigation("Ocupante"); | ||||
|   | ||||
| @@ -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+3b8c6bf754cff6ace486ae8fe850ed4d69233280")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+608ae655bedf6c59be5fec1e14fc308871d2fd62")] | ||||
| [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