diff --git a/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.tsx b/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.tsx
index 6fc6686..8a14c57 100644
--- a/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.tsx
+++ b/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.tsx
@@ -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([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const [editingId, setEditingId] = useState(null);
- const [formData, setFormData] = useState({
- nombreCorto: '',
- color: '#000000',
- logoUrl: '',
+ const queryClient = useQueryClient();
+
+ const [editedAgrupaciones, setEditedAgrupaciones] = useState>>({});
+ const [editedLogos, setEditedLogos] = useState([]);
+
+ // Query 1: Obtener agrupaciones
+ const { data: agrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery({
+ queryKey: ['agrupaciones'],
+ queryFn: getAgrupaciones,
});
- useEffect(() => {
- fetchAgrupaciones();
- }, []);
+ // Query 2: Obtener logos
+ const { data: logos = [], isLoading: isLoadingLogos } = useQuery({
+ 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) => {
- 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 Cargando agrupaciones...
;
- if (error) return {error}
;
return (
-
Gestión de Agrupaciones Políticas
-
-
-
- Nombre
- Nombre Corto
- Color
- Logo URL
- Acciones
-
-
-
- {agrupaciones.map((agrupacion) => (
-
- {editingId === agrupacion.id ? (
- <>
+ Gestión de Agrupaciones y Logos
+ {isLoading ? Cargando...
: (
+ <>
+
+ handleInputChange(agrupacion.id, 'nombreCorto', e.target.value)} />
+ handleInputChange(agrupacion.id, 'color', e.target.value)} />
+ handleLogoChange(agrupacion.id, SENADORES_ID, e.target.value)} />
+ handleLogoChange(agrupacion.id, DIPUTADOS_ID, e.target.value)} />
+ handleLogoChange(agrupacion.id, CONCEJALES_ID, e.target.value)} />
+
+ ))}
+
+
+
+ Guardar Todos los Cambios
+
+ >
+ )}
);
};
\ No newline at end of file
diff --git a/Elecciones-Web/frontend-admin/src/components/ConfiguracionGeneral.tsx b/Elecciones-Web/frontend-admin/src/components/ConfiguracionGeneral.tsx
index 1ab43c6..171afbc 100644
--- a/Elecciones-Web/frontend-admin/src/components/ConfiguracionGeneral.tsx
+++ b/Elecciones-Web/frontend-admin/src/components/ConfiguracionGeneral.tsx
@@ -10,6 +10,8 @@ export const ConfiguracionGeneral = () => {
const [agrupaciones, setAgrupaciones] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
+ const [tickerCantidad, setTickerCantidad] = useState('3');
+ const [concejalesCantidad, setConcejalesCantidad] = useState('5');
const [presidenciaSenadoId, setPresidenciaSenadoId] = useState('');
// 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.
-
+
Presidencia Cámara de Diputados
@@ -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.
+
+ Cantidad en Ticker (Dip/Sen)
+ setTickerCantidad(e.target.value)} />
+
+
+ Cantidad en Widget Concejales
+ setConcejalesCantidad(e.target.value)}
+ />
+
Guardar Configuración
diff --git a/Elecciones-Web/frontend-admin/src/services/apiService.ts b/Elecciones-Web/frontend-admin/src/services/apiService.ts
index 7216e21..40b34c2 100644
--- a/Elecciones-Web/frontend-admin/src/services/apiService.ts
+++ b/Elecciones-Web/frontend-admin/src/services/apiService.ts
@@ -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
=> {
export const updateConfiguracion = async (data: Record): Promise => {
await adminApiClient.put('/configuracion', data);
+};
+
+export const getLogos = async (): Promise => {
+ const response = await adminApiClient.get('/logos');
+ return response.data;
+};
+
+export const updateLogos = async (data: LogoAgrupacionCategoria[]): Promise => {
+ await adminApiClient.put('/logos', data);
};
\ No newline at end of file
diff --git a/Elecciones-Web/frontend-admin/src/types/index.ts b/Elecciones-Web/frontend-admin/src/types/index.ts
index a875434..a0605e0 100644
--- a/Elecciones-Web/frontend-admin/src/types/index.ts
+++ b/Elecciones-Web/frontend-admin/src/types/index.ts
@@ -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;
}
\ No newline at end of file
diff --git a/Elecciones-Web/frontend/src/App.tsx b/Elecciones-Web/frontend/src/App.tsx
index 92f53de..dba97b7 100644
--- a/Elecciones-Web/frontend/src/App.tsx
+++ b/Elecciones-Web/frontend/src/App.tsx
@@ -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() {
Resultados Electorales - Provincia de Buenos Aires
+
diff --git a/Elecciones-Web/frontend/src/apiService.ts b/Elecciones-Web/frontend/src/apiService.ts
index 22241b0..f3429ae 100644
--- a/Elecciones-Web/frontend/src/apiService.ts
+++ b/Elecciones-Web/frontend/src/apiService.ts
@@ -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 => {
+export const getResumenProvincial = async (): Promise => {
const response = await apiClient.get('/resultados/provincia/02');
return response.data;
};
@@ -113,4 +119,14 @@ export const getComposicionCongreso = async (): Promise => {
export const getBancadasDetalle = async (): Promise => {
const response = await apiClient.get('/resultados/bancadas-detalle');
return response.data;
+};
+
+export const getConfiguracionPublica = async (): Promise => {
+ const response = await apiClient.get('/resultados/configuracion-publica');
+ return response.data;
+};
+
+export const getResultadosConcejales = async (seccionId: string): Promise => {
+ const response = await apiClient.get(`/resultados/concejales/${seccionId}`);
+ return response.data;
};
\ No newline at end of file
diff --git a/Elecciones-Web/frontend/src/components/ConcejalesWidget.tsx b/Elecciones-Web/frontend/src/components/ConcejalesWidget.tsx
new file mode 100644
index 0000000..b1ecc64
--- /dev/null
+++ b/Elecciones-Web/frontend/src/components/ConcejalesWidget.tsx
@@ -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([]);
+ const [seccionActualId, setSeccionActualId] = useState('');
+
+ // 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({
+ 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 (
+
+
+
CONCEJALES - LA PLATA
+ setSeccionActualId(e.target.value)} disabled={secciones.length === 0}>
+ {secciones.map(s => {s.nombre} )}
+
+
+
+ {isLoading ?
Cargando...
:
+ displayResults.map(partido => (
+
+
+
+
+
+
+ {partido.nombreCorto || partido.nombre}
+ {formatPercent(partido.votosPorcentaje)}
+
+
+
+
+ ))
+ }
+
+
+ );
+};
\ No newline at end of file
diff --git a/Elecciones-Web/frontend/src/components/CongresoWidget.tsx b/Elecciones-Web/frontend/src/components/CongresoWidget.tsx
index 7b39bc7..826eb59 100644
--- a/Elecciones-Web/frontend/src/components/CongresoWidget.tsx
+++ b/Elecciones-Web/frontend/src/components/CongresoWidget.tsx
@@ -80,17 +80,13 @@ export const CongresoWidget = () => {
{camaraActiva === 'diputados' ? (
) : (
)}
diff --git a/Elecciones-Web/frontend/src/components/ImageWithFallback.tsx b/Elecciones-Web/frontend/src/components/ImageWithFallback.tsx
new file mode 100644
index 0000000..cfe5a15
--- /dev/null
+++ b/Elecciones-Web/frontend/src/components/ImageWithFallback.tsx
@@ -0,0 +1,28 @@
+// src/components/ImageWithFallback.tsx
+import { useState, useEffect } from 'react';
+
+interface Props extends React.ImgHTMLAttributes {
+ 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 (
+
+ );
+};
\ No newline at end of file
diff --git a/Elecciones-Web/frontend/src/components/TelegramaWidget.tsx b/Elecciones-Web/frontend/src/components/TelegramaWidget.tsx
index 0e5ef56..43b7c90 100644
--- a/Elecciones-Web/frontend/src/components/TelegramaWidget.tsx
+++ b/Elecciones-Web/frontend/src/components/TelegramaWidget.tsx
@@ -87,7 +87,7 @@ export const TelegramaWidget = () => {
.finally(() => setLoading(false));
}
}, [selectedMesa]);
-
+
return (
Consulta de Telegramas por Ubicación
diff --git a/Elecciones-Web/frontend/src/components/TickerWidget.css b/Elecciones-Web/frontend/src/components/TickerWidget.css
index 4c41195..de70dc5 100644
--- a/Elecciones-Web/frontend/src/components/TickerWidget.css
+++ b/Elecciones-Web/frontend/src/components/TickerWidget.css
@@ -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 */
}
\ No newline at end of file
diff --git a/Elecciones-Web/frontend/src/components/TickerWidget.tsx b/Elecciones-Web/frontend/src/components/TickerWidget.tsx
index 4f017fb..5ae22e1 100644
--- a/Elecciones-Web/frontend/src/components/TickerWidget.tsx
+++ b/Elecciones-Web/frontend/src/components/TickerWidget.tsx
@@ -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
(null);
- const [loading, setLoading] = useState(true);
- // Se añade un nuevo estado para manejar errores de forma explícita
- const [error, setError] = useState(null);
+ const { data: categorias, isLoading, error } = useQuery({
+ 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 Cargando resumen...
;
+ if (error || !categorias) return No hay datos disponibles.
;
- if (loading) {
- return Cargando resultados provinciales...
;
- }
-
- // Si hay un error, se muestra el mensaje correspondiente
- if (error) {
- return {error}
;
- }
-
- if (!data) {
- return No hay datos disponibles.
;
- }
+ const categoriasFiltradas = categorias.filter(c => c.categoriaId !== 7);
return (
-
-
-
PROVINCIA BS. AS.
-
- Mesas Escrutadas: {formatPercent(data.porcentajeEscrutado)}
- Participación Total: {formatPercent(data.porcentajeParticipacion)}
-
-
-
- {data.resultados.slice(0, 3).map((partido, index) => (
-
-
-
{partido.nombre}
-
{formatPercent(partido.porcentaje)}
+
+ {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 (
+
+
+
{categoria.categoriaNombre}
+
+ Mesas: {formatPercent(categoria.estadoRecuento?.mesasTotalizadasPorcentaje ?? 0)}
+ Part: {formatPercent(categoria.estadoRecuento?.participacionPorcentaje ?? 0)}
+
-
-
+
+ {displayResults.map(partido => (
+
+
+
+
+
+
+ {partido.nombreCorto || partido.nombre}
+ {formatPercent(partido.votosPorcentaje)}
+
+
+
+
+ ))}
- ))}
-
+ );
+ })}
);
};
\ No newline at end of file
diff --git a/Elecciones-Web/frontend/src/types/types.ts b/Elecciones-Web/frontend/src/types/types.ts
index 0dde52b..2dc209b 100644
--- a/Elecciones-Web/frontend/src/types/types.ts
+++ b/Elecciones-Web/frontend/src/types/types.ts
@@ -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;
}
diff --git a/Elecciones-Web/src/Elecciones.Api/Controllers/AdminController.cs b/Elecciones-Web/src/Elecciones.Api/Controllers/AdminController.cs
index 9efedcd..ae22c7e 100644
--- a/Elecciones-Web/src/Elecciones.Api/Controllers/AdminController.cs
+++ b/Elecciones-Web/src/Elecciones.Api/Controllers/AdminController.cs
@@ -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
GetLogos()
+ {
+ return Ok(await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync());
+ }
+
+ [HttpPut("logos")]
+ public async Task UpdateLogos([FromBody] List 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();
+ }
}
\ No newline at end of file
diff --git a/Elecciones-Web/src/Elecciones.Api/Controllers/ResultadosController.cs b/Elecciones-Web/src/Elecciones.Api/Controllers/ResultadosController.cs
index 0e7004e..31e8ed2 100644
--- a/Elecciones-Web/src/Elecciones.Api/Controllers/ResultadosController.cs
+++ b/Elecciones-Web/src/Elecciones.Api/Controllers/ResultadosController.cs
@@ -92,59 +92,69 @@ public class ResultadosController : ControllerBase
[HttpGet("provincia/{distritoId}")]
public async Task 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 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
+ {
+ "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 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());
+ }
+
+ // 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);
+ }
}
\ No newline at end of file
diff --git a/Elecciones-Web/src/Elecciones.Api/Program.cs b/Elecciones-Web/src/Elecciones.Api/Program.cs
index 3c40891..107b70b 100644
--- a/Elecciones-Web/src/Elecciones.Api/Program.cs
+++ b/Elecciones-Web/src/Elecciones.Api/Program.cs
@@ -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.
diff --git a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs
index 88cecb0..a25c2cb 100644
--- a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs
+++ b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs
@@ -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")]
diff --git a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json
index 82c36f1..3f5f3bb 100644
--- a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json
+++ b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json
@@ -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":{}}
\ No newline at end of file
+{"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":{}}
\ No newline at end of file
diff --git a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json
index 999061c..4a985b0 100644
--- a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json
+++ b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json
@@ -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":{}}
\ No newline at end of file
+{"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":{}}
\ No newline at end of file
diff --git a/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs
index e05bc04..2345ed0 100644
--- a/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs
+++ b/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs
@@ -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")]
diff --git a/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs b/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs
index f011b9f..8708534 100644
--- a/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs
+++ b/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs
@@ -19,6 +19,7 @@ public class EleccionesDbContext(DbContextOptions options)
public DbSet Configuraciones { get; set; }
public DbSet Bancadas { get; set; }
public DbSet OcupantesBancas { get; set; }
+ public DbSet LogosAgrupacionesCategorias { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -75,5 +76,9 @@ public class EleccionesDbContext(DbContextOptions options)
// Opcional: puede definir un índice
entity.HasIndex(o => o.BancadaId).IsUnique();
});
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(l => new { l.AgrupacionPoliticaId, l.CategoriaId }).IsUnique();
+ });
}
}
\ No newline at end of file
diff --git a/Elecciones-Web/src/Elecciones.Database/Entities/AgrupacionPolitica.cs b/Elecciones-Web/src/Elecciones.Database/Entities/AgrupacionPolitica.cs
index eec34e5..b0db2dc 100644
--- a/Elecciones-Web/src/Elecciones.Database/Entities/AgrupacionPolitica.cs
+++ b/Elecciones-Web/src/Elecciones.Database/Entities/AgrupacionPolitica.cs
@@ -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; }
}
\ No newline at end of file
diff --git a/Elecciones-Web/src/Elecciones.Database/Entities/LogoAgrupacionCategoria.cs b/Elecciones-Web/src/Elecciones.Database/Entities/LogoAgrupacionCategoria.cs
new file mode 100644
index 0000000..309a2a0
--- /dev/null
+++ b/Elecciones-Web/src/Elecciones.Database/Entities/LogoAgrupacionCategoria.cs
@@ -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; }
+}
\ No newline at end of file
diff --git a/Elecciones-Web/src/Elecciones.Database/Entities/ResumenVoto.cs b/Elecciones-Web/src/Elecciones.Database/Entities/ResumenVoto.cs
index 0186574..1dcd438 100644
--- a/Elecciones-Web/src/Elecciones.Database/Entities/ResumenVoto.cs
+++ b/Elecciones-Web/src/Elecciones.Database/Entities/ResumenVoto.cs
@@ -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; }
}
\ No newline at end of file
diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163255_AddLogoAgrupacionCategoriaTable.Designer.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163255_AddLogoAgrupacionCategoriaTable.Designer.cs
new file mode 100644
index 0000000..a3a4c36
--- /dev/null
+++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163255_AddLogoAgrupacionCategoriaTable.Designer.cs
@@ -0,0 +1,555 @@
+//
+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
+ {
+ ///
+ 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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PasswordSalt")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Username")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.HasKey("Id");
+
+ b.ToTable("AdminUsers");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.AgrupacionPolitica", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Color")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IdTelegrama")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LogoUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Nombre")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NombreCorto")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("OrdenDiputados")
+ .HasColumnType("int");
+
+ b.Property("OrdenSenadores")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("AgrupacionesPoliticas");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.AmbitoGeografico", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CircuitoId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DistritoId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("EstablecimientoId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("MesaId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("MunicipioId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NivelId")
+ .HasColumnType("int");
+
+ b.Property("Nombre")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SeccionId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SeccionProvincialId")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("AmbitosGeograficos");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Camara")
+ .HasColumnType("int");
+
+ b.Property("NumeroBanca")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AgrupacionPoliticaId");
+
+ b.ToTable("Bancadas");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.CategoriaElectoral", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("int");
+
+ b.Property("Nombre")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Orden")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("CategoriasElectorales");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.Configuracion", b =>
+ {
+ b.Property("Clave")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Valor")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.HasKey("Clave");
+
+ b.ToTable("Configuraciones");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b =>
+ {
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("CantidadElectores")
+ .HasColumnType("int");
+
+ b.Property("CantidadVotantes")
+ .HasColumnType("int");
+
+ b.Property("FechaTotalizacion")
+ .HasColumnType("datetime2");
+
+ b.Property("MesasEsperadas")
+ .HasColumnType("int");
+
+ b.Property("MesasTotalizadas")
+ .HasColumnType("int");
+
+ b.Property("MesasTotalizadasPorcentaje")
+ .HasPrecision(5, 2)
+ .HasColumnType("decimal(5,2)");
+
+ b.Property("ParticipacionPorcentaje")
+ .HasPrecision(5, 2)
+ .HasColumnType("decimal(5,2)");
+
+ b.Property("VotosEnBlanco")
+ .HasColumnType("bigint");
+
+ b.Property("VotosEnBlancoPorcentaje")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("VotosNulos")
+ .HasColumnType("bigint");
+
+ b.Property("VotosNulosPorcentaje")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("VotosRecurridos")
+ .HasColumnType("bigint");
+
+ b.Property("VotosRecurridosPorcentaje")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.HasKey("AmbitoGeograficoId", "CategoriaId");
+
+ b.ToTable("EstadosRecuentos");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b =>
+ {
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("CantidadElectores")
+ .HasColumnType("int");
+
+ b.Property("CantidadVotantes")
+ .HasColumnType("int");
+
+ b.Property("FechaTotalizacion")
+ .HasColumnType("datetime2");
+
+ b.Property("MesasEsperadas")
+ .HasColumnType("int");
+
+ b.Property("MesasTotalizadas")
+ .HasColumnType("int");
+
+ b.Property("MesasTotalizadasPorcentaje")
+ .HasPrecision(5, 2)
+ .HasColumnType("decimal(5,2)");
+
+ b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("LogoUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AgrupacionPoliticaId", "CategoriaId")
+ .IsUnique();
+
+ b.ToTable("LogosAgrupacionesCategorias");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("BancadaId")
+ .HasColumnType("int");
+
+ b.Property("FotoUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NombreOcupante")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Periodo")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BancadaId")
+ .IsUnique();
+
+ b.ToTable("OcupantesBancas");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("FechaTotalizacion")
+ .HasColumnType("datetime2");
+
+ b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("CantidadVotos")
+ .HasColumnType("bigint");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("Votos")
+ .HasColumnType("bigint");
+
+ b.Property("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("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("ContenidoBase64")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FechaEscaneo")
+ .HasColumnType("datetime2");
+
+ b.Property("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
+ }
+ }
+}
diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163255_AddLogoAgrupacionCategoriaTable.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163255_AddLogoAgrupacionCategoriaTable.cs
new file mode 100644
index 0000000..c8dce72
--- /dev/null
+++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163255_AddLogoAgrupacionCategoriaTable.cs
@@ -0,0 +1,79 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Elecciones.Database.Migrations
+{
+ ///
+ public partial class AddLogoAgrupacionCategoriaTable : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AlterColumn(
+ 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(type: "int", nullable: false)
+ .Annotation("SqlServer:Identity", "1, 1"),
+ AgrupacionPoliticaId = table.Column(type: "nvarchar(450)", nullable: false),
+ CategoriaId = table.Column(type: "int", nullable: false),
+ LogoUrl = table.Column(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);
+ }
+
+ ///
+ 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(
+ name: "AgrupacionPoliticaId",
+ table: "ResumenesVotos",
+ type: "nvarchar(max)",
+ nullable: false,
+ oldClrType: typeof(string),
+ oldType: "nvarchar(450)");
+ }
+ }
+}
diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163521_RemoveLogoUrlFromAgrupacionPolitica.Designer.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163521_RemoveLogoUrlFromAgrupacionPolitica.Designer.cs
new file mode 100644
index 0000000..e99a9ab
--- /dev/null
+++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163521_RemoveLogoUrlFromAgrupacionPolitica.Designer.cs
@@ -0,0 +1,552 @@
+//
+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
+ {
+ ///
+ 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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PasswordSalt")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Username")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.HasKey("Id");
+
+ b.ToTable("AdminUsers");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.AgrupacionPolitica", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Color")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IdTelegrama")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Nombre")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NombreCorto")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("OrdenDiputados")
+ .HasColumnType("int");
+
+ b.Property("OrdenSenadores")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("AgrupacionesPoliticas");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.AmbitoGeografico", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CircuitoId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DistritoId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("EstablecimientoId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("MesaId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("MunicipioId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NivelId")
+ .HasColumnType("int");
+
+ b.Property("Nombre")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SeccionId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SeccionProvincialId")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("AmbitosGeograficos");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Camara")
+ .HasColumnType("int");
+
+ b.Property("NumeroBanca")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AgrupacionPoliticaId");
+
+ b.ToTable("Bancadas");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.CategoriaElectoral", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("int");
+
+ b.Property("Nombre")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Orden")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("CategoriasElectorales");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.Configuracion", b =>
+ {
+ b.Property("Clave")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Valor")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.HasKey("Clave");
+
+ b.ToTable("Configuraciones");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b =>
+ {
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("CantidadElectores")
+ .HasColumnType("int");
+
+ b.Property("CantidadVotantes")
+ .HasColumnType("int");
+
+ b.Property("FechaTotalizacion")
+ .HasColumnType("datetime2");
+
+ b.Property("MesasEsperadas")
+ .HasColumnType("int");
+
+ b.Property("MesasTotalizadas")
+ .HasColumnType("int");
+
+ b.Property("MesasTotalizadasPorcentaje")
+ .HasPrecision(5, 2)
+ .HasColumnType("decimal(5,2)");
+
+ b.Property("ParticipacionPorcentaje")
+ .HasPrecision(5, 2)
+ .HasColumnType("decimal(5,2)");
+
+ b.Property("VotosEnBlanco")
+ .HasColumnType("bigint");
+
+ b.Property("VotosEnBlancoPorcentaje")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("VotosNulos")
+ .HasColumnType("bigint");
+
+ b.Property("VotosNulosPorcentaje")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("VotosRecurridos")
+ .HasColumnType("bigint");
+
+ b.Property("VotosRecurridosPorcentaje")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.HasKey("AmbitoGeograficoId", "CategoriaId");
+
+ b.ToTable("EstadosRecuentos");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b =>
+ {
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("CantidadElectores")
+ .HasColumnType("int");
+
+ b.Property("CantidadVotantes")
+ .HasColumnType("int");
+
+ b.Property("FechaTotalizacion")
+ .HasColumnType("datetime2");
+
+ b.Property("MesasEsperadas")
+ .HasColumnType("int");
+
+ b.Property("MesasTotalizadas")
+ .HasColumnType("int");
+
+ b.Property("MesasTotalizadasPorcentaje")
+ .HasPrecision(5, 2)
+ .HasColumnType("decimal(5,2)");
+
+ b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("LogoUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AgrupacionPoliticaId", "CategoriaId")
+ .IsUnique();
+
+ b.ToTable("LogosAgrupacionesCategorias");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("BancadaId")
+ .HasColumnType("int");
+
+ b.Property("FotoUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NombreOcupante")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Periodo")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BancadaId")
+ .IsUnique();
+
+ b.ToTable("OcupantesBancas");
+ });
+
+ modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("FechaTotalizacion")
+ .HasColumnType("datetime2");
+
+ b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("CantidadVotos")
+ .HasColumnType("bigint");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("Votos")
+ .HasColumnType("bigint");
+
+ b.Property("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("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AmbitoGeograficoId")
+ .HasColumnType("int");
+
+ b.Property("ContenidoBase64")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FechaEscaneo")
+ .HasColumnType("datetime2");
+
+ b.Property("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
+ }
+ }
+}
diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163521_RemoveLogoUrlFromAgrupacionPolitica.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163521_RemoveLogoUrlFromAgrupacionPolitica.cs
new file mode 100644
index 0000000..35f4704
--- /dev/null
+++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250901163521_RemoveLogoUrlFromAgrupacionPolitica.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Elecciones.Database.Migrations
+{
+ ///
+ public partial class RemoveLogoUrlFromAgrupacionPolitica : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "LogoUrl",
+ table: "AgrupacionesPoliticas");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "LogoUrl",
+ table: "AgrupacionesPoliticas",
+ type: "nvarchar(max)",
+ nullable: true);
+ }
+ }
+}
diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs
index de2bcb4..9d2aee4 100644
--- a/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs
+++ b/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs
@@ -61,9 +61,6 @@ namespace Elecciones.Database.Migrations
.IsRequired()
.HasColumnType("nvarchar(max)");
- b.Property("LogoUrl")
- .HasColumnType("nvarchar(max)");
-
b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AgrupacionPoliticaId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("CategoriaId")
+ .HasColumnType("int");
+
+ b.Property("LogoUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AgrupacionPoliticaId", "CategoriaId")
+ .IsUnique();
+
+ b.ToTable("LogosAgrupacionesCategorias");
+ });
+
modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b =>
{
b.Property("Id")
@@ -383,7 +406,7 @@ namespace Elecciones.Database.Migrations
b.Property("AgrupacionPoliticaId")
.IsRequired()
- .HasColumnType("nvarchar(max)");
+ .HasColumnType("nvarchar(450)");
b.Property("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");
diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs
index 0ab162e..1bbbb55 100644
--- a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs
+++ b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs
@@ -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")]