Feat CarouselNacional y Fix Workers
This commit is contained in:
@@ -318,3 +318,14 @@ export const getHomeResumen = async (eleccionId: number, distritoId: string, cat
|
|||||||
const { data } = await apiClient.get(url);
|
const { data } = await apiClient.get(url);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getHomeResumenNacional = async (eleccionId: number, categoriaId: number): Promise<CategoriaResumenHome> => {
|
||||||
|
const queryParams = new URLSearchParams({
|
||||||
|
eleccionId: eleccionId.toString(),
|
||||||
|
categoriaId: categoriaId.toString(),
|
||||||
|
});
|
||||||
|
// Apunta al nuevo endpoint que creamos
|
||||||
|
const url = `/elecciones/home-resumen-nacional?${queryParams.toString()}`;
|
||||||
|
const { data } = await apiClient.get(url);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import { CongresoNacionalWidget } from './nacionales/CongresoNacionalWidget';
|
|||||||
import { PanelNacionalWidget } from './nacionales/PanelNacionalWidget';
|
import { PanelNacionalWidget } from './nacionales/PanelNacionalWidget';
|
||||||
import { HomeCarouselWidget } from './nacionales/HomeCarouselWidget';
|
import { HomeCarouselWidget } from './nacionales/HomeCarouselWidget';
|
||||||
import './DevAppStyle.css'
|
import './DevAppStyle.css'
|
||||||
|
import { HomeCarouselNacionalWidget } from './nacionales/HomeCarouselNacionalWidget';
|
||||||
|
|
||||||
// --- NUEVO COMPONENTE REUTILIZABLE PARA CONTENIDO COLAPSABLE ---
|
// --- NUEVO COMPONENTE REUTILIZABLE PARA CONTENIDO COLAPSABLE ---
|
||||||
const CollapsibleWidgetWrapper = ({ children }: { children: React.ReactNode }) => {
|
const CollapsibleWidgetWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
@@ -52,18 +53,30 @@ export const DevAppLegislativas = () => {
|
|||||||
<PanelNacionalWidget eleccionId={2} />
|
<PanelNacionalWidget eleccionId={2} />
|
||||||
|
|
||||||
<div style={sectionStyle}>
|
<div style={sectionStyle}>
|
||||||
<h2>Widget: Carrusel de Resultados (Home)</h2>
|
<h2>Widget: Carrusel de Resultados Provincias (Home)</h2>
|
||||||
<p style={descriptionStyle}>
|
<p style={descriptionStyle}>
|
||||||
Uso: <code style={codeStyle}><HomeCarouselWidget eleccionId={2} distritoId="02" categoriaId={2} titulo="Diputados - Provincia de Buenos Aires" /></code>
|
Uso: <code style={codeStyle}><HomeCarouselWidget eleccionId={2} distritoId="02" categoriaId={3} titulo="Diputados - Provincia de Buenos Aires" /></code>
|
||||||
</p>
|
</p>
|
||||||
<HomeCarouselWidget
|
<HomeCarouselWidget
|
||||||
eleccionId={2} // Nacional
|
eleccionId={2} // Nacional
|
||||||
distritoId="02" // Buenos Aires
|
distritoId="02" // Buenos Aires
|
||||||
categoriaId={2} // Diputados Nacionales
|
categoriaId={3} // Diputados Nacionales
|
||||||
titulo="Diputados - Provincia de Buenos Aires"
|
titulo="Diputados - Provincia de Buenos Aires"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style={sectionStyle}>
|
||||||
|
<h2>Widget: Carrusel de Resultados Nación (Home)</h2>
|
||||||
|
<p style={descriptionStyle}>
|
||||||
|
Uso: <code style={codeStyle}><HomeCarouselNacionalWidget eleccionId={2} categoriaId={3} titulo="Diputados - Argentina" /></code>
|
||||||
|
</p>
|
||||||
|
<HomeCarouselNacionalWidget
|
||||||
|
eleccionId={2}
|
||||||
|
categoriaId={3} // 3 para Diputados, 2 para Senadores
|
||||||
|
titulo="Diputados - Total País"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* --- SECCIÓN PARA EL WIDGET DE TARJETAS CON EJEMPLOS --- */}
|
{/* --- SECCIÓN PARA EL WIDGET DE TARJETAS CON EJEMPLOS --- */}
|
||||||
<div style={sectionStyle}>
|
<div style={sectionStyle}>
|
||||||
<h2>Widget: Resultados por Provincia (Tarjetas)</h2>
|
<h2>Widget: Resultados por Provincia (Tarjetas)</h2>
|
||||||
@@ -101,9 +114,9 @@ export const DevAppLegislativas = () => {
|
|||||||
<p style={descriptionStyle}>
|
<p style={descriptionStyle}>
|
||||||
Muestra todas las provincias que votan para una categoría específica.
|
Muestra todas las provincias que votan para una categoría específica.
|
||||||
<br />
|
<br />
|
||||||
Ejemplo Senadores (ID 1): <code style={codeStyle}><ResultadosNacionalesCardsWidget eleccionId={2} focoCategoriaId={1} /></code>
|
Ejemplo Senadores (ID 2): <code style={codeStyle}><ResultadosNacionalesCardsWidget eleccionId={2} focoCategoriaId={2} /></code>
|
||||||
</p>
|
</p>
|
||||||
<ResultadosNacionalesCardsWidget eleccionId={2} focoCategoriaId={1} />
|
<ResultadosNacionalesCardsWidget eleccionId={2} focoCategoriaId={2} />
|
||||||
|
|
||||||
<hr style={{ marginTop: '2rem' }} />
|
<hr style={{ marginTop: '2rem' }} />
|
||||||
|
|
||||||
@@ -135,9 +148,9 @@ export const DevAppLegislativas = () => {
|
|||||||
<br />
|
<br />
|
||||||
Ejemplo: Mostrar el TOP 1 (el ganador) para la categoría de SENADORES en la provincia de RÍO NEGRO (Distrito ID "16").
|
Ejemplo: Mostrar el TOP 1 (el ganador) para la categoría de SENADORES en la provincia de RÍO NEGRO (Distrito ID "16").
|
||||||
<br />
|
<br />
|
||||||
Uso: <code style={codeStyle}><ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="16" focoCategoriaId={1} cantidadResultados={1} /></code>
|
Uso: <code style={codeStyle}><ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="16" focoCategoriaId={2} cantidadResultados={1} /></code>
|
||||||
</p>
|
</p>
|
||||||
<ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="16" focoCategoriaId={1} cantidadResultados={1} />
|
<ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="16" focoCategoriaId={2} cantidadResultados={1} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
// src/features/legislativas/nacionales/HomeCarouselNacionalWidget.tsx
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getHomeResumenNacional } from '../../../apiService';
|
||||||
|
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||||
|
import { assetBaseUrl } from '../../../apiService';
|
||||||
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
|
import { Navigation, A11y } from 'swiper/modules';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import 'swiper/css';
|
||||||
|
// @ts-ignore
|
||||||
|
import 'swiper/css/navigation';
|
||||||
|
import styles from './HomeCarouselWidget.module.css';
|
||||||
|
|
||||||
|
const formatPercent = (num: number | null | undefined) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||||
|
const formatNumber = (num: number) => num.toLocaleString('es-AR');
|
||||||
|
|
||||||
|
const formatDateTime = (dateString: string | undefined | null) => {
|
||||||
|
if (!dateString) return '...';
|
||||||
|
try {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
return `${day}/${month}/${year}, ${hours}:${minutes} hs.`;
|
||||||
|
} catch (e) {
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Las props ya no incluyen distritoId
|
||||||
|
interface Props {
|
||||||
|
eleccionId: number;
|
||||||
|
categoriaId: number;
|
||||||
|
titulo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HomeCarouselNacionalWidget = ({ eleccionId, categoriaId, titulo }: Props) => {
|
||||||
|
const { data, isLoading, error } = useQuery({
|
||||||
|
// La queryKey ahora no necesita distritoId
|
||||||
|
queryKey: ['homeResumenNacional', eleccionId, categoriaId],
|
||||||
|
// Llama a la nueva función de la API
|
||||||
|
queryFn: () => getHomeResumenNacional(eleccionId, categoriaId),
|
||||||
|
});
|
||||||
|
if (isLoading) return <div>Cargando widget...</div>;
|
||||||
|
if (error || !data) return <div>No se pudieron cargar los datos.</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.homeCarouselWidget}>
|
||||||
|
<h2 className={styles.widgetTitle}>{titulo}</h2>
|
||||||
|
|
||||||
|
<div className={styles.topStatsBar}>
|
||||||
|
<div>
|
||||||
|
<span>Participación</span>
|
||||||
|
<strong>{formatPercent(data.estadoRecuento?.participacionPorcentaje)}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className={styles.longText}>Mesas escrutadas</span>
|
||||||
|
<span className={styles.shortText}>Escrutado</span>
|
||||||
|
<strong>{formatPercent(data.estadoRecuento?.mesasTotalizadasPorcentaje)}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className={styles.longText}>Votos en blanco</span>
|
||||||
|
<span className={styles.shortText}>En blanco</span>
|
||||||
|
<strong>{formatPercent(data.votosEnBlancoPorcentaje)}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className={styles.longText}>Votos totales</span>
|
||||||
|
<span className={styles.shortText}>Votos</span>
|
||||||
|
<strong>{formatNumber(data.votosTotales)}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.carouselContainer}>
|
||||||
|
<Swiper
|
||||||
|
modules={[Navigation, A11y]}
|
||||||
|
spaceBetween={16}
|
||||||
|
slidesPerView={1.3}
|
||||||
|
navigation={{
|
||||||
|
prevEl: `.${styles.navButtonPrev}`,
|
||||||
|
nextEl: `.${styles.navButtonNext}`,
|
||||||
|
}}
|
||||||
|
breakpoints={{
|
||||||
|
320: { slidesPerView: 1.25, spaceBetween: 10 },
|
||||||
|
430: { slidesPerView: 1.4, spaceBetween: 12 },
|
||||||
|
640: { slidesPerView: 2.5 },
|
||||||
|
1024: { slidesPerView: 3 },
|
||||||
|
1200: { slidesPerView: 3.5 }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.resultados.map(candidato => (
|
||||||
|
<SwiperSlide key={candidato.agrupacionId}>
|
||||||
|
<div className={styles.candidateCard} style={{ '--candidate-color': candidato.color || '#ccc' } as React.CSSProperties}>
|
||||||
|
|
||||||
|
<div className={styles.candidatePhotoWrapper}>
|
||||||
|
<ImageWithFallback
|
||||||
|
src={candidato.fotoUrl ?? undefined}
|
||||||
|
fallbackSrc={`${assetBaseUrl}/default-avatar.png`}
|
||||||
|
alt={candidato.nombreCandidato ?? ''}
|
||||||
|
className={styles.candidatePhoto}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.candidateDetails}>
|
||||||
|
<div className={styles.candidateInfo}>
|
||||||
|
{candidato.nombreCandidato ? (
|
||||||
|
<>
|
||||||
|
<span className={styles.candidateName}>
|
||||||
|
{candidato.nombreCandidato}
|
||||||
|
</span>
|
||||||
|
<span className={styles.partyName}>
|
||||||
|
{candidato.nombreCortoAgrupacion || candidato.nombreAgrupacion}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span className={styles.candidateName}>
|
||||||
|
{candidato.nombreCortoAgrupacion || candidato.nombreAgrupacion}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.candidateResults}>
|
||||||
|
<span className={styles.percentage}>{formatPercent(candidato.porcentaje)}</span>
|
||||||
|
<span className={styles.votes}>{formatNumber(candidato.votos)} votos</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
|
||||||
|
<div className={`${styles.navButton} ${styles.navButtonPrev}`}></div>
|
||||||
|
<div className={`${styles.navButton} ${styles.navButtonNext}`}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.widgetFooter}>
|
||||||
|
Última actualización: {formatDateTime(data.ultimaActualizacion)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1442,18 +1442,18 @@ List<CandidatoOverride> overrides, string agrupacionId, int categoriaId, int? am
|
|||||||
}
|
}
|
||||||
private LogoAgrupacionCategoria? FindBestLogoMatch(
|
private LogoAgrupacionCategoria? FindBestLogoMatch(
|
||||||
List<LogoAgrupacionCategoria> logos, string agrupacionId, int categoriaId, int? ambitoId, int eleccionId)
|
List<LogoAgrupacionCategoria> logos, string agrupacionId, int categoriaId, int? ambitoId, int eleccionId)
|
||||||
{
|
{
|
||||||
// Prioridad 1: Coincidencia exacta (Elección, Categoría, Ámbito)
|
// Prioridad 1: Coincidencia exacta (Elección, Categoría, Ámbito)
|
||||||
return logos.FirstOrDefault(l => l.EleccionId == eleccionId && l.AgrupacionPoliticaId == agrupacionId && l.CategoriaId == categoriaId && l.AmbitoGeograficoId == ambitoId)
|
return logos.FirstOrDefault(l => l.EleccionId == eleccionId && l.AgrupacionPoliticaId == agrupacionId && l.CategoriaId == categoriaId && l.AmbitoGeograficoId == ambitoId)
|
||||||
// Prioridad 2: Coincidencia por Elección y Categoría (Ámbito genérico)
|
// Prioridad 2: Coincidencia por Elección y Categoría (Ámbito genérico)
|
||||||
?? logos.FirstOrDefault(l => l.EleccionId == eleccionId && l.AgrupacionPoliticaId == agrupacionId && l.CategoriaId == categoriaId && l.AmbitoGeograficoId == null)
|
?? logos.FirstOrDefault(l => l.EleccionId == eleccionId && l.AgrupacionPoliticaId == agrupacionId && l.CategoriaId == categoriaId && l.AmbitoGeograficoId == null)
|
||||||
// Prioridad 3: Coincidencia de Fallback por Ámbito (Elección genérica)
|
// Prioridad 3: Coincidencia de Fallback por Ámbito (Elección genérica)
|
||||||
?? logos.FirstOrDefault(l => l.EleccionId == 0 && l.AgrupacionPoliticaId == agrupacionId && l.CategoriaId == categoriaId && l.AmbitoGeograficoId == ambitoId)
|
?? logos.FirstOrDefault(l => l.EleccionId == 0 && l.AgrupacionPoliticaId == agrupacionId && l.CategoriaId == categoriaId && l.AmbitoGeograficoId == ambitoId)
|
||||||
// Prioridad 4: Coincidencia de Fallback por Categoría (Elección y Ámbito genéricos)
|
// Prioridad 4: Coincidencia de Fallback por Categoría (Elección y Ámbito genéricos)
|
||||||
?? logos.FirstOrDefault(l => l.EleccionId == 0 && l.AgrupacionPoliticaId == agrupacionId && l.CategoriaId == categoriaId && l.AmbitoGeograficoId == null)
|
?? logos.FirstOrDefault(l => l.EleccionId == 0 && l.AgrupacionPoliticaId == agrupacionId && l.CategoriaId == categoriaId && l.AmbitoGeograficoId == null)
|
||||||
// Prioridad 5: LOGO GLOBAL. Coincidencia solo por Partido (Elección y Categoría genéricas)
|
// Prioridad 5: LOGO GLOBAL. Coincidencia solo por Partido (Elección y Categoría genéricas)
|
||||||
?? logos.FirstOrDefault(l => l.EleccionId == 0 && l.AgrupacionPoliticaId == agrupacionId && l.CategoriaId == 0 && l.AmbitoGeograficoId == null);
|
?? logos.FirstOrDefault(l => l.EleccionId == 0 && l.AgrupacionPoliticaId == agrupacionId && l.CategoriaId == 0 && l.AmbitoGeograficoId == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("resumen-por-provincia")]
|
[HttpGet("resumen-por-provincia")]
|
||||||
public async Task<IActionResult> GetResumenPorProvincia(
|
public async Task<IActionResult> GetResumenPorProvincia(
|
||||||
@@ -1617,4 +1617,80 @@ List<CandidatoOverride> overrides, string agrupacionId, int categoriaId, int? am
|
|||||||
|
|
||||||
return Ok(respuesta);
|
return Ok(respuesta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("~/api/elecciones/home-resumen-nacional")]
|
||||||
|
public async Task<IActionResult> GetHomeResumenNacional(
|
||||||
|
[FromQuery] int eleccionId,
|
||||||
|
[FromQuery] int categoriaId)
|
||||||
|
{
|
||||||
|
// Buscamos el ámbito que representa a toda la nación (NivelId 0 o 1 según tu diseño)
|
||||||
|
var ambitoNacional = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(a => a.NivelId == 0 || a.NivelId == 1);
|
||||||
|
|
||||||
|
if (ambitoNacional == null) return NotFound("No se encontró el ámbito geográfico a nivel nacional.");
|
||||||
|
|
||||||
|
// Agregamos los votos de TODOS los distritos para la categoría especificada
|
||||||
|
var votosAgregados = await _dbContext.ResultadosVotos.AsNoTracking()
|
||||||
|
.Where(r => r.EleccionId == eleccionId && r.CategoriaId == categoriaId)
|
||||||
|
.GroupBy(r => r.AgrupacionPolitica)
|
||||||
|
.Select(g => new { Agrupacion = g.Key, Votos = g.Sum(r => r.CantidadVotos) })
|
||||||
|
.OrderByDescending(x => x.Votos)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// El resto de la lógica es muy similar al GetHomeResumen original
|
||||||
|
var todosLosOverrides = await _dbContext.CandidatosOverrides.AsNoTracking().Where(c => c.EleccionId == eleccionId || c.EleccionId == 0).ToListAsync();
|
||||||
|
var todosLosLogos = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().Where(l => l.EleccionId == eleccionId || l.EleccionId == 0).ToListAsync();
|
||||||
|
|
||||||
|
// Para el estado del recuento, buscamos el registro general a nivel nacional
|
||||||
|
var estado = await _dbContext.EstadosRecuentosGenerales.AsNoTracking()
|
||||||
|
.Include(e => e.CategoriaElectoral)
|
||||||
|
.FirstOrDefaultAsync(e => e.EleccionId == eleccionId && e.CategoriaId == categoriaId && e.AmbitoGeograficoId == ambitoNacional.Id);
|
||||||
|
|
||||||
|
// Sumamos los votos no positivos de todos los ámbitos
|
||||||
|
var votosNoPositivosAgregados = await _dbContext.EstadosRecuentos.AsNoTracking()
|
||||||
|
.Where(e => e.EleccionId == eleccionId && e.CategoriaId == categoriaId)
|
||||||
|
.GroupBy(e => 1)
|
||||||
|
.Select(g => new { VotosEnBlanco = g.Sum(e => e.VotosEnBlanco), VotosNulos = g.Sum(e => e.VotosNulos), VotosRecurridos = g.Sum(e => e.VotosRecurridos) })
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
var totalVotosPositivos = (decimal)votosAgregados.Sum(r => r.Votos);
|
||||||
|
var votosEnBlanco = votosNoPositivosAgregados?.VotosEnBlanco ?? 0;
|
||||||
|
var votosTotales = totalVotosPositivos + votosEnBlanco + (votosNoPositivosAgregados?.VotosNulos ?? 0) + (votosNoPositivosAgregados?.VotosRecurridos ?? 0);
|
||||||
|
|
||||||
|
var respuesta = new CategoriaResumenHomeDto
|
||||||
|
{
|
||||||
|
CategoriaId = categoriaId,
|
||||||
|
CategoriaNombre = estado?.CategoriaElectoral.Nombre ?? (categoriaId == 3 ? "DIPUTADOS NACIONALES" : "SENADORES NACIONALES"),
|
||||||
|
UltimaActualizacion = estado?.FechaTotalizacion ?? DateTime.UtcNow,
|
||||||
|
EstadoRecuento = estado != null ? new EstadoRecuentoDto
|
||||||
|
{
|
||||||
|
ParticipacionPorcentaje = estado.ParticipacionPorcentaje,
|
||||||
|
MesasTotalizadasPorcentaje = estado.MesasTotalizadasPorcentaje,
|
||||||
|
CantidadVotantes = estado.CantidadVotantes
|
||||||
|
} : null,
|
||||||
|
VotosEnBlanco = votosEnBlanco,
|
||||||
|
VotosEnBlancoPorcentaje = votosTotales > 0 ? (votosEnBlanco / votosTotales) * 100 : 0,
|
||||||
|
VotosTotales = (long)votosTotales,
|
||||||
|
Resultados = votosAgregados.Select(r =>
|
||||||
|
{
|
||||||
|
// A nivel nacional, no hay un ámbito provincial específico, así que pasamos null.
|
||||||
|
var candidatoMatch = FindBestCandidatoMatch(todosLosOverrides, r.Agrupacion.Id, categoriaId, null, eleccionId);
|
||||||
|
var logoMatch = FindBestLogoMatch(todosLosLogos, r.Agrupacion.Id, categoriaId, null, eleccionId);
|
||||||
|
|
||||||
|
return new ResultadoCandidatoDto
|
||||||
|
{
|
||||||
|
AgrupacionId = r.Agrupacion.Id,
|
||||||
|
NombreAgrupacion = r.Agrupacion.Nombre,
|
||||||
|
NombreCortoAgrupacion = r.Agrupacion.NombreCorto,
|
||||||
|
NombreCandidato = candidatoMatch?.NombreCandidato,
|
||||||
|
Color = r.Agrupacion.Color,
|
||||||
|
Votos = r.Votos,
|
||||||
|
Porcentaje = totalVotosPositivos > 0 ? (r.Votos / totalVotosPositivos) * 100 : 0,
|
||||||
|
FotoUrl = logoMatch?.LogoUrl
|
||||||
|
};
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(respuesta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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+903c2b6a9416b0f694d30b046bd8764b01696e1e")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4bc257df43f5813ec432b89b47fa078c1cfa1fc8")]
|
||||||
[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")]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","UIntj4QoiyGr7bnJN8KK5PGrhQd89m\u002BLfh4T8VKPxAk=","J\u002Bfv/j3QyIW9bxolc46wDka8641F622/QgIllt0Re80=","Y/o0rakw9VYzEfz9M659qW77P9kvz\u002B2gTe1Lv3zgUDE=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","GY9iGdezAppjqYxnc92Ue2gI167CBYuqcWqyTUygx4E="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","UIntj4QoiyGr7bnJN8KK5PGrhQd89m\u002BLfh4T8VKPxAk=","J\u002Bfv/j3QyIW9bxolc46wDka8641F622/QgIllt0Re80=","Y/o0rakw9VYzEfz9M659qW77P9kvz\u002B2gTe1Lv3zgUDE=","/UJFXzVO5Y6TKX\u002BD5m/A1RI/tbK98BAoQFTS7wCUAJI=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","YO7Xv4ZJWkAJrh9hWzESGMBLWiXQVbzGiJis/zKzy9k="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","UIntj4QoiyGr7bnJN8KK5PGrhQd89m\u002BLfh4T8VKPxAk=","J\u002Bfv/j3QyIW9bxolc46wDka8641F622/QgIllt0Re80=","Y/o0rakw9VYzEfz9M659qW77P9kvz\u002B2gTe1Lv3zgUDE=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","GY9iGdezAppjqYxnc92Ue2gI167CBYuqcWqyTUygx4E="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","UIntj4QoiyGr7bnJN8KK5PGrhQd89m\u002BLfh4T8VKPxAk=","J\u002Bfv/j3QyIW9bxolc46wDka8641F622/QgIllt0Re80=","Y/o0rakw9VYzEfz9M659qW77P9kvz\u002B2gTe1Lv3zgUDE=","/UJFXzVO5Y6TKX\u002BD5m/A1RI/tbK98BAoQFTS7wCUAJI=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","YO7Xv4ZJWkAJrh9hWzESGMBLWiXQVbzGiJis/zKzy9k="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -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+903c2b6a9416b0f694d30b046bd8764b01696e1e")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4bc257df43f5813ec432b89b47fa078c1cfa1fc8")]
|
||||||
[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")]
|
||||||
|
|||||||
@@ -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+903c2b6a9416b0f694d30b046bd8764b01696e1e")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4bc257df43f5813ec432b89b47fa078c1cfa1fc8")]
|
||||||
[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")]
|
||||||
|
|||||||
@@ -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+903c2b6a9416b0f694d30b046bd8764b01696e1e")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4bc257df43f5813ec432b89b47fa078c1cfa1fc8")]
|
||||||
[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")]
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ public class CriticalDataWorker : BackgroundService
|
|||||||
if (settings.Prioridad == "Resultados" && settings.ResultadosActivado)
|
if (settings.Prioridad == "Resultados" && settings.ResultadosActivado)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Ejecutando tareas de Resultados en alta prioridad.");
|
_logger.LogInformation("Ejecutando tareas de Resultados en alta prioridad.");
|
||||||
|
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
||||||
await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
|
await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
|
||||||
await SondearResumenProvincialAsync(authToken, stoppingToken);
|
await SondearResumenProvincialAsync(authToken, stoppingToken);
|
||||||
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
|
||||||
}
|
}
|
||||||
else if (settings.Prioridad == "Telegramas" && settings.BajasActivado)
|
else if (settings.Prioridad == "Telegramas" && settings.BajasActivado)
|
||||||
{
|
{
|
||||||
@@ -588,92 +588,110 @@ public class CriticalDataWorker : BackgroundService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// PASO 1: Crear un "scope" para obtener una instancia fresca de DbContext.
|
|
||||||
// Esto es una práctica recomendada para servicios de larga duración para evitar problemas de concurrencia.
|
|
||||||
using var scope = _serviceProvider.CreateScope();
|
using var scope = _serviceProvider.CreateScope();
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
||||||
|
|
||||||
// PASO 2: Obtener el ámbito geográfico de la Provincia.
|
var provinciasASondear = await dbContext.AmbitosGeograficos
|
||||||
// Necesitamos este objeto para obtener su 'DistritoId' ("02"), que es requerido por la API.
|
.AsNoTracking()
|
||||||
var provincia = await dbContext.AmbitosGeograficos
|
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
||||||
.AsNoTracking() // Optimización: Solo necesitamos leer datos, no modificarlos.
|
.ToListAsync(stoppingToken);
|
||||||
.FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken);
|
|
||||||
|
|
||||||
// Comprobación de seguridad: Si la sincronización inicial falló y no tenemos el registro de la provincia,
|
// Busca NivelId 1 (País) o 0 como fallback.
|
||||||
// no podemos continuar. Registramos una advertencia y salimos del método.
|
var ambitoNacional = await dbContext.AmbitosGeograficos
|
||||||
if (provincia == null)
|
.AsNoTracking()
|
||||||
{
|
.FirstOrDefaultAsync(a => a.NivelId == 1 || a.NivelId == 0, stoppingToken);
|
||||||
_logger.LogWarning("No se encontró el ámbito 'Provincia' (NivelId 10) en la BD. Omitiendo sondeo de estado general.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PASO 3: Obtener todas las categorías electorales disponibles desde nuestra base de datos.
|
|
||||||
// Esto hace que el método sea dinámico y no dependa de IDs fijos en el código.
|
|
||||||
var categoriasParaSondear = await dbContext.CategoriasElectorales
|
var categoriasParaSondear = await dbContext.CategoriasElectorales
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.ToListAsync(stoppingToken);
|
.ToListAsync(stoppingToken);
|
||||||
|
|
||||||
if (!categoriasParaSondear.Any())
|
if (!provinciasASondear.Any() || !categoriasParaSondear.Any())
|
||||||
{
|
{
|
||||||
_logger.LogWarning("No hay categorías en la BD para sondear el estado general del recuento.");
|
_logger.LogWarning("No se encontraron Provincias o Categorías para sondear estado general.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Iniciando sondeo de Estado Recuento General para {count} categorías...", categoriasParaSondear.Count);
|
_logger.LogInformation("Iniciando sondeo de Estado Recuento General para {provCount} provincias, el total nacional y {catCount} categorías...", provinciasASondear.Count, categoriasParaSondear.Count);
|
||||||
|
|
||||||
// PASO 4: Iterar sobre cada categoría para obtener su estado de recuento individual.
|
// Sondeo a nivel provincial
|
||||||
foreach (var categoria in categoriasParaSondear)
|
foreach (var provincia in provinciasASondear)
|
||||||
{
|
{
|
||||||
// Salimos limpiamente del bucle si la aplicación se está deteniendo.
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
|
foreach (var categoria in categoriasParaSondear)
|
||||||
// Llamamos a la API con el distrito y la CATEGORÍA ACTUAL del bucle.
|
|
||||||
var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id);
|
|
||||||
|
|
||||||
// Solo procedemos si la API devolvió datos válidos.
|
|
||||||
if (estadoDto != null)
|
|
||||||
{
|
{
|
||||||
// Lógica "Upsert" (Update or Insert):
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
// Buscamos un registro existente usando la CLAVE PRIMARIA COMPUESTA.
|
|
||||||
var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(
|
|
||||||
new object[] { provincia.Id, categoria.Id },
|
|
||||||
cancellationToken: stoppingToken
|
|
||||||
);
|
|
||||||
|
|
||||||
// Si no se encuentra (FindAsync devuelve null), es un registro nuevo.
|
var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id);
|
||||||
if (registroDb == null)
|
if (estadoDto != null)
|
||||||
{
|
{
|
||||||
// Creamos una nueva instancia de la entidad.
|
var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { provincia.Id, categoria.Id }, stoppingToken);
|
||||||
registroDb = new EstadoRecuentoGeneral
|
if (registroDb == null)
|
||||||
{
|
{
|
||||||
EleccionId = EleccionId,
|
registroDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id };
|
||||||
AmbitoGeograficoId = provincia.Id,
|
dbContext.EstadosRecuentosGenerales.Add(registroDb);
|
||||||
CategoriaId = categoria.Id // Asignamos ambas partes de la clave primaria.
|
}
|
||||||
};
|
registroDb.FechaTotalizacion = DateTime.UtcNow;
|
||||||
// Y la añadimos al ChangeTracker de EF para que la inserte en la BD.
|
registroDb.MesasEsperadas = estadoDto.MesasEsperadas;
|
||||||
dbContext.EstadosRecuentosGenerales.Add(registroDb);
|
registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas;
|
||||||
|
registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje;
|
||||||
|
registroDb.CantidadElectores = estadoDto.CantidadElectores;
|
||||||
|
registroDb.CantidadVotantes = estadoDto.CantidadVotantes;
|
||||||
|
registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mapeamos los datos del DTO de la API a nuestra entidad de base de datos.
|
|
||||||
// Esto se hace tanto para registros nuevos como para los existentes que se van a actualizar.
|
|
||||||
registroDb.MesasEsperadas = estadoDto.MesasEsperadas;
|
|
||||||
registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas;
|
|
||||||
registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje;
|
|
||||||
registroDb.CantidadElectores = estadoDto.CantidadElectores;
|
|
||||||
registroDb.CantidadVotantes = estadoDto.CantidadVotantes;
|
|
||||||
registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PASO 5: Guardar todos los cambios en la base de datos.
|
// Bloque para el sondeo a nivel nacional
|
||||||
// Al llamar a SaveChangesAsync UNA SOLA VEZ fuera del bucle, EF Core agrupa
|
if (ambitoNacional != null && !stoppingToken.IsCancellationRequested)
|
||||||
// todas las inserciones y actualizaciones en una única transacción eficiente.
|
{
|
||||||
await dbContext.SaveChangesAsync(stoppingToken);
|
_logger.LogInformation("Sondeando totales a nivel Nacional (Ambito ID: {ambitoId})...", ambitoNacional.Id);
|
||||||
_logger.LogInformation("Sondeo de Estado Recuento General completado para todas las categorías.");
|
foreach (var categoria in categoriasParaSondear)
|
||||||
|
{
|
||||||
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
|
|
||||||
|
var estadoNacionalDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, "", categoria.Id);
|
||||||
|
|
||||||
|
if (estadoNacionalDto != null)
|
||||||
|
{
|
||||||
|
var registroNacionalDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { ambitoNacional.Id, categoria.Id }, stoppingToken);
|
||||||
|
if (registroNacionalDb == null)
|
||||||
|
{
|
||||||
|
registroNacionalDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = ambitoNacional.Id, CategoriaId = categoria.Id };
|
||||||
|
dbContext.EstadosRecuentosGenerales.Add(registroNacionalDb);
|
||||||
|
}
|
||||||
|
registroNacionalDb.FechaTotalizacion = DateTime.UtcNow;
|
||||||
|
registroNacionalDb.MesasEsperadas = estadoNacionalDto.MesasEsperadas;
|
||||||
|
registroNacionalDb.MesasTotalizadas = estadoNacionalDto.MesasTotalizadas;
|
||||||
|
registroNacionalDb.MesasTotalizadasPorcentaje = estadoNacionalDto.MesasTotalizadasPorcentaje;
|
||||||
|
registroNacionalDb.CantidadElectores = estadoNacionalDto.CantidadElectores;
|
||||||
|
registroNacionalDb.CantidadVotantes = estadoNacionalDto.CantidadVotantes;
|
||||||
|
registroNacionalDb.ParticipacionPorcentaje = estadoNacionalDto.ParticipacionPorcentaje;
|
||||||
|
_logger.LogInformation("Datos nacionales para categoría '{catNombre}' actualizados.", categoria.Nombre);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ambitoNacional == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("No se encontró el ámbito geográfico para el Nivel Nacional (NivelId 1 o 0). No se pueden capturar los totales del país.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardar todos los cambios
|
||||||
|
if (dbContext.ChangeTracker.HasChanges())
|
||||||
|
{
|
||||||
|
await dbContext.SaveChangesAsync(stoppingToken);
|
||||||
|
_logger.LogInformation("Sondeo de Estado Recuento General completado. Se han guardado los cambios en la base de datos.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Sondeo de Estado Recuento General completado. No se detectaron cambios.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Sondeo de Estado Recuento General cancelado.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Capturamos cualquier excepción inesperada para que no detenga el worker y la registramos.
|
|
||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General.");
|
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ public class LowPriorityDataWorker : BackgroundService
|
|||||||
if (settings.Prioridad == "Telegramas" && settings.ResultadosActivado)
|
if (settings.Prioridad == "Telegramas" && settings.ResultadosActivado)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Ejecutando tareas de Resultados en baja prioridad.");
|
_logger.LogInformation("Ejecutando tareas de Resultados en baja prioridad.");
|
||||||
|
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
||||||
await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
|
await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
|
||||||
await SondearResumenProvincialAsync(authToken, stoppingToken);
|
await SondearResumenProvincialAsync(authToken, stoppingToken);
|
||||||
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
|
||||||
}
|
}
|
||||||
else if (settings.Prioridad == "Resultados" && settings.BajasActivado)
|
else if (settings.Prioridad == "Resultados" && settings.BajasActivado)
|
||||||
{
|
{
|
||||||
@@ -320,92 +320,110 @@ public class LowPriorityDataWorker : BackgroundService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// PASO 1: Crear un "scope" para obtener una instancia fresca de DbContext.
|
|
||||||
// Esto es una práctica recomendada para servicios de larga duración para evitar problemas de concurrencia.
|
|
||||||
using var scope = _serviceProvider.CreateScope();
|
using var scope = _serviceProvider.CreateScope();
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
||||||
|
|
||||||
// PASO 2: Obtener el ámbito geográfico de la Provincia.
|
var provinciasASondear = await dbContext.AmbitosGeograficos
|
||||||
// Necesitamos este objeto para obtener su 'DistritoId' ("02"), que es requerido por la API.
|
.AsNoTracking()
|
||||||
var provincia = await dbContext.AmbitosGeograficos
|
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
||||||
.AsNoTracking() // Optimización: Solo necesitamos leer datos, no modificarlos.
|
.ToListAsync(stoppingToken);
|
||||||
.FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken);
|
|
||||||
|
|
||||||
// Comprobación de seguridad: Si la sincronización inicial falló y no tenemos el registro de la provincia,
|
// Busca NivelId 1 (País) o 0 como fallback.
|
||||||
// no podemos continuar. Registramos una advertencia y salimos del método.
|
var ambitoNacional = await dbContext.AmbitosGeograficos
|
||||||
if (provincia == null)
|
.AsNoTracking()
|
||||||
{
|
.FirstOrDefaultAsync(a => a.NivelId == 1 || a.NivelId == 0, stoppingToken);
|
||||||
_logger.LogWarning("No se encontró el ámbito 'Provincia' (NivelId 10) en la BD. Omitiendo sondeo de estado general.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PASO 3: Obtener todas las categorías electorales disponibles desde nuestra base de datos.
|
|
||||||
// Esto hace que el método sea dinámico y no dependa de IDs fijos en el código.
|
|
||||||
var categoriasParaSondear = await dbContext.CategoriasElectorales
|
var categoriasParaSondear = await dbContext.CategoriasElectorales
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.ToListAsync(stoppingToken);
|
.ToListAsync(stoppingToken);
|
||||||
|
|
||||||
if (!categoriasParaSondear.Any())
|
if (!provinciasASondear.Any() || !categoriasParaSondear.Any())
|
||||||
{
|
{
|
||||||
_logger.LogWarning("No hay categorías en la BD para sondear el estado general del recuento.");
|
_logger.LogWarning("No se encontraron Provincias o Categorías para sondear estado general.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Iniciando sondeo de Estado Recuento General para {count} categorías...", categoriasParaSondear.Count);
|
_logger.LogInformation("Iniciando sondeo de Estado Recuento General para {provCount} provincias, el total nacional y {catCount} categorías...", provinciasASondear.Count, categoriasParaSondear.Count);
|
||||||
|
|
||||||
// PASO 4: Iterar sobre cada categoría para obtener su estado de recuento individual.
|
// Sondeo a nivel provincial
|
||||||
foreach (var categoria in categoriasParaSondear)
|
foreach (var provincia in provinciasASondear)
|
||||||
{
|
{
|
||||||
// Salimos limpiamente del bucle si la aplicación se está deteniendo.
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
|
foreach (var categoria in categoriasParaSondear)
|
||||||
// Llamamos a la API con el distrito y la CATEGORÍA ACTUAL del bucle.
|
|
||||||
var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id);
|
|
||||||
|
|
||||||
// Solo procedemos si la API devolvió datos válidos.
|
|
||||||
if (estadoDto != null)
|
|
||||||
{
|
{
|
||||||
// Lógica "Upsert" (Update or Insert):
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
// Buscamos un registro existente usando la CLAVE PRIMARIA COMPUESTA.
|
|
||||||
var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(
|
|
||||||
new object[] { provincia.Id, categoria.Id },
|
|
||||||
cancellationToken: stoppingToken
|
|
||||||
);
|
|
||||||
|
|
||||||
// Si no se encuentra (FindAsync devuelve null), es un registro nuevo.
|
var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id);
|
||||||
if (registroDb == null)
|
if (estadoDto != null)
|
||||||
{
|
{
|
||||||
// Creamos una nueva instancia de la entidad.
|
var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { provincia.Id, categoria.Id }, stoppingToken);
|
||||||
registroDb = new EstadoRecuentoGeneral
|
if (registroDb == null)
|
||||||
{
|
{
|
||||||
EleccionId = EleccionId,
|
registroDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id };
|
||||||
AmbitoGeograficoId = provincia.Id,
|
dbContext.EstadosRecuentosGenerales.Add(registroDb);
|
||||||
CategoriaId = categoria.Id // Asignamos ambas partes de la clave primaria.
|
}
|
||||||
};
|
registroDb.FechaTotalizacion = DateTime.UtcNow;
|
||||||
// Y la añadimos al ChangeTracker de EF para que la inserte en la BD.
|
registroDb.MesasEsperadas = estadoDto.MesasEsperadas;
|
||||||
dbContext.EstadosRecuentosGenerales.Add(registroDb);
|
registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas;
|
||||||
|
registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje;
|
||||||
|
registroDb.CantidadElectores = estadoDto.CantidadElectores;
|
||||||
|
registroDb.CantidadVotantes = estadoDto.CantidadVotantes;
|
||||||
|
registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mapeamos los datos del DTO de la API a nuestra entidad de base de datos.
|
|
||||||
// Esto se hace tanto para registros nuevos como para los existentes que se van a actualizar.
|
|
||||||
registroDb.MesasEsperadas = estadoDto.MesasEsperadas;
|
|
||||||
registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas;
|
|
||||||
registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje;
|
|
||||||
registroDb.CantidadElectores = estadoDto.CantidadElectores;
|
|
||||||
registroDb.CantidadVotantes = estadoDto.CantidadVotantes;
|
|
||||||
registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PASO 5: Guardar todos los cambios en la base de datos.
|
// Bloque para el sondeo a nivel nacional
|
||||||
// Al llamar a SaveChangesAsync UNA SOLA VEZ fuera del bucle, EF Core agrupa
|
if (ambitoNacional != null && !stoppingToken.IsCancellationRequested)
|
||||||
// todas las inserciones y actualizaciones en una única transacción eficiente.
|
{
|
||||||
await dbContext.SaveChangesAsync(stoppingToken);
|
_logger.LogInformation("Sondeando totales a nivel Nacional (Ambito ID: {ambitoId})...", ambitoNacional.Id);
|
||||||
_logger.LogInformation("Sondeo de Estado Recuento General completado para todas las categorías.");
|
foreach (var categoria in categoriasParaSondear)
|
||||||
|
{
|
||||||
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
|
|
||||||
|
var estadoNacionalDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, "", categoria.Id);
|
||||||
|
|
||||||
|
if (estadoNacionalDto != null)
|
||||||
|
{
|
||||||
|
var registroNacionalDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { ambitoNacional.Id, categoria.Id }, stoppingToken);
|
||||||
|
if (registroNacionalDb == null)
|
||||||
|
{
|
||||||
|
registroNacionalDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = ambitoNacional.Id, CategoriaId = categoria.Id };
|
||||||
|
dbContext.EstadosRecuentosGenerales.Add(registroNacionalDb);
|
||||||
|
}
|
||||||
|
registroNacionalDb.FechaTotalizacion = DateTime.UtcNow;
|
||||||
|
registroNacionalDb.MesasEsperadas = estadoNacionalDto.MesasEsperadas;
|
||||||
|
registroNacionalDb.MesasTotalizadas = estadoNacionalDto.MesasTotalizadas;
|
||||||
|
registroNacionalDb.MesasTotalizadasPorcentaje = estadoNacionalDto.MesasTotalizadasPorcentaje;
|
||||||
|
registroNacionalDb.CantidadElectores = estadoNacionalDto.CantidadElectores;
|
||||||
|
registroNacionalDb.CantidadVotantes = estadoNacionalDto.CantidadVotantes;
|
||||||
|
registroNacionalDb.ParticipacionPorcentaje = estadoNacionalDto.ParticipacionPorcentaje;
|
||||||
|
_logger.LogInformation("Datos nacionales para categoría '{catNombre}' actualizados.", categoria.Nombre);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ambitoNacional == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("No se encontró el ámbito geográfico para el Nivel Nacional (NivelId 1 o 0). No se pueden capturar los totales del país.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardar todos los cambios
|
||||||
|
if (dbContext.ChangeTracker.HasChanges())
|
||||||
|
{
|
||||||
|
await dbContext.SaveChangesAsync(stoppingToken);
|
||||||
|
_logger.LogInformation("Sondeo de Estado Recuento General completado. Se han guardado los cambios en la base de datos.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Sondeo de Estado Recuento General completado. No se detectaron cambios.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Sondeo de Estado Recuento General cancelado.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Capturamos cualquier excepción inesperada para que no detenga el worker y la registramos.
|
|
||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General.");
|
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user