Feat Widgets

Se añade la tabla CandidatosOverrides
Se añade el Overrides de Candidatos al panel de administrador
Se Añade el nombre de los candidatos a los Widgets de categorias por municipio
This commit is contained in:
2025-09-05 11:38:25 -03:00
parent 479c2c60f2
commit d78a02a0eb
23 changed files with 1033 additions and 38 deletions

View File

@@ -0,0 +1,86 @@
import { useState, useMemo, useEffect } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import Select from 'react-select';
import { getMunicipiosForAdmin, getAgrupaciones, getCandidatos, updateCandidatos } from '../services/apiService';
import type { MunicipioSimple, AgrupacionPolitica, CandidatoOverride } from '../types';
// Las categorías son las mismas que para los logos
const CATEGORIAS_OPTIONS = [
{ value: 5, label: 'Senadores' },
{ value: 6, label: 'Diputados' },
{ value: 7, label: 'Concejales' }
];
export const CandidatoOverridesManager = () => {
const queryClient = useQueryClient();
const { data: municipios = [] } = useQuery<MunicipioSimple[]>({ queryKey: ['municipiosForAdmin'], queryFn: getMunicipiosForAdmin });
const { data: agrupaciones = [] } = useQuery<AgrupacionPolitica[]>({ queryKey: ['agrupaciones'], queryFn: getAgrupaciones });
// --- Usar la query para candidatos ---
const { data: candidatos = [] } = useQuery<CandidatoOverride[]>({ queryKey: ['candidatos'], queryFn: getCandidatos });
const [selectedCategoria, setSelectedCategoria] = useState<{ value: number; label: string } | null>(null);
const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null);
const [selectedAgrupacion, setSelectedAgrupacion] = useState<{ value: string; label: string } | null>(null);
// --- El estado es para el nombre del candidato ---
const [nombreCandidato, setNombreCandidato] = useState('');
const municipioOptions = useMemo(() => municipios.map(m => ({ value: m.id, label: m.nombre })), [municipios]);
const agrupacionOptions = useMemo(() => agrupaciones.map(a => ({ value: a.id, label: a.nombre })), [agrupaciones]);
// --- Lógica para encontrar el nombre del candidato actual ---
const currentCandidato = useMemo(() => {
if (!selectedMunicipio || !selectedAgrupacion || !selectedCategoria) return '';
return candidatos.find(c =>
c.ambitoGeograficoId === parseInt(selectedMunicipio.value) &&
c.agrupacionPoliticaId === selectedAgrupacion.value &&
c.categoriaId === selectedCategoria.value
)?.nombreCandidato || '';
}, [candidatos, selectedMunicipio, selectedAgrupacion, selectedCategoria]);
useEffect(() => { setNombreCandidato(currentCandidato) }, [currentCandidato]);
const handleSave = async () => {
if (!selectedMunicipio || !selectedAgrupacion || !selectedCategoria) return;
// --- Construir el objeto CandidatoOverride ---
const newCandidatoEntry: CandidatoOverride = {
id: 0, // El backend no necesita el ID para un upsert
agrupacionPoliticaId: selectedAgrupacion.value,
categoriaId: selectedCategoria.value,
ambitoGeograficoId: parseInt(selectedMunicipio.value),
nombreCandidato: nombreCandidato
};
try {
await updateCandidatos([newCandidatoEntry]);
queryClient.invalidateQueries({ queryKey: ['candidatos'] });
alert('Override de candidato guardado.');
} catch { alert('Error al guardar.'); }
};
return (
<div className="admin-module">
<h3>Overrides de Nombres de Candidatos</h3>
<p>Configure un nombre de candidato específico para un partido en un municipio y categoría determinados.</p>
<div style={{ display: 'flex', gap: '1rem', alignItems: 'flex-end' }}>
<div style={{ flex: 1 }}>
<label>Categoría</label>
<Select options={CATEGORIAS_OPTIONS} value={selectedCategoria} onChange={setSelectedCategoria} isClearable placeholder="Seleccione..."/>
</div>
<div style={{ flex: 1 }}>
<label>Municipio</label>
<Select options={municipioOptions} value={selectedMunicipio} onChange={setSelectedMunicipio} isClearable placeholder="Seleccione..."/>
</div>
<div style={{ flex: 1 }}>
<label>Agrupación</label>
<Select options={agrupacionOptions} value={selectedAgrupacion} onChange={setSelectedAgrupacion} isClearable placeholder="Seleccione..."/>
</div>
<div style={{ flex: 2 }}>
<label>Nombre del Candidato</label>
<input type="text" value={nombreCandidato} onChange={e => setNombreCandidato(e.target.value)} style={{ width: '100%' }} disabled={!selectedMunicipio || !selectedAgrupacion || !selectedCategoria} />
</div>
<button onClick={handleSave} disabled={!selectedMunicipio || !selectedAgrupacion || !selectedCategoria}>Guardar</button>
</div>
</div>
);
};

View File

@@ -6,6 +6,7 @@ import { OrdenSenadoresManager } from './OrdenSenadoresManager';
import { ConfiguracionGeneral } from './ConfiguracionGeneral'; import { ConfiguracionGeneral } from './ConfiguracionGeneral';
import { BancasManager } from './BancasManager'; import { BancasManager } from './BancasManager';
import { LogoOverridesManager } from './LogoOverridesManager'; import { LogoOverridesManager } from './LogoOverridesManager';
import { CandidatoOverridesManager } from './CandidatoOverridesManager';
export const DashboardPage = () => { export const DashboardPage = () => {
const { logout } = useAuth(); const { logout } = useAuth();
@@ -16,9 +17,14 @@ export const DashboardPage = () => {
<h1>Panel de Administración Electoral</h1> <h1>Panel de Administración Electoral</h1>
<button onClick={logout}>Cerrar Sesión</button> <button onClick={logout}>Cerrar Sesión</button>
</header> </header>
<main style={{ marginTop: '2rem' }}> <main style={{ marginTop: '2rem' }}>
<AgrupacionesManager /> <AgrupacionesManager />
<LogoOverridesManager /> <div style={{ flex: '1 1 800px' }}>
<LogoOverridesManager />
</div>
<div style={{ flex: '1 1 800px' }}>
<CandidatoOverridesManager />
</div>
<div style={{ display: 'flex', gap: '2rem', flexWrap: 'wrap', marginTop: '2rem' }}> <div style={{ display: 'flex', gap: '2rem', flexWrap: 'wrap', marginTop: '2rem' }}>
<div style={{ flex: '1 1 400px' }}> <div style={{ flex: '1 1 400px' }}>
<OrdenDiputadosManager /> <OrdenDiputadosManager />
@@ -27,7 +33,7 @@ export const DashboardPage = () => {
<OrdenSenadoresManager /> <OrdenSenadoresManager />
</div> </div>
</div> </div>
<ConfiguracionGeneral /> <ConfiguracionGeneral />
<BancasManager /> <BancasManager />
</main> </main>
</div> </div>

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, LogoAgrupacionCategoria, MunicipioSimple } from '../types'; import type { CandidatoOverride, AgrupacionPolitica, UpdateAgrupacionData, Bancada, LogoAgrupacionCategoria, MunicipioSimple } from '../types';
/** /**
* URL base para las llamadas a la API. * URL base para las llamadas a la API.
@@ -125,4 +125,14 @@ export const getMunicipiosForAdmin = async (): Promise<MunicipioSimple[]> => {
// La URL final será /api/admin/catalogos/municipios // La URL final será /api/admin/catalogos/municipios
const response = await adminApiClient.get('/catalogos/municipios'); const response = await adminApiClient.get('/catalogos/municipios');
return response.data; return response.data;
};
// 6. Overrides de Candidatos
export const getCandidatos = async (): Promise<CandidatoOverride[]> => {
const response = await adminApiClient.get('/candidatos');
return response.data;
};
export const updateCandidatos = async (data: CandidatoOverride[]): Promise<void> => {
await adminApiClient.put('/candidatos', data);
}; };

View File

@@ -48,4 +48,12 @@ export interface LogoAgrupacionCategoria {
ambitoGeograficoId: number | null; ambitoGeograficoId: number | null;
} }
export interface MunicipioSimple { id: string; nombre: string; } export interface MunicipioSimple { id: string; nombre: string; }
export interface CandidatoOverride {
id: number;
agrupacionPoliticaId: string;
categoriaId: number;
ambitoGeograficoId: number | null;
nombreCandidato: string;
}

View File

@@ -27,11 +27,11 @@ export const ConcejalesWidget = () => {
// 2. Query para la lista de municipios // 2. Query para la lista de municipios
const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({ const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({
// Usamos una clave genérica porque siempre pedimos la lista completa. // Usamos una clave genérica porque siempre pedimos la lista completa.
queryKey: ['municipios'], queryKey: ['municipios'],
// Llamamos a la función sin argumentos para obtener todos los municipios. // Llamamos a la función sin argumentos para obtener todos los municipios.
queryFn: () => getMunicipios(), queryFn: () => getMunicipios(),
}); });
const cantidadAMostrar = parseInt(configData?.ConcejalesResultadosCantidad || '5', 10); const cantidadAMostrar = parseInt(configData?.ConcejalesResultadosCantidad || '5', 10);
@@ -48,7 +48,7 @@ export const ConcejalesWidget = () => {
municipios municipios
.map(m => ({ value: m.id, label: m.nombre })) .map(m => ({ value: m.id, label: m.nombre }))
.sort((a, b) => a.label.localeCompare(b.label)), .sort((a, b) => a.label.localeCompare(b.label)),
[municipios]); [municipios]);
const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({ const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({
queryKey: ['resultadosPorMunicipio', selectedMunicipio?.value, CATEGORIA_ID], queryKey: ['resultadosPorMunicipio', selectedMunicipio?.value, CATEGORIA_ID],
@@ -92,7 +92,7 @@ export const ConcejalesWidget = () => {
</div> </div>
<div className="ticker-results"> <div className="ticker-results">
{(isLoadingMunicipios || (isLoadingResultados && selectedMunicipio)) && <p>Cargando...</p>} {(isLoadingMunicipios || (isLoadingResultados && selectedMunicipio)) && <p>Cargando...</p>}
{!selectedMunicipio && !isLoadingMunicipios && <p style={{textAlign: 'center', color: '#666'}}>Seleccione un municipio.</p>} {!selectedMunicipio && !isLoadingMunicipios && <p style={{ textAlign: 'center', color: '#666' }}>Seleccione un municipio.</p>}
{displayResults.map(partido => ( {displayResults.map(partido => (
<div key={partido.id} className="ticker-party"> <div key={partido.id} className="ticker-party">
<div className="party-logo"> <div className="party-logo">
@@ -106,6 +106,9 @@ export const ConcejalesWidget = () => {
<div className="party-bar-background"> <div className="party-bar-background">
<div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor: partido.color || '#888' }}></div> <div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor: partido.color || '#888' }}></div>
</div> </div>
<div className="party-candidate-name">
{partido.nombreCandidato}
</div>
</div> </div>
</div> </div>
))} ))}

View File

@@ -50,7 +50,7 @@ export const DiputadosWidget = () => {
municipios municipios
.map(m => ({ value: m.id, label: m.nombre })) .map(m => ({ value: m.id, label: m.nombre }))
.sort((a, b) => a.label.localeCompare(b.label)), .sort((a, b) => a.label.localeCompare(b.label)),
[municipios]); [municipios]);
const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({ const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({
queryKey: ['resultadosMunicipio', selectedMunicipio?.value, CATEGORIA_ID], queryKey: ['resultadosMunicipio', selectedMunicipio?.value, CATEGORIA_ID],
@@ -94,7 +94,7 @@ export const DiputadosWidget = () => {
</div> </div>
<div className="ticker-results"> <div className="ticker-results">
{(isLoadingMunicipios || (isLoadingResultados && selectedMunicipio)) && <p>Cargando...</p>} {(isLoadingMunicipios || (isLoadingResultados && selectedMunicipio)) && <p>Cargando...</p>}
{!selectedMunicipio && !isLoadingMunicipios && <p style={{textAlign: 'center', color: '#666'}}>Seleccione un municipio.</p>} {!selectedMunicipio && !isLoadingMunicipios && <p style={{ textAlign: 'center', color: '#666' }}>Seleccione un municipio.</p>}
{displayResults.map(partido => ( {displayResults.map(partido => (
<div key={partido.id} className="ticker-party"> <div key={partido.id} className="ticker-party">
<div className="party-logo"> <div className="party-logo">
@@ -108,6 +108,9 @@ export const DiputadosWidget = () => {
<div className="party-bar-background"> <div className="party-bar-background">
<div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor: partido.color || '#888' }}></div> <div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor: partido.color || '#888' }}></div>
</div> </div>
<div className="party-candidate-name">
{partido.nombreCandidato}
</div>
</div> </div>
</div> </div>
))} ))}

View File

@@ -107,6 +107,9 @@ export const SenadoresWidget = () => {
<div className="party-bar-background"> <div className="party-bar-background">
<div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor: partido.color || '#888' }}></div> <div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor: partido.color || '#888' }}></div>
</div> </div>
<div className="party-candidate-name">
{partido.nombreCandidato}
</div>
</div> </div>
</div> </div>
))} ))}

View File

@@ -133,4 +133,13 @@
.ticker-party .party-name { .ticker-party .party-name {
white-space: normal; white-space: normal;
} }
}
.party-candidate-name {
font-size: 0.8rem;
color: #555;
margin-top: 4px; /* Espacio entre la barra y el nombre */
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
} }

View File

@@ -49,6 +49,7 @@ export interface ResultadoTicker {
logoUrl: string | null; logoUrl: string | null;
votos: number; votos: number;
porcentaje: number; porcentaje: number;
nombreCandidato?: string | null;
} }
export interface EstadoRecuentoTicker { export interface EstadoRecuentoTicker {

View File

@@ -235,4 +235,77 @@ public class AdminController : ControllerBase
return Ok(municipios); return Ok(municipios);
} }
/// <summary>
/// Obtiene todos los overrides de candidatos configurados.
/// </summary>
[HttpGet("candidatos")]
public async Task<IActionResult> GetCandidatos()
{
var candidatos = await _dbContext.CandidatosOverrides
.AsNoTracking()
.ToListAsync();
return Ok(candidatos);
}
/// <summary>
/// Guarda (actualiza o crea) una lista de overrides de candidatos.
/// </summary>
[HttpPut("candidatos")]
public async Task<IActionResult> UpdateCandidatos([FromBody] List<CandidatoOverride> candidatos)
{
foreach (var candidatoDto in candidatos)
{
// Buscamos un override existente basado en la combinación única
var candidatoExistente = await _dbContext.CandidatosOverrides
.FirstOrDefaultAsync(c =>
c.AgrupacionPoliticaId == candidatoDto.AgrupacionPoliticaId &&
c.CategoriaId == candidatoDto.CategoriaId &&
c.AmbitoGeograficoId == candidatoDto.AmbitoGeograficoId);
if (candidatoExistente != null)
{
// Si existe y el nombre es diferente, lo actualizamos.
if (candidatoExistente.NombreCandidato != candidatoDto.NombreCandidato)
{
candidatoExistente.NombreCandidato = candidatoDto.NombreCandidato;
}
}
else
{
// Si no se encontró un registro exacto, lo añadimos a la base de datos,
// pero solo si el nombre del candidato no está vacío.
if (!string.IsNullOrWhiteSpace(candidatoDto.NombreCandidato))
{
_dbContext.CandidatosOverrides.Add(new CandidatoOverride
{
AgrupacionPoliticaId = candidatoDto.AgrupacionPoliticaId,
CategoriaId = candidatoDto.CategoriaId,
AmbitoGeograficoId = candidatoDto.AmbitoGeograficoId,
NombreCandidato = candidatoDto.NombreCandidato
});
}
}
}
// También necesitamos manejar los casos donde se borra un nombre (se envía un string vacío)
var overridesAEliminar = await _dbContext.CandidatosOverrides
.Where(c => candidatos.Any(dto =>
dto.AgrupacionPoliticaId == c.AgrupacionPoliticaId &&
dto.CategoriaId == c.CategoriaId &&
dto.AmbitoGeograficoId == c.AmbitoGeograficoId &&
string.IsNullOrWhiteSpace(dto.NombreCandidato)
))
.ToListAsync();
if (overridesAEliminar.Any())
{
_dbContext.CandidatosOverrides.RemoveRange(overridesAEliminar);
}
await _dbContext.SaveChangesAsync();
_logger.LogInformation("Se procesaron {Count} overrides de candidatos.", candidatos.Count);
return NoContent(); // Respuesta estándar para un PUT exitoso
}
} }

View File

@@ -22,7 +22,7 @@ public class ResultadosController : ControllerBase
_configuration = configuration; _configuration = configuration;
} }
[HttpGet("partido/{municipioId}")] // Renombramos el parámetro para mayor claridad [HttpGet("partido/{municipioId}")]
public async Task<IActionResult> GetResultadosPorPartido(string municipioId, [FromQuery] int categoriaId) public async Task<IActionResult> GetResultadosPorPartido(string municipioId, [FromQuery] int categoriaId)
{ {
var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking() var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking()
@@ -51,6 +51,11 @@ public class ResultadosController : ControllerBase
.Where(rv => rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId) .Where(rv => rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId)
.ToListAsync(); .ToListAsync();
var candidatosRelevantes = await _dbContext.CandidatosOverrides.AsNoTracking()
.Where(c => c.CategoriaId == categoriaId && agrupacionIds.Contains(c.AgrupacionPoliticaId) &&
(c.AmbitoGeograficoId == null || c.AmbitoGeograficoId == ambito.Id))
.ToListAsync();
long totalVotosPositivos = resultadosVotos.Sum(r => r.CantidadVotos); long totalVotosPositivos = resultadosVotos.Sum(r => r.CantidadVotos);
var respuestaDto = new MunicipioResultadosDto var respuestaDto = new MunicipioResultadosDto
@@ -64,6 +69,9 @@ public class ResultadosController : ControllerBase
var logoUrl = logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == ambito.Id)?.LogoUrl var logoUrl = logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == ambito.Id)?.LogoUrl
?? logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == null)?.LogoUrl; ?? logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == null)?.LogoUrl;
var nombreCandidato = candidatosRelevantes.FirstOrDefault(c => c.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && c.AmbitoGeograficoId == ambito.Id)?.NombreCandidato
?? candidatosRelevantes.FirstOrDefault(c => c.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && c.AmbitoGeograficoId == null)?.NombreCandidato;
return new AgrupacionResultadoDto return new AgrupacionResultadoDto
{ {
Id = rv.AgrupacionPolitica.Id, Id = rv.AgrupacionPolitica.Id,
@@ -72,6 +80,7 @@ public class ResultadosController : ControllerBase
Color = rv.AgrupacionPolitica.Color, Color = rv.AgrupacionPolitica.Color,
LogoUrl = logoUrl, LogoUrl = logoUrl,
Votos = rv.CantidadVotos, Votos = rv.CantidadVotos,
NombreCandidato = nombreCandidato,
Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0 Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0
}; };
}).OrderByDescending(r => r.Votos).ToList(), }).OrderByDescending(r => r.Votos).ToList(),
@@ -557,27 +566,18 @@ public class ResultadosController : ControllerBase
if (!municipiosDeLaSeccion.Any()) if (!municipiosDeLaSeccion.Any())
{ {
return Ok(new List<object>()); return Ok(new { UltimaActualizacion = DateTime.UtcNow, Resultados = new List<object>() });
} }
// --- INICIO DE LA CORRECCIÓN --- // --- INICIO DE LA CORRECCIÓN DE LOGOS ---
// 1. Obtenemos TODOS los logos para la categoría, sin convertirlos a diccionario todavía. // 1. Buscamos logos que sean para esta categoría Y que sean generales (ámbito null).
var todosLosLogos = await _dbContext.LogosAgrupacionesCategorias var logosGenerales = await _dbContext.LogosAgrupacionesCategorias
.AsNoTracking() .AsNoTracking()
.Where(l => l.CategoriaId == categoriaId) .Where(l => l.CategoriaId == categoriaId && l.AmbitoGeograficoId == null)
.ToListAsync(); .ToDictionaryAsync(l => l.AgrupacionPoliticaId);
// 2. Procesamos los logos en memoria para manejar duplicados, priorizando los que tienen ámbito. // --- FIN DE LA CORRECCIÓN DE LOGOS ---
// El resultado es un diccionario limpio sin claves duplicadas.
var logos = todosLosLogos
.GroupBy(l => l.AgrupacionPoliticaId) // Agrupamos por la clave que causa el problema
.ToDictionary(
g => g.Key, // La clave del diccionario es AgrupacionPoliticaId
g => g.OrderByDescending(l => l.AmbitoGeograficoId).First() // Para cada grupo, tomamos el logo más específico (el que tiene un AmbitoId) o el general si es el único.
);
// --- FIN DE LA CORRECCIÓN ---
var resultadosMunicipales = await _dbContext.ResultadosVotos var resultadosMunicipales = await _dbContext.ResultadosVotos
.AsNoTracking() .AsNoTracking()
@@ -601,7 +601,8 @@ public class ResultadosController : ControllerBase
r.Agrupacion.Nombre, r.Agrupacion.Nombre,
r.Agrupacion.NombreCorto, r.Agrupacion.NombreCorto,
r.Agrupacion.Color, r.Agrupacion.Color,
LogoUrl = logos.GetValueOrDefault(r.Agrupacion.Id)?.LogoUrl, // 2. Usamos el diccionario de logos generales para buscar la URL.
LogoUrl = logosGenerales.GetValueOrDefault(r.Agrupacion.Id)?.LogoUrl,
Votos = r.Votos, Votos = r.Votos,
Porcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0 Porcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0
}) })

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+0ce5e2e2c9bd6b64a7ea8629eeef35d204013a33")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+479c2c60f214472aeffd4404b482ffb940c3049e")]
[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":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","hGxHNQ\u002B1azEK\u002Bi1edq5HWPFILZgOw/WtDwYY8eIiOpE=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","DJ\u002BouDXt6xAa6457U4R5nMU/5dFpuSVR7W9oUVdiomg="],"CachedAssets":{},"CachedCopyCandidates":{}} {"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","/hrHm\u002B3v8DuHSlyFsxHPCFUvDW\u002BZsLR4kaMxNwUHl2M=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","v\u002BEjGaN1m59e9gwl3kXTpjNw\u002B3kwhJD2SLKx38/opjM="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","hGxHNQ\u002B1azEK\u002Bi1edq5HWPFILZgOw/WtDwYY8eIiOpE=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","DJ\u002BouDXt6xAa6457U4R5nMU/5dFpuSVR7W9oUVdiomg="],"CachedAssets":{},"CachedCopyCandidates":{}} {"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","/hrHm\u002B3v8DuHSlyFsxHPCFUvDW\u002BZsLR4kaMxNwUHl2M=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","v\u002BEjGaN1m59e9gwl3kXTpjNw\u002B3kwhJD2SLKx38/opjM="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -8,6 +8,7 @@ public class AgrupacionResultadoDto
public string? NombreCorto { get; set; } = null!; public string? NombreCorto { get; set; } = null!;
public string? Color { get; set; } = null!; public string? Color { get; set; } = null!;
public string? LogoUrl { get; set; } = null!; public string? LogoUrl { get; set; } = null!;
public string? NombreCandidato { get; set; } = null!;
public long Votos { get; set; } public long Votos { get; set; }
public decimal Porcentaje { get; set; } public decimal Porcentaje { get; set; }
} }

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+0ce5e2e2c9bd6b64a7ea8629eeef35d204013a33")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+479c2c60f214472aeffd4404b482ffb940c3049e")]
[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

@@ -20,6 +20,7 @@ public class EleccionesDbContext(DbContextOptions<EleccionesDbContext> options)
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; } public DbSet<LogoAgrupacionCategoria> LogosAgrupacionesCategorias { get; set; }
public DbSet<CandidatoOverride> CandidatosOverrides { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
@@ -81,5 +82,12 @@ public class EleccionesDbContext(DbContextOptions<EleccionesDbContext> options)
// La combinación de las tres columnas debe ser única. // La combinación de las tres columnas debe ser única.
entity.HasIndex(l => new { l.AgrupacionPoliticaId, l.CategoriaId, l.AmbitoGeograficoId }).IsUnique(); entity.HasIndex(l => new { l.AgrupacionPoliticaId, l.CategoriaId, l.AmbitoGeograficoId }).IsUnique();
}); });
modelBuilder.Entity<CandidatoOverride>(entity =>
{
// La combinación de agrupación, categoría y ámbito debe ser única
// para evitar tener dos nombres de candidato diferentes para la misma situación.
entity.HasIndex(c => new { c.AgrupacionPoliticaId, c.CategoriaId, c.AmbitoGeograficoId })
.IsUnique();
});
} }
} }

View File

@@ -0,0 +1,34 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Elecciones.Database.Entities;
public class CandidatoOverride
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string AgrupacionPoliticaId { get; set; } = null!;
[ForeignKey("AgrupacionPoliticaId")]
public AgrupacionPolitica AgrupacionPolitica { get; set; } = null!;
[Required]
public int CategoriaId { get; set; }
[ForeignKey("CategoriaId")]
public CategoriaElectoral CategoriaElectoral { get; set; } = null!;
// El AmbitoGeograficoId es opcional. Si es null, el override es general.
public int? AmbitoGeograficoId { get; set; }
[ForeignKey("AmbitoGeograficoId")]
public AmbitoGeografico? AmbitoGeografico { get; set; }
// El nombre del candidato que queremos mostrar.
[Required]
[MaxLength(255)]
public string NombreCandidato { get; set; } = null!;
}

View File

@@ -0,0 +1,617 @@
// <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("20250905134421_AddCandidatosOverridesTable")]
partial class AddCandidatosOverridesTable
{
/// <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.CandidatoOverride", 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<string>("NombreCandidato")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.HasKey("Id");
b.HasIndex("AmbitoGeograficoId");
b.HasIndex("CategoriaId");
b.HasIndex("AgrupacionPoliticaId", "CategoriaId", "AmbitoGeograficoId")
.IsUnique()
.HasFilter("[AmbitoGeograficoId] IS NOT NULL");
b.ToTable("CandidatosOverrides");
});
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?>("AmbitoGeograficoId")
.HasColumnType("int");
b.Property<int>("CategoriaId")
.HasColumnType("int");
b.Property<string>("LogoUrl")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("AgrupacionPoliticaId", "CategoriaId", "AmbitoGeograficoId")
.IsUnique()
.HasFilter("[AmbitoGeograficoId] IS NOT NULL");
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.CandidatoOverride", 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");
b.HasOne("Elecciones.Database.Entities.CategoriaElectoral", "CategoriaElectoral")
.WithMany()
.HasForeignKey("CategoriaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AgrupacionPolitica");
b.Navigation("AmbitoGeografico");
b.Navigation("CategoriaElectoral");
});
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,71 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elecciones.Database.Migrations
{
/// <inheritdoc />
public partial class AddCandidatosOverridesTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "CandidatosOverrides",
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),
AmbitoGeograficoId = table.Column<int>(type: "int", nullable: true),
NombreCandidato = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CandidatosOverrides", x => x.Id);
table.ForeignKey(
name: "FK_CandidatosOverrides_AgrupacionesPoliticas_AgrupacionPoliticaId",
column: x => x.AgrupacionPoliticaId,
principalTable: "AgrupacionesPoliticas",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_CandidatosOverrides_AmbitosGeograficos_AmbitoGeograficoId",
column: x => x.AmbitoGeograficoId,
principalTable: "AmbitosGeograficos",
principalColumn: "Id");
table.ForeignKey(
name: "FK_CandidatosOverrides_CategoriasElectorales_CategoriaId",
column: x => x.CategoriaId,
principalTable: "CategoriasElectorales",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_CandidatosOverrides_AgrupacionPoliticaId_CategoriaId_AmbitoGeograficoId",
table: "CandidatosOverrides",
columns: new[] { "AgrupacionPoliticaId", "CategoriaId", "AmbitoGeograficoId" },
unique: true,
filter: "[AmbitoGeograficoId] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_CandidatosOverrides_AmbitoGeograficoId",
table: "CandidatosOverrides",
column: "AmbitoGeograficoId");
migrationBuilder.CreateIndex(
name: "IX_CandidatosOverrides_CategoriaId",
table: "CandidatosOverrides",
column: "CategoriaId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CandidatosOverrides");
}
}
}

View File

@@ -144,6 +144,42 @@ namespace Elecciones.Database.Migrations
b.ToTable("Bancadas"); b.ToTable("Bancadas");
}); });
modelBuilder.Entity("Elecciones.Database.Entities.CandidatoOverride", 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<string>("NombreCandidato")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.HasKey("Id");
b.HasIndex("AmbitoGeograficoId");
b.HasIndex("CategoriaId");
b.HasIndex("AgrupacionPoliticaId", "CategoriaId", "AmbitoGeograficoId")
.IsUnique()
.HasFilter("[AmbitoGeograficoId] IS NOT NULL");
b.ToTable("CandidatosOverrides");
});
modelBuilder.Entity("Elecciones.Database.Entities.CategoriaElectoral", b => modelBuilder.Entity("Elecciones.Database.Entities.CategoriaElectoral", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -461,6 +497,31 @@ namespace Elecciones.Database.Migrations
b.Navigation("AgrupacionPolitica"); b.Navigation("AgrupacionPolitica");
}); });
modelBuilder.Entity("Elecciones.Database.Entities.CandidatoOverride", 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");
b.HasOne("Elecciones.Database.Entities.CategoriaElectoral", "CategoriaElectoral")
.WithMany()
.HasForeignKey("CategoriaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AgrupacionPolitica");
b.Navigation("AmbitoGeografico");
b.Navigation("CategoriaElectoral");
});
modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b => modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b =>
{ {
b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico") b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico")

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")]
[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+0ce5e2e2c9bd6b64a7ea8629eeef35d204013a33")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+479c2c60f214472aeffd4404b482ffb940c3049e")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

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+0ce5e2e2c9bd6b64a7ea8629eeef35d204013a33")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+479c2c60f214472aeffd4404b482ffb940c3049e")]
[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")]