Feat Widgets

This commit is contained in:
2025-09-01 14:04:40 -03:00
parent 608ae655be
commit 12860f2406
30 changed files with 1904 additions and 247 deletions

View File

@@ -1,113 +1,133 @@
// src/components/AgrupacionesManager.tsx // src/components/AgrupacionesManager.tsx
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { getAgrupaciones, updateAgrupacion } from '../services/apiService'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import type { AgrupacionPolitica, UpdateAgrupacionData } from '../types'; import { getAgrupaciones, updateAgrupacion, getLogos, updateLogos } from '../services/apiService';
import type { AgrupacionPolitica, LogoAgrupacionCategoria } from '../types';
import './AgrupacionesManager.css'; import './AgrupacionesManager.css';
const SENADORES_ID = 5;
const DIPUTADOS_ID = 6;
const CONCEJALES_ID = 7;
export const AgrupacionesManager = () => { export const AgrupacionesManager = () => {
const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]); const queryClient = useQueryClient();
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [editedAgrupaciones, setEditedAgrupaciones] = useState<Record<string, Partial<AgrupacionPolitica>>>({});
const [editingId, setEditingId] = useState<string | null>(null); const [editedLogos, setEditedLogos] = useState<LogoAgrupacionCategoria[]>([]);
const [formData, setFormData] = useState<UpdateAgrupacionData>({
nombreCorto: '', // Query 1: Obtener agrupaciones
color: '#000000', const { data: agrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery<AgrupacionPolitica[]>({
logoUrl: '', queryKey: ['agrupaciones'],
queryFn: getAgrupaciones,
}); });
useEffect(() => { // Query 2: Obtener logos
fetchAgrupaciones(); const { data: logos = [], isLoading: isLoadingLogos } = useQuery<LogoAgrupacionCategoria[]>({
}, []); queryKey: ['logos'],
queryFn: getLogos,
});
const fetchAgrupaciones = async () => { // Usamos useEffect para reaccionar cuando los datos de 'logos' se cargan o cambian.
try { useEffect(() => {
setLoading(true); if (logos) {
const data = await getAgrupaciones(); setEditedLogos(logos);
setAgrupaciones(data);
} catch (err) {
setError('No se pudieron cargar las agrupaciones.');
} finally {
setLoading(false);
} }
}, [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) => { const handleLogoChange = (agrupacionId: string, categoriaId: number, value: string) => {
setEditingId(agrupacion.id); setEditedLogos(prev => {
setFormData({ const newLogos = [...prev];
nombreCorto: agrupacion.nombreCorto || '', const existing = newLogos.find(l => l.agrupacionPoliticaId === agrupacionId && l.categoriaId === categoriaId);
color: agrupacion.color || '#000000', if (existing) {
logoUrl: agrupacion.logoUrl || '', existing.logoUrl = value;
} else {
newLogos.push({ id: 0, agrupacionPoliticaId: agrupacionId, categoriaId, logoUrl: value });
}
return newLogos;
}); });
}; };
const handleCancel = () => { const handleSaveAll = async () => {
setEditingId(null);
};
const handleSave = async (id: string) => {
try { try {
await updateAgrupacion(id, formData); const agrupacionPromises = Object.entries(editedAgrupaciones).map(([id, changes]) => {
setEditingId(null); if (Object.keys(changes).length > 0) {
fetchAgrupaciones(); // Recargar datos para ver los cambios 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) { } 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>) => { const isLoading = isLoadingAgrupaciones || isLoadingLogos;
setFormData({ ...formData, [e.target.name]: e.target.value });
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 ( return (
<div className="admin-module"> <div className="admin-module">
<h3>Gestión de Agrupaciones Políticas</h3> <h3>Gestión de Agrupaciones y Logos</h3>
<table> {isLoading ? <p>Cargando...</p> : (
<thead> <>
<tr> <table>
<th>Nombre</th> <thead>
<th>Nombre Corto</th> <tr>
<th>Color</th> <th>Nombre</th>
<th>Logo URL</th> <th>Nombre Corto</th>
<th>Acciones</th> <th>Color</th>
</tr> <th>Logo Senadores</th>
</thead> <th>Logo Diputados</th>
<tbody> <th>Logo Concejales</th>
{agrupaciones.map((agrupacion) => ( </tr>
<tr key={agrupacion.id}> </thead>
{editingId === agrupacion.id ? ( <tbody>
<> {agrupaciones.map(agrupacion => (
<tr key={agrupacion.id}>
<td>{agrupacion.nombre}</td> <td>{agrupacion.nombre}</td>
<td><input type="text" name="nombreCorto" value={formData.nombreCorto || ''} onChange={handleChange} /></td> <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" name="color" value={formData.color || '#000000'} onChange={handleChange} /></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" name="logoUrl" value={formData.logoUrl || ''} onChange={handleChange} /></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> <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>
<button onClick={() => handleSave(agrupacion.id)}>Guardar</button> <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>
<button onClick={handleCancel}>Cancelar</button> </tr>
</td> ))}
</> </tbody>
) : ( </table>
<> <button onClick={handleSaveAll} style={{ marginTop: '1rem' }}>
<td>{agrupacion.nombre}</td> Guardar Todos los Cambios
<td>{agrupacion.nombreCorto}</td> </button>
<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>
</div> </div>
); );
}; };

View File

@@ -10,6 +10,8 @@ export const ConfiguracionGeneral = () => {
const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]); const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [tickerCantidad, setTickerCantidad] = useState('3');
const [concejalesCantidad, setConcejalesCantidad] = useState('5');
const [presidenciaSenadoId, setPresidenciaSenadoId] = useState<string>(''); const [presidenciaSenadoId, setPresidenciaSenadoId] = useState<string>('');
// Renombramos el estado para mayor claridad // Renombramos el estado para mayor claridad
@@ -24,6 +26,8 @@ export const ConfiguracionGeneral = () => {
setAgrupaciones(agrupacionesData); setAgrupaciones(agrupacionesData);
setPresidenciaSenadoId(configData.PresidenciaSenadores || ''); setPresidenciaSenadoId(configData.PresidenciaSenadores || '');
setModoOficialActivo(configData.UsarDatosDeBancadasOficiales === 'true'); setModoOficialActivo(configData.UsarDatosDeBancadasOficiales === 'true');
setTickerCantidad(configData.TickerResultadosCantidad || '3');
setConcejalesCantidad(configData.ConcejalesResultadosCantidad || '5');
} catch (err) { } catch (err) {
console.error("Error al cargar datos de configuración:", err); console.error("Error al cargar datos de configuración:", err);
setError("No se pudieron cargar los datos necesarios para la configuración."); setError("No se pudieron cargar los datos necesarios para la configuración.");
@@ -36,7 +40,9 @@ export const ConfiguracionGeneral = () => {
try { try {
await updateConfiguracion({ await updateConfiguracion({
"PresidenciaSenadores": presidenciaSenadoId, "PresidenciaSenadores": presidenciaSenadoId,
"UsarDatosDeBancadasOficiales": modoOficialActivo.toString() "UsarDatosDeBancadasOficiales": modoOficialActivo.toString(),
"TickerResultadosCantidad": tickerCantidad,
"ConcejalesResultadosCantidad": concejalesCantidad
}); });
await queryClient.invalidateQueries({ queryKey: ['composicionCongreso'] }); await queryClient.invalidateQueries({ queryKey: ['composicionCongreso'] });
await queryClient.invalidateQueries({ queryKey: ['bancadasDetalle'] }); 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. Seleccione el partido político al que pertenece el Vicegobernador. El asiento presidencial del Senado se pintará con el color de este partido.
</p> </p>
</div> </div>
<div style={{ marginTop: '1rem' }}> <div style={{ marginTop: '1rem', borderBottom: '2px solid #eee' }}>
<p style={{ fontWeight: 'bold', margin: 0 }}> <p style={{ fontWeight: 'bold', margin: 0 }}>
Presidencia Cámara de Diputados Presidencia Cámara de Diputados
</p> </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. 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> </p>
</div> </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' }}> <button onClick={handleSave} style={{ marginTop: '1.5rem' }}>
Guardar Configuración Guardar Configuración
</button> </button>

View File

@@ -1,7 +1,7 @@
// src/services/apiService.ts // src/services/apiService.ts
import axios from 'axios'; import axios from 'axios';
import { triggerLogout } from '../context/authUtils'; 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 AUTH_API_URL = 'http://localhost:5217/api/auth';
const ADMIN_API_URL = 'http://localhost:5217/api/admin'; 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> => { export const updateConfiguracion = async (data: Record<string, string>): Promise<void> => {
await adminApiClient.put('/configuracion', data); 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);
}; };

View File

@@ -6,7 +6,6 @@ export interface AgrupacionPolitica {
nombre: string; nombre: string;
nombreCorto: string | null; nombreCorto: string | null;
color: string | null; color: string | null;
logoUrl: string | null;
ordenDiputados: number | null; ordenDiputados: number | null;
ordenSenadores: number | null; ordenSenadores: number | null;
} }
@@ -14,7 +13,6 @@ export interface AgrupacionPolitica {
export interface UpdateAgrupacionData { export interface UpdateAgrupacionData {
nombreCorto: string | null; nombreCorto: string | null;
color: string | null; color: string | null;
logoUrl: string | null;
} }
export const TipoCamara = { export const TipoCamara = {
@@ -40,4 +38,11 @@ export interface Bancada {
agrupacionPoliticaId: string | null; agrupacionPoliticaId: string | null;
agrupacionPolitica: AgrupacionPolitica | null; agrupacionPolitica: AgrupacionPolitica | null;
ocupante: OcupanteBanca | null; ocupante: OcupanteBanca | null;
}
export interface LogoAgrupacionCategoria {
id: number;
agrupacionPoliticaId: string;
categoriaId: number;
logoUrl: string | null;
} }

View File

@@ -5,6 +5,7 @@ import { CongresoWidget } from './components/CongresoWidget'
import MapaBsAs from './components/MapaBsAs' import MapaBsAs from './components/MapaBsAs'
import { TickerWidget } from './components/TickerWidget' import { TickerWidget } from './components/TickerWidget'
import { TelegramaWidget } from './components/TelegramaWidget' import { TelegramaWidget } from './components/TelegramaWidget'
import { ConcejalesWidget } from './components/ConcejalesWidget'
function App() { function App() {
return ( return (
@@ -12,6 +13,7 @@ function App() {
<h1>Resultados Electorales - Provincia de Buenos Aires</h1> <h1>Resultados Electorales - Provincia de Buenos Aires</h1>
<main> <main>
<TickerWidget /> <TickerWidget />
<ConcejalesWidget />
<CongresoWidget /> <CongresoWidget />
<BancasWidget /> <BancasWidget />
<MapaBsAs /> <MapaBsAs />

View File

@@ -1,6 +1,13 @@
// src/apiService.ts // src/apiService.ts
import axios from 'axios'; 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 { interface PartidoData {
id: string; id: string;
@@ -47,14 +54,13 @@ export interface BancadaDetalle {
ocupante: OcupanteBanca | null; 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({ export const getResumenProvincial = async (): Promise<CategoriaResumen[]> => {
baseURL: API_BASE_URL,
headers: { 'Content-Type': 'application/json' },
});
export const getResumenProvincial = async (): Promise<ResumenProvincial> => {
const response = await apiClient.get('/resultados/provincia/02'); const response = await apiClient.get('/resultados/provincia/02');
return response.data; return response.data;
}; };
@@ -113,4 +119,14 @@ export const getComposicionCongreso = async (): Promise<ComposicionData> => {
export const getBancadasDetalle = async (): Promise<BancadaDetalle[]> => { export const getBancadasDetalle = async (): Promise<BancadaDetalle[]> => {
const response = await apiClient.get('/resultados/bancadas-detalle'); const response = await apiClient.get('/resultados/bancadas-detalle');
return response.data; 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;
}; };

View 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>
);
};

View File

@@ -80,17 +80,13 @@ export const CongresoWidget = () => {
{camaraActiva === 'diputados' ? ( {camaraActiva === 'diputados' ? (
<ParliamentLayout <ParliamentLayout
seatData={seatFillData} seatData={seatFillData}
// --- INICIO DE LA CORRECCIÓN ---
// Solo pasamos la prop 'presidenteBancada' si NO estamos en modo oficial // Solo pasamos la prop 'presidenteBancada' si NO estamos en modo oficial
presidenteBancada={!esModoOficial ? datosCamaraActual.presidenteBancada : undefined} presidenteBancada={!esModoOficial ? datosCamaraActual.presidenteBancada : undefined}
// --- FIN DE LA CORRECCIÓN ---
/> />
) : ( ) : (
<SenateLayout <SenateLayout
seatData={seatFillData} seatData={seatFillData}
// --- INICIO DE LA CORRECCIÓN ---
presidenteBancada={!esModoOficial ? datosCamaraActual.presidenteBancada : undefined} presidenteBancada={!esModoOficial ? datosCamaraActual.presidenteBancada : undefined}
// --- FIN DE LA CORRECCIÓN ---
/> />
)} )}
</div> </div>

View 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}
/>
);
};

View File

@@ -87,7 +87,7 @@ export const TelegramaWidget = () => {
.finally(() => setLoading(false)); .finally(() => setLoading(false));
} }
}, [selectedMesa]); }, [selectedMesa]);
return ( return (
<div className="telegrama-container"> <div className="telegrama-container">
<h4>Consulta de Telegramas por Ubicación</h4> <h4>Consulta de Telegramas por Ubicación</h4>

View File

@@ -1,22 +1,20 @@
/* src/components/TickerWidget.css */ /* src/components/TickerWidget.css */
.ticker-container { .ticker-wrapper {
/* Se cambia a un fondo claro con borde y sombra sutil */ display: grid;
background-color: #ffffff; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
border: 1px solid #e0e0e0; gap: 1.5rem;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); width: 100%;
padding: 15px 20px; max-width: 1280px;
border-radius: 8px; margin: 20px auto;
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-card {
.ticker-container.loading, .ticker-container.error { background-color: #ffffff;
text-align: center; border: 1px solid #e0e0e0;
padding: 30px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
font-style: italic; padding: 15px 20px;
color: #757575; /* Color de texto atenuado */ border-radius: 8px;
display: flex;
flex-direction: column;
} }
.ticker-header { .ticker-header {
@@ -48,9 +46,9 @@
} }
.ticker-results { .ticker-results {
display: grid; display: flex;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); flex-direction: column;
gap: 20px; gap: 12px; /* Espacio entre partidos */
} }
.ticker-party .party-info { .ticker-party .party-info {
@@ -84,4 +82,31 @@
border-radius: 4px; border-radius: 4px;
transition: width 0.5s ease-in-out; transition: width 0.5s ease-in-out;
/* El color de fondo se sigue aplicando desde el componente, esto es correcto */ /* 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 */
} }

View File

@@ -1,78 +1,92 @@
// src/components/TickerWidget.tsx // src/components/TickerWidget.tsx
import { useState, useEffect } from 'react'; import { useQuery } from '@tanstack/react-query';
import { getResumenProvincial } from '../apiService'; import { getResumenProvincial, getConfiguracionPublica } from '../apiService';
import type { ResumenProvincial } from '../types/types'; import type { CategoriaResumen, ResultadoTicker } from '../types/types';
import { ImageWithFallback } from './ImageWithFallback';
import './TickerWidget.css'; import './TickerWidget.css';
const formatPercent = (num: number) => `${num.toFixed(2).replace('.', ',')}%`; const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
const COLORS = [
"#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
"#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"
];
export const TickerWidget = () => { export const TickerWidget = () => {
const [data, setData] = useState<ResumenProvincial | null>(null); const { data: categorias, isLoading, error } = useQuery<CategoriaResumen[]>({
const [loading, setLoading] = useState(true); queryKey: ['resumenProvincial'],
// Se añade un nuevo estado para manejar errores de forma explícita queryFn: getResumenProvincial,
const [error, setError] = useState<string | null>(null); refetchInterval: 30000,
});
useEffect(() => { const { data: configData } = useQuery({
const fetchData = async () => { queryKey: ['configuracionPublica'],
// Se resetea el error en cada intento de carga queryFn: getConfiguracionPublica,
setError(null); staleTime: 0,
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);
}
};
fetchData(); // Carga inicial const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10) + 1;
const intervalId = setInterval(fetchData, 30000); // Actualiza cada 30 segundos
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) { const categoriasFiltradas = categorias.filter(c => c.categoriaId !== 7);
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>;
}
return ( return (
<div className="ticker-container"> <div className="ticker-wrapper">
<div className="ticker-header"> {categoriasFiltradas.map(categoria => {
<h3>PROVINCIA BS. AS.</h3>
<div className="ticker-stats"> let displayResults: ResultadoTicker[] = categoria.resultados;
<span>Mesas Escrutadas: <strong>{formatPercent(data.porcentajeEscrutado)}</strong></span>
<span>Participación Total: <strong>{formatPercent(data.porcentajeParticipacion)}</strong></span> if (categoria.resultados.length > cantidadAMostrar) {
</div> const topParties = categoria.resultados.slice(0, cantidadAMostrar - 1);
</div> const otherParties = categoria.resultados.slice(cantidadAMostrar - 1);
<div className="ticker-results"> const otrosPorcentaje = otherParties.reduce((sum, party) => sum + party.votosPorcentaje, 0);
{data.resultados.slice(0, 3).map((partido, index) => (
<div key={`${partido.nombre}-${index}`} className="ticker-party"> const otrosEntry: ResultadoTicker = {
<div className="party-info"> id: `otros-${categoria.categoriaId}`,
<span className="party-name">{partido.nombre}</span> nombre: 'Otros',
<span className="party-percent">{formatPercent(partido.porcentaje)}</span> 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>
<div className="party-bar-background"> <div className="ticker-results">
<div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor:COLORS[index % COLORS.length] }}></div> {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>
))} );
</div> })}
</div> </div>
); );
}; };

View File

@@ -40,22 +40,46 @@ export interface GeographyObject {
} }
export interface MunicipioSimple { id: string; nombre: string; } 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 VotosAdicionales { enBlanco: number; nulos: number; recurridos: number; }
export interface MunicipioDetalle { export interface MunicipioDetalle {
municipioNombre: string; municipioNombre: string;
ultimaActualizacion: string; ultimaActualizacion: string;
porcentajeEscrutado: number; porcentajeEscrutado: number;
porcentajeParticipacion: number; porcentajeParticipacion: number;
resultados: AgrupacionResultado[]; resultados: CategoriaResumen[];
votosAdicionales: VotosAdicionales; votosAdicionales: VotosAdicionales;
} }
export interface ResumenProvincial { export interface ResumenProvincial {
provinciaNombre: string; provinciaNombre: string;
ultimaActualizacion: string; ultimaActualizacion: string;
porcentajeEscrutado: number; porcentajeEscrutado: number;
porcentajeParticipacion: number; porcentajeParticipacion: number;
resultados: AgrupacionResultado[]; resultados: CategoriaResumen[];
votosAdicionales: VotosAdicionales; votosAdicionales: VotosAdicionales;
} }

View File

@@ -50,7 +50,6 @@ public class AdminController : ControllerBase
// Actualizamos las propiedades de la entidad con los valores del DTO. // Actualizamos las propiedades de la entidad con los valores del DTO.
agrupacion.NombreCorto = agrupacionDto.NombreCorto; agrupacion.NombreCorto = agrupacionDto.NombreCorto;
agrupacion.Color = agrupacionDto.Color; agrupacion.Color = agrupacionDto.Color;
agrupacion.LogoUrl = agrupacionDto.LogoUrl;
// Guardamos los cambios en la base de datos. // Guardamos los cambios en la base de datos.
await _dbContext.SaveChangesAsync(); await _dbContext.SaveChangesAsync();
@@ -178,4 +177,32 @@ public class AdminController : ControllerBase
return NoContent(); 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();
}
} }

View File

@@ -92,59 +92,69 @@ public class ResultadosController : ControllerBase
[HttpGet("provincia/{distritoId}")] [HttpGet("provincia/{distritoId}")]
public async Task<IActionResult> GetResultadosProvinciales(string 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() var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking()
.FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10); .FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10);
if (provincia == null) var todosLosResumenes = await _dbContext.ResumenesVotos.AsNoTracking()
{ .Include(r => r.AgrupacionPolitica)
_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)
.ToListAsync(); .ToListAsync();
// PASO 4: Construir el objeto de respuesta (DTO). // OBTENER TODOS LOS LOGOS EN UNA SOLA CONSULTA
// Si no hay datos de recuento aún, usamos valores por defecto para evitar errores en el frontend. var logosLookup = (await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync())
var respuestaDto = new ResumenProvincialDto .ToLookup(l => $"{l.AgrupacionPoliticaId}-{l.CategoriaId}");
{
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 }
};
_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); 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);
}
} }

View File

@@ -141,6 +141,13 @@ using (var scope = app.Services.CreateScope())
context.SaveChanges(); context.SaveChanges();
Console.WriteLine("--> Seeded default configuration 'MostrarOcupantes'."); 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. // Configurar el pipeline de peticiones HTTP.

View File

@@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3b8c6bf754cff6ace486ae8fe850ed4d69233280")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+608ae655bedf6c59be5fec1e14fc308871d2fd62")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -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":{}}

View File

@@ -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":{}}

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3b8c6bf754cff6ace486ae8fe850ed4d69233280")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+608ae655bedf6c59be5fec1e14fc308871d2fd62")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -19,6 +19,7 @@ public class EleccionesDbContext(DbContextOptions<EleccionesDbContext> options)
public DbSet<Configuracion> Configuraciones { get; set; } public DbSet<Configuracion> Configuraciones { get; set; }
public DbSet<Bancada> Bancadas { get; set; } public DbSet<Bancada> Bancadas { get; set; }
public DbSet<OcupanteBanca> OcupantesBancas { get; set; } public DbSet<OcupanteBanca> OcupantesBancas { get; set; }
public DbSet<LogoAgrupacionCategoria> LogosAgrupacionesCategorias { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
@@ -75,5 +76,9 @@ public class EleccionesDbContext(DbContextOptions<EleccionesDbContext> options)
// Opcional: puede definir un índice // Opcional: puede definir un índice
entity.HasIndex(o => o.BancadaId).IsUnique(); entity.HasIndex(o => o.BancadaId).IsUnique();
}); });
modelBuilder.Entity<LogoAgrupacionCategoria>(entity =>
{
entity.HasIndex(l => new { l.AgrupacionPoliticaId, l.CategoriaId }).IsUnique();
});
} }
} }

View File

@@ -12,8 +12,6 @@ public class AgrupacionPolitica
public string Nombre { get; set; } = null!; public string Nombre { get; set; } = null!;
public string? NombreCorto { get; set; } // Para leyendas y gráficos public string? NombreCorto { get; set; } // Para leyendas y gráficos
public string? Color { get; set; } // Código hexadecimal, ej: "#1f77b4" 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? OrdenDiputados { get; set; }
public int? OrdenSenadores { get; set; } public int? OrdenSenadores { get; set; }
} }

View File

@@ -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; }
}

View File

@@ -1,3 +1,4 @@
// src/Elecciones.Database/Entities/ResumenVoto.cs
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@@ -9,11 +10,18 @@ public class ResumenVoto
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; } public int Id { get; set; }
// El ámbito donde se resume (siempre provincial en este caso) [Required]
public int AmbitoGeograficoId { get; set; } public int AmbitoGeograficoId { get; set; }
[Required]
public string AgrupacionPoliticaId { get; set; } = null!; public string AgrupacionPoliticaId { get; set; } = null!;
[ForeignKey("AgrupacionPoliticaId")]
public AgrupacionPolitica AgrupacionPolitica { get; set; } = null!;
[Required]
public long Votos { get; set; } public long Votos { get; set; }
[Required]
public decimal VotosPorcentaje { get; set; } public decimal VotosPorcentaje { get; set; }
} }

View File

@@ -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
}
}
}

View File

@@ -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)");
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -61,9 +61,6 @@ namespace Elecciones.Database.Migrations
.IsRequired() .IsRequired()
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
b.Property<string>("LogoUrl")
.HasColumnType("nvarchar(max)");
b.Property<string>("Nombre") b.Property<string>("Nombre")
.IsRequired() .IsRequired()
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
@@ -275,6 +272,32 @@ namespace Elecciones.Database.Migrations
b.ToTable("EstadosRecuentosGenerales"); 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 => modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -383,7 +406,7 @@ namespace Elecciones.Database.Migrations
b.Property<string>("AgrupacionPoliticaId") b.Property<string>("AgrupacionPoliticaId")
.IsRequired() .IsRequired()
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(450)");
b.Property<int>("AmbitoGeograficoId") b.Property<int>("AmbitoGeograficoId")
.HasColumnType("int"); .HasColumnType("int");
@@ -397,6 +420,8 @@ namespace Elecciones.Database.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("AgrupacionPoliticaId");
b.ToTable("ResumenesVotos"); b.ToTable("ResumenesVotos");
}); });
@@ -503,6 +528,17 @@ namespace Elecciones.Database.Migrations
b.Navigation("AmbitoGeografico"); 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 => modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b =>
{ {
b.Navigation("Ocupante"); b.Navigation("Ocupante");

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3b8c6bf754cff6ace486ae8fe850ed4d69233280")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+608ae655bedf6c59be5fec1e14fc308871d2fd62")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]