Feat Widgets 1930

This commit is contained in:
2025-09-02 19:38:04 -03:00
parent 9393d2bc05
commit 6732a0e826
15 changed files with 230 additions and 146 deletions

View File

@@ -149,7 +149,18 @@ export const getResultadosPorMunicipioYCategoria = async (municipioId: string, c
return response.data.resultados;
};
export const getMunicipios = async (): Promise<MunicipioSimple[]> => {
const response = await apiClient.get('/catalogos/municipios');
return response.data;
export const getResultadosPorMunicipio = async (municipioId: string, categoriaId: number): Promise<ResultadoTicker[]> => {
const response = await apiClient.get(`/resultados/partido/${municipioId}?categoriaId=${categoriaId}`);
// La respuesta es un objeto, nosotros extraemos el array de resultados
return response.data.resultados;
};
export const getMunicipios = async (categoriaId?: number): Promise<MunicipioSimple[]> => {
let url = '/catalogos/municipios';
if (categoriaId) {
url += `?categoriaId=${categoriaId}`;
}
const response = await apiClient.get(url);
// La API ahora devuelve { seccionId, nombre }, lo mapeamos a { id, nombre }
return response.data.map((m: any) => ({ id: m.seccionId, nombre: m.nombre }));
};

View File

@@ -1,80 +1,75 @@
// src/components/BancasWidget.tsx
import { useState, useEffect } from 'react';
// Se cambia la importación: de ResponsiveWaffle a ResponsiveBar
import { useQuery } from '@tanstack/react-query';
import { ResponsiveBar } from '@nivo/bar';
import { getBancasPorSeccion, getSeccionesElectorales } from '../apiService';
import type { ProyeccionBancas, MunicipioSimple } from '../types/types';
import './BancasWidget.css';
// La paleta de colores se mantiene para consistencia visual
const NIVO_COLORS = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];
export const BancasWidget = () => {
const [secciones, setSecciones] = useState<MunicipioSimple[]>([]);
const [seccionActual, setSeccionActual] = useState<string>('');
const [data, setData] = useState<ProyeccionBancas | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// useEffect para cargar la lista de secciones una sola vez
// useEffect para cargar la lista de secciones una sola vez (esto está bien)
useEffect(() => {
const fetchSecciones = async () => {
try {
const seccionesData = await getSeccionesElectorales();
if (seccionesData && seccionesData.length > 0) {
// --- INICIO DE LA LÓGICA DE ORDENAMIENTO ---
const orden = new Map([
['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3],
['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7]
]);
const getOrden = (nombre: string) => {
const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/);
return match ? orden.get(match[0]) ?? 99 : 99;
};
seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre));
// --- FIN DE LA LÓGICA DE ORDENAMIENTO ---
setSecciones(seccionesData);
setSeccionActual(seccionesData[0].id);
} else {
setError("No se encontraron secciones electorales.");
}
} catch (err) {
console.error("Error cargando secciones electorales:", err);
setError("No se pudo cargar la lista de secciones.");
// Opcional: manejar el error en la UI si la carga de secciones falla
}
};
fetchSecciones();
}, []);
// useEffect para cargar los datos de bancas cuando cambia la sección
useEffect(() => {
if (!seccionActual) return;
// --- CÓDIGO REFACTORIZADO ---
// Eliminamos los useState para data, loading y error
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const result = await getBancasPorSeccion(seccionActual);
setData(result);
} catch (err) {
console.error(`Error cargando datos de bancas para sección ${seccionActual}:`, err);
setData(null);
setError("No hay datos de bancas disponibles para esta sección.");
} finally {
setLoading(false);
}
};
fetchData();
}, [seccionActual]);
// useQuery ahora es la única fuente de verdad para los datos de bancas
const {
data,
isLoading,
error
} = useQuery<ProyeccionBancas, Error>({
queryKey: ['bancasPorSeccion', seccionActual],
queryFn: () => getBancasPorSeccion(seccionActual),
enabled: !!seccionActual,
retry: (failureCount, error: any) => {
if (error.response?.status === 404) return false;
return failureCount < 3;
},
});
// Se preparan los datos para el gráfico de barras.
// Se invierte el array para que el partido con más bancas aparezca arriba.
const barData = data ? [...data.proyeccion].reverse() : [];
const getErrorMessage = () => {
if (error) {
if ((error as any).response?.status === 404) {
return "La proyección de bancas para esta sección aún no está disponible.";
}
return "No se pudo conectar para obtener los datos de bancas.";
}
return null;
};
const errorMessage = getErrorMessage();
return (
<div className="bancas-widget-container">
<div className="bancas-header">
@@ -84,8 +79,7 @@ export const BancasWidget = () => {
</select>
</div>
<div className="waffle-chart-container">
{loading ? <p>Cargando...</p> : error ? <p>{error}</p> :
// --- SE REEMPLAZA EL GRÁFICO WAFFLE POR EL GRÁFICO DE BARRAS ---
{isLoading ? <p>Cargando...</p> : errorMessage ? <p>{errorMessage}</p> :
<ResponsiveBar
data={barData}
keys={['bancas']}
@@ -106,7 +100,6 @@ export const BancasWidget = () => {
legend: 'Cantidad de Bancas',
legendPosition: 'middle',
legendOffset: 20,
// Asegura que los ticks del eje sean números enteros
format: (value) => Math.floor(value) === value ? value : ''
}}
axisLeft={{
@@ -118,7 +111,6 @@ export const BancasWidget = () => {
labelSkipHeight={12}
labelTextColor={{ from: 'color', modifiers: [['darker', 3]] }}
animate={true}
// Se elimina la leyenda, ya que las etiquetas en el eje son suficientes
legends={[]}
/>}
</div>

View File

@@ -1,68 +1,62 @@
// src/components/ConcejalesWidget.tsx
import { useState, useMemo, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import Select from 'react-select'; // <-- 1. Importar react-select
import { getMunicipios, getResultadosPorMunicipioYCategoria, getConfiguracionPublica } from '../apiService';
import Select from 'react-select';
import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica } from '../apiService';
import type { MunicipioSimple, ResultadoTicker } from '../types/types';
import { ImageWithFallback } from './ImageWithFallback';
import './TickerWidget.css';
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
// Estilos personalizados para que el selector se vea bien
const customSelectStyles = {
control: (base: any) => ({ ...base, minWidth: '220px', border: '1px solid #ced4da' }),
menu: (base: any) => ({ ...base, zIndex: 10 }), // Para que el menú se superponga
menu: (base: any) => ({ ...base, zIndex: 10 }),
};
const CATEGORIA_ID = 7; // ID para Concejales
export const ConcejalesWidget = () => {
// 2. Cambiamos el estado para que se adapte a react-select
const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null);
// 1. Query para la configuración (se había eliminado por error)
const { data: configData } = useQuery({
queryKey: ['configuracionPublica'],
queryFn: getConfiguracionPublica,
staleTime: 0,
});
// Usamos la clave de configuración correcta
const cantidadAMostrar = parseInt(configData?.ConcejalesResultadosCantidad || '5', 10);
// 3. Query para obtener la lista de MUNICIPIOS
// 2. Query para la lista de municipios
const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({
// Usamos una clave genérica porque siempre pedimos la lista completa.
queryKey: ['municipios'],
queryFn: getMunicipios,
// Llamamos a la función sin argumentos para obtener todos los municipios.
queryFn: () => getMunicipios(),
});
// Este useEffect se encarga de establecer el valor por defecto
useEffect(() => {
// Se ejecuta solo si tenemos la lista de municipios y aún no hemos seleccionado nada
if (municipios.length > 0 && !selectedMunicipio) {
// Buscamos "LA PLATA" en la lista (insensible a mayúsculas/minúsculas)
const laPlata = municipios.find(m => m.nombre.toUpperCase() === 'LA PLATA');
const cantidadAMostrar = parseInt(configData?.ConcejalesResultadosCantidad || '5', 10);
// Si lo encontramos, lo establecemos como el municipio seleccionado
useEffect(() => {
if (municipios.length > 0 && !selectedMunicipio) {
const laPlata = municipios.find(m => m.nombre.toUpperCase() === 'LA PLATA');
if (laPlata) {
setSelectedMunicipio({ value: laPlata.id, label: laPlata.nombre });
}
}
}, [municipios, selectedMunicipio]); // Se ejecuta cuando 'municipios' o 'selectedMunicipio' cambian
}, [municipios, selectedMunicipio]);
// 4. Transformamos los datos para react-select
const municipioOptions = useMemo(() =>
municipios
.map(m => ({ value: m.id, label: m.nombre }))
.sort((a, b) => a.label.localeCompare(b.label)), // Orden alfabético
.sort((a, b) => a.label.localeCompare(b.label)),
[municipios]);
// 5. Query para obtener los resultados del MUNICIPIO seleccionado
const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({
queryKey: ['resultadosConcejalesPorMunicipio', selectedMunicipio?.value],
queryFn: () => getResultadosPorMunicipioYCategoria(selectedMunicipio!.value, 7),
queryKey: ['resultadosPorMunicipio', selectedMunicipio?.value, CATEGORIA_ID],
queryFn: () => getResultadosPorMunicipio(selectedMunicipio!.value, CATEGORIA_ID),
enabled: !!selectedMunicipio,
});
// 6. Lógica para "Otros" (sin cambios funcionales)
// Lógica para "Otros"
let displayResults: ResultadoTicker[] = resultados || [];
if (resultados && resultados.length > cantidadAMostrar) {
const topParties = resultados.slice(0, cantidadAMostrar - 1);
@@ -92,6 +86,7 @@ export const ConcejalesWidget = () => {
onChange={(option) => setSelectedMunicipio(option)}
isLoading={isLoadingMunicipios}
placeholder="Buscar y seleccionar un municipio..."
isClearable
styles={customSelectStyles}
/>
</div>

View File

@@ -1,19 +1,23 @@
// src/components/DiputadosWidget.tsx
import { useMemo } from 'react';
import { useState, useMemo, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { getResumenProvincial, getConfiguracionPublica } from '../apiService';
import type { CategoriaResumen, ResultadoTicker } from '../types/types';
import Select from 'react-select';
import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica } from '../apiService';
import type { MunicipioSimple, ResultadoTicker } from '../types/types';
import { ImageWithFallback } from './ImageWithFallback';
import './TickerWidget.css';
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
const customSelectStyles = {
control: (base: any) => ({ ...base, minWidth: '220px', border: '1px solid #ced4da' }),
menu: (base: any) => ({ ...base, zIndex: 10 }),
};
// Constante para la categoría de este widget
const CATEGORIA_ID = 6; // Diputados
export const DiputadosWidget = () => {
const { data: categorias, isLoading, error } = useQuery<CategoriaResumen[]>({
queryKey: ['resumenProvincial'],
queryFn: getResumenProvincial,
refetchInterval: 30000,
});
const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null);
const { data: configData } = useQuery({
queryKey: ['configuracionPublica'],
@@ -21,24 +25,47 @@ export const DiputadosWidget = () => {
staleTime: 0,
});
// Usamos la clave de configuración del Ticker, ya que es para Senadores/Diputados
const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10);
// Usamos useMemo para encontrar los datos específicos de Diputados (ID 6)
const diputadosData = useMemo(() => {
return categorias?.find(c => c.categoriaId === 6);
}, [categorias]);
const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({
queryKey: ['municipios', CATEGORIA_ID], // Key única para la lista de municipios de diputados
queryFn: () => getMunicipios(CATEGORIA_ID), // Pide solo los municipios que votan diputados
});
if (isLoading) return <div className="ticker-card loading">Cargando...</div>;
if (error || !diputadosData) return <div className="ticker-card error">Datos de Diputados no disponibles.</div>;
// useEffect para establecer "LA PLATA" por defecto
useEffect(() => {
if (municipios.length > 0 && !selectedMunicipio) {
const laPlata = municipios.find(m => m.nombre.toUpperCase() === 'LA PLATA');
if (laPlata) {
setSelectedMunicipio({ value: laPlata.id, label: laPlata.nombre });
} else if (municipios.length > 0) {
// Si no está La Plata, seleccionamos el primero de la lista
setSelectedMunicipio({ value: municipios[0].id, label: municipios[0].nombre });
}
}
}, [municipios, selectedMunicipio]);
// Lógica para "Otros" aplicada solo a los resultados de Diputados
let displayResults: ResultadoTicker[] = diputadosData.resultados;
if (diputadosData.resultados.length > cantidadAMostrar) {
const topParties = diputadosData.resultados.slice(0, cantidadAMostrar - 1);
const otherParties = diputadosData.resultados.slice(cantidadAMostrar - 1);
const otrosPorcentaje = otherParties.reduce((sum, party) => sum + party.porcentaje, 0);
const municipioOptions = useMemo(() =>
municipios
.map(m => ({ value: m.id, label: m.nombre }))
.sort((a, b) => a.label.localeCompare(b.label)),
[municipios]);
const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({
queryKey: ['resultadosMunicipio', selectedMunicipio?.value, CATEGORIA_ID],
queryFn: () => getResultadosPorMunicipio(selectedMunicipio!.value, CATEGORIA_ID),
enabled: !!selectedMunicipio,
});
// Lógica para "Otros"
let displayResults: ResultadoTicker[] = resultados || [];
if (resultados && resultados.length > cantidadAMostrar) {
const topParties = resultados.slice(0, cantidadAMostrar - 1);
const otherParties = resultados.slice(cantidadAMostrar - 1);
const otrosPorcentaje = otherParties.reduce((sum, party) => sum + (party.porcentaje || 0), 0);
const otrosEntry: ResultadoTicker = {
id: `otros-diputados`,
id: `otros-diputados-${selectedMunicipio?.value}`,
nombre: 'Otros',
nombreCorto: 'Otros',
color: '#888888',
@@ -47,20 +74,27 @@ export const DiputadosWidget = () => {
porcentaje: otrosPorcentaje,
};
displayResults = [...topParties, otrosEntry];
} else {
displayResults = diputadosData.resultados.slice(0, cantidadAMostrar);
} else if (resultados) {
displayResults = resultados.slice(0, cantidadAMostrar);
}
return (
<div className="ticker-card">
<div className="ticker-header">
<h3>{diputadosData.categoriaNombre.replace(' PROVINCIALES', '')}</h3>
<div className="ticker-stats">
<span>Mesas: <strong>{formatPercent(diputadosData.estadoRecuento?.mesasTotalizadasPorcentaje || 0)}</strong></span>
<span>Part: <strong>{formatPercent(diputadosData.estadoRecuento?.participacionPorcentaje || 0)}</strong></span>
</div>
<h3>DIPUTADOS POR MUNICIPIO</h3>
<Select
options={municipioOptions}
value={selectedMunicipio}
onChange={(option) => setSelectedMunicipio(option)}
isLoading={isLoadingMunicipios}
placeholder="Buscar municipio..."
isClearable
styles={customSelectStyles}
/>
</div>
<div className="ticker-results">
{(isLoadingMunicipios || (isLoadingResultados && selectedMunicipio)) && <p>Cargando...</p>}
{!selectedMunicipio && !isLoadingMunicipios && <p style={{textAlign: 'center', color: '#666'}}>Seleccione un municipio.</p>}
{displayResults.map(partido => (
<div key={partido.id} className="ticker-party">
<div className="party-logo">

View File

@@ -1,19 +1,25 @@
// src/components/SenadoresWidget.tsx
import { useMemo } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { getResumenProvincial, getConfiguracionPublica } from '../apiService';
import type { CategoriaResumen, ResultadoTicker } from '../types/types';
import Select from 'react-select'; // Importamos react-select
import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica } from '../apiService'; // Usamos las funciones genéricas
import type { MunicipioSimple, ResultadoTicker } from '../types/types';
import { ImageWithFallback } from './ImageWithFallback';
import './TickerWidget.css';
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
// Estilos para el selector, podemos moverlos a un archivo común más adelante
const customSelectStyles = {
control: (base: any) => ({ ...base, minWidth: '220px', border: '1px solid #ced4da' }),
menu: (base: any) => ({ ...base, zIndex: 10 }),
};
// Constante para la categoría de este widget
const CATEGORIA_ID = 5; // Senadores
export const SenadoresWidget = () => {
const { data: categorias, isLoading, error } = useQuery<CategoriaResumen[]>({
queryKey: ['resumenProvincial'],
queryFn: getResumenProvincial,
refetchInterval: 30000,
});
const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null);
const { data: configData } = useQuery({
queryKey: ['configuracionPublica'],
@@ -21,24 +27,44 @@ export const SenadoresWidget = () => {
staleTime: 0,
});
// Usamos la clave de configuración del Ticker, ya que es para Senadores/Diputados
const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10);
// Usamos useMemo para encontrar los datos específicos de Senadores (ID 5)
const senadoresData = useMemo(() => {
return categorias?.find(c => c.categoriaId === 5);
}, [categorias]);
const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({
queryKey: ['municipios', CATEGORIA_ID], // Key única para la caché
queryFn: () => getMunicipios(CATEGORIA_ID), // Pasamos el ID de la categoría
});
if (isLoading) return <div className="ticker-card loading">Cargando...</div>;
if (error || !senadoresData) return <div className="ticker-card error">Datos de Senadores no disponibles.</div>;
// useEffect para establecer "LA PLATA" por defecto
useEffect(() => {
if (municipios.length > 0 && !selectedMunicipio) {
const laPlata = municipios.find(m => m.nombre.toUpperCase() === 'LA PLATA');
if (laPlata) {
setSelectedMunicipio({ value: laPlata.id, label: laPlata.nombre });
}
}
}, [municipios, selectedMunicipio]);
// Lógica para "Otros" aplicada solo a los resultados de Senadores
let displayResults: ResultadoTicker[] = senadoresData.resultados;
if (senadoresData.resultados.length > cantidadAMostrar) {
const topParties = senadoresData.resultados.slice(0, cantidadAMostrar - 1);
const otherParties = senadoresData.resultados.slice(cantidadAMostrar - 1);
const otrosPorcentaje = otherParties.reduce((sum, party) => sum + party.porcentaje, 0);
const municipioOptions = useMemo(() =>
municipios
.map(m => ({ value: m.id, label: m.nombre }))
.sort((a, b) => a.label.localeCompare(b.label)),
[municipios]);
const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({
queryKey: ['resultadosMunicipio', selectedMunicipio?.value, CATEGORIA_ID],
queryFn: () => getResultadosPorMunicipio(selectedMunicipio!.value, CATEGORIA_ID),
enabled: !!selectedMunicipio,
});
// Lógica para "Otros"
let displayResults: ResultadoTicker[] = resultados || [];
if (resultados && resultados.length > cantidadAMostrar) {
const topParties = resultados.slice(0, cantidadAMostrar - 1);
const otherParties = resultados.slice(cantidadAMostrar - 1);
const otrosPorcentaje = otherParties.reduce((sum, party) => sum + (party.porcentaje || 0), 0);
const otrosEntry: ResultadoTicker = {
id: `otros-senadores`,
id: `otros-senadores-${selectedMunicipio?.value}`,
nombre: 'Otros',
nombreCorto: 'Otros',
color: '#888888',
@@ -47,20 +73,27 @@ export const SenadoresWidget = () => {
porcentaje: otrosPorcentaje,
};
displayResults = [...topParties, otrosEntry];
} else {
displayResults = senadoresData.resultados.slice(0, cantidadAMostrar);
} else if (resultados) {
displayResults = resultados.slice(0, cantidadAMostrar);
}
return (
<div className="ticker-card">
<div className="ticker-header">
<h3>{senadoresData.categoriaNombre.replace(' PROVINCIALES', '')}</h3>
<div className="ticker-stats">
<span>Mesas: <strong>{formatPercent(senadoresData.estadoRecuento?.mesasTotalizadasPorcentaje || 0)}</strong></span>
<span>Part: <strong>{formatPercent(senadoresData.estadoRecuento?.participacionPorcentaje || 0)}</strong></span>
</div>
<h3>SENADORES POR MUNICIPIO</h3>
<Select
options={municipioOptions}
value={selectedMunicipio}
onChange={(option) => setSelectedMunicipio(option)}
isLoading={isLoadingMunicipios}
placeholder="Buscar municipio..."
isClearable
styles={customSelectStyles}
/>
</div>
<div className="ticker-results">
{(isLoadingMunicipios || (isLoadingResultados && selectedMunicipio)) && <p>Cargando...</p>}
{!selectedMunicipio && !isLoadingMunicipios && <p style={{ textAlign: 'center', color: '#666' }}>Seleccione un municipio.</p>}
{displayResults.map(partido => (
<div key={partido.id} className="ticker-party">
<div className="party-logo">

View File

@@ -17,20 +17,39 @@ public class CatalogosController : ControllerBase
}
[HttpGet("municipios")]
public async Task<IActionResult> GetMunicipios()
public async Task<IActionResult> GetMunicipios([FromQuery] int? categoriaId)
{
var municipios = await _dbContext.AmbitosGeograficos
var municipiosQuery = _dbContext.AmbitosGeograficos
.AsNoTracking()
.Where(a => a.NivelId == 30 && a.SeccionId != null)
.Select(a => new MunicipioSimpleDto
.Where(a => a.NivelId == 30);
// Si NO se proporciona una categoriaId, devolvemos todos (para Concejales).
if (categoriaId == null)
{
Id = a.SeccionId!,
Nombre = a.Nombre
})
.OrderBy(m => m.Nombre)
var todosLosMunicipios = await municipiosQuery
.OrderBy(a => a.Nombre)
.Select(a => new { a.SeccionId, a.Nombre }) // Usamos SeccionId como el ID electoral
.ToListAsync();
return Ok(todosLosMunicipios);
}
// --- LÓGICA DE FILTRADO PARA DIPUTADOS Y SENADORES ---
// 1. Encontrar las Secciones Electorales que SÍ tienen resultados para la categoría.
var seccionesActivasIds = await _dbContext.ProyeccionesBancas
.AsNoTracking()
.Where(p => p.CategoriaId == categoriaId)
.Select(p => p.AmbitoGeografico.SeccionProvincialId)
.Distinct()
.ToListAsync();
return Ok(municipios);
// 2. Filtramos la lista de municipios.
var municipiosFiltrados = await municipiosQuery
.Where(m => seccionesActivasIds.Contains(m.SeccionProvincialId))
.OrderBy(a => a.Nombre)
.Select(a => new { a.SeccionId, a.Nombre })
.ToListAsync();
return Ok(municipiosFiltrados);
}
[HttpGet("agrupaciones")]

View File

@@ -1,6 +1,6 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=TECNICA3;Database=Elecciones2025;User Id=apielecciones2025;Password=PTP847Elecciones2025;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;"
"DefaultConnection": "Server=192.168.5.128;Database=Elecciones2025;User Id=apielecciones2025;Password=PTP847Elecciones2025;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;"
},
"Logging": {
"LogLevel": {

View File

@@ -1,6 +1,6 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=TECNICA3;Database=Elecciones2025;User Id=apielecciones2025;Password=PTP847Elecciones2025;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;"
"DefaultConnection": "Server=192.168.5.128;Database=Elecciones2025;User Id=apielecciones2025;Password=PTP847Elecciones2025;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;"
},
"Logging": {
"LogLevel": {

View File

@@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","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=","quzFFdCjq28RUvzP4QEPqUv1aG3JErbxco/IZLvopsc=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","IXt64lxLeDS4dW1rszsenQT32xUD\u002BLsm/9zmNzXoV/c="],"CachedAssets":{},"CachedCopyCandidates":{}}
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","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=","KaNgYB2ifCIE3p/Tay5fVAWfGAbZ/FRwD44afnqRoKI=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","iL0B6pwfSYZpOYzq7AuHcEbBAAVseMon4HovdUC\u002BTcU="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","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=","quzFFdCjq28RUvzP4QEPqUv1aG3JErbxco/IZLvopsc=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","IXt64lxLeDS4dW1rszsenQT32xUD\u002BLsm/9zmNzXoV/c="],"CachedAssets":{},"CachedCopyCandidates":{}}
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","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=","KaNgYB2ifCIE3p/Tay5fVAWfGAbZ/FRwD44afnqRoKI=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","iL0B6pwfSYZpOYzq7AuHcEbBAAVseMon4HovdUC\u002BTcU="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"O7YawHw32G/Fh2bs+snZgm9O7okI0WYgTQmXM931znY=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM="],"CachedAssets":{},"CachedCopyCandidates":{}}
{"GlobalPropertiesHash":"O7YawHw32G/Fh2bs+snZgm9O7okI0WYgTQmXM931znY=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")]
[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.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6ac60342554e3ad2bc0d4f6dd3a7e62c81cd79a9")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]