From 3b0eee25e6441da1831442a90b6552cc8ef59d07 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Sun, 28 Sep 2025 19:04:09 -0300 Subject: [PATCH] =?UTF-8?q?Feat=20Widgets=20Cards=20y=20Optimizaci=C3=B3n?= =?UTF-8?q?=20de=20Consultas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/AgrupacionesManager.css | 17 + .../src/components/AgrupacionesManager.tsx | 144 ++-- .../components/BancasNacionalesManager.tsx | 117 +++ .../src/components/BancasPreviasManager.tsx | 125 +++ ...ager.tsx => BancasProvincialesManager.tsx} | 40 +- .../components/CandidatoOverridesManager.tsx | 119 +-- .../src/components/ConfiguracionNacional.tsx | 110 +++ .../src/components/DashboardPage.tsx | 92 ++- .../src/components/LogoOverridesManager.tsx | 115 +-- .../OrdenDiputadosNacionalesManager.tsx | 102 +++ .../OrdenSenadoresNacionalesManager.tsx | 94 +++ .../src/constants/categorias.ts | 23 + .../frontend-admin/src/services/apiService.ts | 108 ++- .../frontend-admin/src/types/index.ts | 20 +- Elecciones-Web/frontend/package-lock.json | 482 +++++++++--- Elecciones-Web/frontend/package.json | 5 +- .../maps/provincias-svg/buenos_aires.svg | 23 + .../public/maps/provincias-svg/catamarca.svg | 23 + .../public/maps/provincias-svg/chaco.svg | 23 + .../public/maps/provincias-svg/chubut.svg | 23 + .../ciudad_autonoma_de_buenos_aires.svg | 23 + .../public/maps/provincias-svg/cordoba.svg | 23 + .../public/maps/provincias-svg/corrientes.svg | 23 + .../public/maps/provincias-svg/entre_rios.svg | 23 + .../public/maps/provincias-svg/formosa.svg | 23 + .../public/maps/provincias-svg/jujuy.svg | 23 + .../public/maps/provincias-svg/la_pampa.svg | 23 + .../public/maps/provincias-svg/la_rioja.svg | 23 + .../public/maps/provincias-svg/mendoza.svg | 23 + .../public/maps/provincias-svg/misiones.svg | 23 + .../public/maps/provincias-svg/neuquen.svg | 23 + .../public/maps/provincias-svg/rio_negro.svg | 23 + .../public/maps/provincias-svg/salta.svg | 23 + .../public/maps/provincias-svg/san_juan.svg | 23 + .../public/maps/provincias-svg/san_luis.svg | 23 + .../public/maps/provincias-svg/santa_cruz.svg | 23 + .../public/maps/provincias-svg/santa_fe.svg | 23 + .../provincias-svg/santiago_del_estero.svg | 23 + .../maps/provincias-svg/tierra_del_fuego.svg | 23 + .../public/maps/provincias-svg/tucuman.svg | 23 + Elecciones-Web/frontend/src/apiService.ts | 43 +- .../common/DiputadosNacionalesLayout.tsx | 339 ++++++++ .../common/SenadoresNacionalesLayout.tsx | 154 ++++ .../legislativas/DevAppLegislativas.tsx | 7 +- .../nacionales/CongresoNacionalWidget.tsx | 162 ++++ .../legislativas/nacionales/PanelNacional.css | 9 +- .../ResultadosNacionalesCardsWidget.css | 259 +++++++ .../ResultadosNacionalesCardsWidget.tsx | 30 + .../nacionales/components/MiniMapaSvg.tsx | 64 ++ .../nacionales/components/ProvinciaCard.tsx | 78 ++ .../provinciales/CongresoWidget.css | 219 +++++- Elecciones-Web/frontend/src/types/types.ts | 25 + .../Controllers/AdminController.cs | 120 ++- .../Controllers/ResultadosController.cs | 223 +++++- Elecciones-Web/src/Elecciones.Api/Program.cs | 213 +++++- .../net9.0/Elecciones.Api.AssemblyInfo.cs | 2 +- .../Debug/net9.0/rjsmcshtml.dswa.cache.json | 2 +- .../Debug/net9.0/rjsmrazor.dswa.cache.json | 2 +- .../DTOs/ApiResponses/ResumenProvinciaDto.cs | 22 + .../net9.0/Elecciones.Core.AssemblyInfo.cs | 2 +- .../EleccionesDbContext.cs | 6 + .../Entities/AgrupacionPolitica.cs | 10 +- .../Entities/BancaPrevia.cs | 28 + .../Entities/EstadoRecuentoGeneral.cs | 1 + ...22213437_AddBancasPreviasTable.Designer.cs | 715 +++++++++++++++++ .../20250922213437_AddBancasPreviasTable.cs | 48 ++ ...AddOrdenNacionalToAgrupaciones.Designer.cs | 721 ++++++++++++++++++ ...24000007_AddOrdenNacionalToAgrupaciones.cs | 38 + .../EleccionesDbContextModelSnapshot.cs | 46 ++ .../Elecciones.Database.AssemblyInfo.cs | 2 +- .../Elecciones.Infrastructure.AssemblyInfo.cs | 2 +- 71 files changed, 5415 insertions(+), 442 deletions(-) create mode 100644 Elecciones-Web/frontend-admin/src/components/BancasNacionalesManager.tsx create mode 100644 Elecciones-Web/frontend-admin/src/components/BancasPreviasManager.tsx rename Elecciones-Web/frontend-admin/src/components/{BancasManager.tsx => BancasProvincialesManager.tsx} (72%) create mode 100644 Elecciones-Web/frontend-admin/src/components/ConfiguracionNacional.tsx create mode 100644 Elecciones-Web/frontend-admin/src/components/OrdenDiputadosNacionalesManager.tsx create mode 100644 Elecciones-Web/frontend-admin/src/components/OrdenSenadoresNacionalesManager.tsx create mode 100644 Elecciones-Web/frontend-admin/src/constants/categorias.ts create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/buenos_aires.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/catamarca.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/chaco.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/chubut.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/ciudad_autonoma_de_buenos_aires.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/cordoba.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/corrientes.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/entre_rios.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/formosa.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/jujuy.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/la_pampa.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/la_rioja.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/mendoza.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/misiones.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/neuquen.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/rio_negro.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/salta.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/san_juan.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/san_luis.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/santa_cruz.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/santa_fe.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/santiago_del_estero.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/tierra_del_fuego.svg create mode 100644 Elecciones-Web/frontend/public/maps/provincias-svg/tucuman.svg create mode 100644 Elecciones-Web/frontend/src/components/common/DiputadosNacionalesLayout.tsx create mode 100644 Elecciones-Web/frontend/src/components/common/SenadoresNacionalesLayout.tsx create mode 100644 Elecciones-Web/frontend/src/features/legislativas/nacionales/CongresoNacionalWidget.tsx create mode 100644 Elecciones-Web/frontend/src/features/legislativas/nacionales/ResultadosNacionalesCardsWidget.css create mode 100644 Elecciones-Web/frontend/src/features/legislativas/nacionales/ResultadosNacionalesCardsWidget.tsx create mode 100644 Elecciones-Web/frontend/src/features/legislativas/nacionales/components/MiniMapaSvg.tsx create mode 100644 Elecciones-Web/frontend/src/features/legislativas/nacionales/components/ProvinciaCard.tsx create mode 100644 Elecciones-Web/src/Elecciones.Core/DTOs/ApiResponses/ResumenProvinciaDto.cs create mode 100644 Elecciones-Web/src/Elecciones.Database/Entities/BancaPrevia.cs create mode 100644 Elecciones-Web/src/Elecciones.Database/Migrations/20250922213437_AddBancasPreviasTable.Designer.cs create mode 100644 Elecciones-Web/src/Elecciones.Database/Migrations/20250922213437_AddBancasPreviasTable.cs create mode 100644 Elecciones-Web/src/Elecciones.Database/Migrations/20250924000007_AddOrdenNacionalToAgrupaciones.Designer.cs create mode 100644 Elecciones-Web/src/Elecciones.Database/Migrations/20250924000007_AddOrdenNacionalToAgrupaciones.cs diff --git a/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.css b/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.css index 072b482..34d75f1 100644 --- a/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.css +++ b/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.css @@ -36,6 +36,23 @@ td button { margin-right: 5px; } +.table-container { + max-height: 500px; /* Altura máxima antes de que aparezca el scroll */ + overflow-y: auto; /* Habilita el scroll vertical cuando es necesario */ + border: 1px solid #ddd; + border-radius: 4px; + position: relative; /* Necesario para que 'sticky' funcione correctamente */ +} + +/* Hacemos que la cabecera de la tabla se quede fija en la parte superior */ +.table-container thead th { + position: sticky; + top: 0; + z-index: 1; + /* El color de fondo es crucial para que no se vea el contenido que pasa por debajo */ + background-color: #f2f2f2; +} + .sortable-list-horizontal { list-style: none; padding: 8px; diff --git a/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.tsx b/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.tsx index d199b19..6b0aff9 100644 --- a/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.tsx +++ b/Elecciones-Web/frontend-admin/src/components/AgrupacionesManager.tsx @@ -1,46 +1,52 @@ // src/components/AgrupacionesManager.tsx import { useState, useEffect } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; +import Select from 'react-select'; // Importamos Select import { getAgrupaciones, updateAgrupacion, getLogos, updateLogos } from '../services/apiService'; import type { AgrupacionPolitica, LogoAgrupacionCategoria } from '../types'; import './AgrupacionesManager.css'; +// Constantes para los IDs de categoría const SENADORES_ID = 5; const DIPUTADOS_ID = 6; const CONCEJALES_ID = 7; +const SENADORES_NAC_ID = 1; +const DIPUTADOS_NAC_ID = 2; + +// Opciones para el nuevo selector de Elección +const ELECCION_OPTIONS = [ + { value: 2, label: 'Elecciones Nacionales' }, + { value: 1, label: 'Elecciones Provinciales' } +]; -// Esta función limpia cualquier carácter no válido de un string de color. const sanitizeColor = (color: string | null | undefined): string => { - if (!color) return '#000000'; // Devuelve un color válido por defecto si es nulo - // Usa una expresión regular para eliminar todo lo que no sea un '#' o un carácter hexadecimal + if (!color) return '#000000'; const sanitized = color.replace(/[^#0-9a-fA-F]/g, ''); return sanitized.startsWith('#') ? sanitized : `#${sanitized}`; }; export const AgrupacionesManager = () => { const queryClient = useQueryClient(); + + // --- NUEVO ESTADO PARA LA ELECCIÓN SELECCIONADA --- + const [selectedEleccion, setSelectedEleccion] = useState(ELECCION_OPTIONS[0]); const [editedAgrupaciones, setEditedAgrupaciones] = useState>>({}); const [editedLogos, setEditedLogos] = useState([]); - // Query 1: Obtener agrupaciones const { data: agrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery({ queryKey: ['agrupaciones'], queryFn: getAgrupaciones, }); - // Query 2: Obtener logos + // --- CORRECCIÓN: La query de logos ahora depende del ID de la elección --- const { data: logos = [], isLoading: isLoadingLogos } = useQuery({ - queryKey: ['logos'], - queryFn: getLogos, + queryKey: ['logos', selectedEleccion.value], + queryFn: () => getLogos(selectedEleccion.value), // Pasamos el valor numérico }); useEffect(() => { - // Solo procedemos si los datos de agrupaciones están disponibles if (agrupaciones && agrupaciones.length > 0) { - // Inicializamos el estado de 'editedAgrupaciones' una sola vez. - // Usamos una función en setState para asegurarnos de que solo se ejecute - // si el estado está vacío, evitando sobreescribir ediciones del usuario. setEditedAgrupaciones(prev => { if (Object.keys(prev).length === 0) { return Object.fromEntries(agrupaciones.map(a => [a.id, {}])); @@ -48,20 +54,14 @@ export const AgrupacionesManager = () => { return prev; }); } - - // Hacemos lo mismo para los logos - if (logos && logos.length > 0) { - setEditedLogos(prev => { - if (prev.length === 0) { - // Creamos una copia profunda para evitar mutaciones accidentales - return JSON.parse(JSON.stringify(logos)); - } - return prev; - }); + }, [agrupaciones]); + + // Este useEffect ahora también depende de 'logos' para reinicializarse + useEffect(() => { + if (logos) { + setEditedLogos(JSON.parse(JSON.stringify(logos))); } - // La dependencia ahora es el estado de carga. El hook se ejecutará cuando - // isLoadingAgrupaciones o isLoadingLogos cambien de true a false. - }, [agrupaciones, logos]); + }, [logos]); const handleInputChange = (id: string, field: 'nombreCorto' | 'color', value: string) => { setEditedAgrupaciones(prev => ({ @@ -74,6 +74,7 @@ export const AgrupacionesManager = () => { setEditedLogos(prev => { const newLogos = [...prev]; const existing = newLogos.find(l => + l.eleccionId === selectedEleccion.value && l.agrupacionPoliticaId === agrupacionId && l.categoriaId === categoriaId && l.ambitoGeograficoId == null @@ -84,6 +85,7 @@ export const AgrupacionesManager = () => { } else { newLogos.push({ id: 0, + eleccionId: selectedEleccion.value, // Añadimos el ID de la elección agrupacionPoliticaId: agrupacionId, categoriaId, logoUrl: value, @@ -99,7 +101,7 @@ export const AgrupacionesManager = () => { const agrupacionPromises = Object.entries(editedAgrupaciones).map(([id, changes]) => { if (Object.keys(changes).length > 0) { const original = agrupaciones.find(a => a.id === id); - if (original) { // Chequeo de seguridad + if (original) { return updateAgrupacion(id, { ...original, ...changes }); } } @@ -111,7 +113,7 @@ export const AgrupacionesManager = () => { await Promise.all([...agrupacionPromises, logoPromise]); queryClient.invalidateQueries({ queryKey: ['agrupaciones'] }); - queryClient.invalidateQueries({ queryKey: ['logos'] }); + queryClient.invalidateQueries({ queryKey: ['logos', selectedEleccion.value] }); // Invalidamos la query correcta alert('¡Todos los cambios han sido guardados!'); } catch (err) { @@ -124,6 +126,7 @@ export const AgrupacionesManager = () => { const getLogoUrl = (agrupacionId: string, categoriaId: number) => { return editedLogos.find(l => + l.eleccionId === selectedEleccion.value && l.agrupacionPoliticaId === agrupacionId && l.categoriaId === categoriaId && l.ambitoGeograficoId == null @@ -132,40 +135,67 @@ export const AgrupacionesManager = () => { return (
-

Gestión de Agrupaciones y Logos

+
+

Gestión de Agrupaciones y Logos Generales

+
+ handleInputChange(agrupacion.id, 'nombreCorto', e.target.value)} /> - - handleInputChange(agrupacion.id, 'color', e.target.value)} - /> - - handleLogoChange(agrupacion.id, SENADORES_ID, e.target.value)} /> - handleLogoChange(agrupacion.id, DIPUTADOS_ID, e.target.value)} /> - handleLogoChange(agrupacion.id, CONCEJALES_ID, e.target.value)} /> +
+ + + + + + + {/* --- CABECERAS CONDICIONALES --- */} + {selectedEleccion.value === 2 ? ( + <> + + + + ) : ( + <> + + + + + )} - ))} - -
NombreNombre CortoColorLogo Senadores Nac.Logo Diputados Nac.Logo Senadores Prov.Logo Diputados Prov.Logo Concejales
+ + + {agrupaciones.map(agrupacion => ( + + {agrupacion.nombre} + handleInputChange(agrupacion.id, 'nombreCorto', e.target.value)} /> + + handleInputChange(agrupacion.id, 'color', e.target.value)} /> + + {/* --- CELDAS CONDICIONALES --- */} + {selectedEleccion.value === 2 ? ( + <> + handleLogoChange(agrupacion.id, SENADORES_NAC_ID, e.target.value)} /> + handleLogoChange(agrupacion.id, DIPUTADOS_NAC_ID, e.target.value)} /> + + ) : ( + <> + handleLogoChange(agrupacion.id, SENADORES_ID, e.target.value)} /> + handleLogoChange(agrupacion.id, DIPUTADOS_ID, e.target.value)} /> + handleLogoChange(agrupacion.id, CONCEJALES_ID, e.target.value)} /> + + )} + + ))} + + +
diff --git a/Elecciones-Web/frontend-admin/src/components/BancasNacionalesManager.tsx b/Elecciones-Web/frontend-admin/src/components/BancasNacionalesManager.tsx new file mode 100644 index 0000000..d6c6b84 --- /dev/null +++ b/Elecciones-Web/frontend-admin/src/components/BancasNacionalesManager.tsx @@ -0,0 +1,117 @@ +// src/components/BancasNacionalesManager.tsx +import { useState } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { getBancadas, getAgrupaciones, updateBancada, type UpdateBancadaData } from '../services/apiService'; +import type { Bancada, AgrupacionPolitica } from '../types'; +import { OcupantesModal } from './OcupantesModal'; +import './AgrupacionesManager.css'; + +const ELECCION_ID_NACIONAL = 2; +const camaras = ['diputados', 'senadores'] as const; + +export const BancasNacionalesManager = () => { + const [activeTab, setActiveTab] = useState<'diputados' | 'senadores'>('diputados'); + const [modalVisible, setModalVisible] = useState(false); + const [bancadaSeleccionada, setBancadaSeleccionada] = useState(null); + const queryClient = useQueryClient(); + + const { data: agrupaciones = [] } = useQuery({ + queryKey: ['agrupaciones'], + queryFn: getAgrupaciones + }); + + const { data: bancadas = [], isLoading, error } = useQuery({ + queryKey: ['bancadas', activeTab, ELECCION_ID_NACIONAL], + queryFn: () => getBancadas(activeTab, ELECCION_ID_NACIONAL), + }); + + const handleAgrupacionChange = async (bancadaId: number, nuevaAgrupacionId: string | null) => { + const bancadaActual = bancadas.find(b => b.id === bancadaId); + if (!bancadaActual) return; + + const payload: UpdateBancadaData = { + agrupacionPoliticaId: nuevaAgrupacionId, + nombreOcupante: nuevaAgrupacionId ? (bancadaActual.ocupante?.nombreOcupante ?? null) : null, + fotoUrl: nuevaAgrupacionId ? (bancadaActual.ocupante?.fotoUrl ?? null) : null, + periodo: nuevaAgrupacionId ? (bancadaActual.ocupante?.periodo ?? null) : null, + }; + + try { + await updateBancada(bancadaId, payload); + queryClient.invalidateQueries({ queryKey: ['bancadas', activeTab, ELECCION_ID_NACIONAL] }); + } catch (err) { + alert("Error al guardar el cambio de agrupación."); + } + }; + + const handleOpenModal = (bancada: Bancada) => { + setBancadaSeleccionada(bancada); + setModalVisible(true); + }; + + if (error) return

Error al cargar las bancas nacionales.

; + + return ( +
+

Gestión de Bancas (Nacionales)

+

Asigne partidos y ocupantes a las bancas del Congreso de la Nación.

+ +
+ {camaras.map(camara => ( + + ))} +
+ + {isLoading ?

Cargando bancas...

: ( +
+ + + + + + + + + + + {bancadas.map((bancada) => ( + + + + + + + ))} + +
Banca #Partido AsignadoOcupante ActualAcciones
{`${activeTab.charAt(0).toUpperCase()}-${bancada.numeroBanca}`} + + {bancada.ocupante?.nombreOcupante || 'Sin asignar'} + +
+
+ )} + + {modalVisible && bancadaSeleccionada && ( + setModalVisible(false)} + activeTab={activeTab} + /> + )} +
+ ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend-admin/src/components/BancasPreviasManager.tsx b/Elecciones-Web/frontend-admin/src/components/BancasPreviasManager.tsx new file mode 100644 index 0000000..b8cb6c0 --- /dev/null +++ b/Elecciones-Web/frontend-admin/src/components/BancasPreviasManager.tsx @@ -0,0 +1,125 @@ +// src/components/BancasPreviasManager.tsx +import { useState, useEffect } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { getBancasPrevias, updateBancasPrevias, getAgrupaciones } from '../services/apiService'; +import type { BancaPrevia, AgrupacionPolitica } from '../types'; +import { TipoCamara } from '../types'; + +const ELECCION_ID_NACIONAL = 2; + +export const BancasPreviasManager = () => { + const queryClient = useQueryClient(); + const [editedBancas, setEditedBancas] = useState>>({}); + + const { data: agrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery({ + queryKey: ['agrupaciones'], + queryFn: getAgrupaciones, + }); + + const { data: bancasPrevias = [], isLoading: isLoadingBancas } = useQuery({ + queryKey: ['bancasPrevias', ELECCION_ID_NACIONAL], + queryFn: () => getBancasPrevias(ELECCION_ID_NACIONAL), + }); + + useEffect(() => { + if (agrupaciones.length > 0) { + const initialData: Record> = {}; + agrupaciones.forEach(agrupacion => { + // Para Diputados + const keyDip = `${agrupacion.id}-${TipoCamara.Diputados}`; + const existingDip = bancasPrevias.find(b => b.agrupacionPoliticaId === agrupacion.id && b.camara === TipoCamara.Diputados); + initialData[keyDip] = { cantidad: existingDip?.cantidad || 0 }; + + // Para Senadores + const keySen = `${agrupacion.id}-${TipoCamara.Senadores}`; + const existingSen = bancasPrevias.find(b => b.agrupacionPoliticaId === agrupacion.id && b.camara === TipoCamara.Senadores); + initialData[keySen] = { cantidad: existingSen?.cantidad || 0 }; + }); + setEditedBancas(initialData); + } + }, [agrupaciones, bancasPrevias]); + + const handleInputChange = (agrupacionId: string, camara: typeof TipoCamara.Diputados | typeof TipoCamara.Senadores, value: string) => { + const key = `${agrupacionId}-${camara}`; + const cantidad = parseInt(value, 10); + setEditedBancas(prev => ({ + ...prev, + [key]: { ...prev[key], cantidad: isNaN(cantidad) ? 0 : cantidad } + })); + }; + + const handleSave = async () => { + const payload: BancaPrevia[] = Object.entries(editedBancas) + .map(([key, value]) => { + const [agrupacionPoliticaId, camara] = key.split('-'); + return { + id: 0, + eleccionId: ELECCION_ID_NACIONAL, + agrupacionPoliticaId, + camara: parseInt(camara) as typeof TipoCamara.Diputados | typeof TipoCamara.Senadores, + cantidad: value.cantidad || 0, + }; + }) + .filter(b => b.cantidad > 0); + + try { + await updateBancasPrevias(ELECCION_ID_NACIONAL, payload); + queryClient.invalidateQueries({ queryKey: ['bancasPrevias', ELECCION_ID_NACIONAL] }); + alert('Bancas previas guardadas con éxito.'); + } catch (error) { + console.error(error); + alert('Error al guardar las bancas previas.'); + } + }; + + const totalDiputados = Object.entries(editedBancas).reduce((sum, [key, value]) => key.endsWith(`-${TipoCamara.Diputados}`) ? sum + (value.cantidad || 0) : sum, 0); + const totalSenadores = Object.entries(editedBancas).reduce((sum, [key, value]) => key.endsWith(`-${TipoCamara.Senadores}`) ? sum + (value.cantidad || 0) : sum, 0); + + const isLoading = isLoadingAgrupaciones || isLoadingBancas; + + return ( +
+

Gestión de Bancas Previas (Composición Nacional)

+

Define cuántas bancas retiene cada partido antes de la elección. Estos son los escaños que **no** están en juego.

+ {isLoading ?

Cargando...

: ( + <> +
+ + + + + + + + + + {agrupaciones.map(agrupacion => ( + + + + + + ))} + +
Agrupación PolíticaBancas Previas Diputados (Total: {totalDiputados} / 130)Bancas Previas Senadores (Total: {totalSenadores} / 48)
{agrupacion.nombre} + handleInputChange(agrupacion.id, TipoCamara.Diputados, e.target.value)} + /> + + handleInputChange(agrupacion.id, TipoCamara.Senadores, e.target.value)} + /> +
+
+ + + )} +
+ ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend-admin/src/components/BancasManager.tsx b/Elecciones-Web/frontend-admin/src/components/BancasProvincialesManager.tsx similarity index 72% rename from Elecciones-Web/frontend-admin/src/components/BancasManager.tsx rename to Elecciones-Web/frontend-admin/src/components/BancasProvincialesManager.tsx index 86a85c4..b2bef4e 100644 --- a/Elecciones-Web/frontend-admin/src/components/BancasManager.tsx +++ b/Elecciones-Web/frontend-admin/src/components/BancasProvincialesManager.tsx @@ -1,4 +1,4 @@ -// src/components/BancasManager.tsx +// src/components/BancasProvincialesManager.tsx import { useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { getBancadas, getAgrupaciones, updateBancada, type UpdateBancadaData } from '../services/apiService'; @@ -6,9 +6,10 @@ import type { Bancada, AgrupacionPolitica } from '../types'; import { OcupantesModal } from './OcupantesModal'; import './AgrupacionesManager.css'; +const ELECCION_ID_PROVINCIAL = 1; const camaras = ['diputados', 'senadores'] as const; -export const BancasManager = () => { +export const BancasProvincialesManager = () => { const [activeTab, setActiveTab] = useState<'diputados' | 'senadores'>('diputados'); const [modalVisible, setModalVisible] = useState(false); const [bancadaSeleccionada, setBancadaSeleccionada] = useState(null); @@ -19,16 +20,18 @@ export const BancasManager = () => { queryFn: getAgrupaciones }); + // --- CORRECCIÓN CLAVE --- + // 1. La queryKey ahora incluye el eleccionId para ser única. + // 2. La función queryFn ahora pasa el ELECCION_ID_PROVINCIAL a getBancadas. const { data: bancadas = [], isLoading, error } = useQuery({ - queryKey: ['bancadas', activeTab], - queryFn: () => getBancadas(activeTab), + queryKey: ['bancadas', activeTab, ELECCION_ID_PROVINCIAL], + queryFn: () => getBancadas(activeTab, ELECCION_ID_PROVINCIAL), }); const handleAgrupacionChange = async (bancadaId: number, nuevaAgrupacionId: string | null) => { const bancadaActual = bancadas.find(b => b.id === bancadaId); if (!bancadaActual) return; - // Si se desasigna el partido (vacante), también se limpia el ocupante const payload: UpdateBancadaData = { agrupacionPoliticaId: nuevaAgrupacionId, nombreOcupante: nuevaAgrupacionId ? (bancadaActual.ocupante?.nombreOcupante ?? null) : null, @@ -38,7 +41,7 @@ export const BancasManager = () => { try { await updateBancada(bancadaId, payload); - queryClient.invalidateQueries({ queryKey: ['bancadas', activeTab] }); + queryClient.invalidateQueries({ queryKey: ['bancadas', activeTab, ELECCION_ID_PROVINCIAL] }); } catch (err) { alert("Error al guardar el cambio de agrupación."); } @@ -49,12 +52,12 @@ export const BancasManager = () => { setModalVisible(true); }; - if (error) return

Error al cargar las bancas.

; + if (error) return

Error al cargar las bancas provinciales.

; return (
-

Gestión de Ocupación de Bancas

-

Asigne a cada banca física un partido político y, opcionalmente, los datos de la persona que la ocupa.

+

Gestión de Bancas (Provinciales)

+

Asigne partidos y ocupantes a las bancas de la legislatura provincial.

{camaras.map(camara => ( @@ -63,7 +66,7 @@ export const BancasManager = () => { className={activeTab === camara ? 'active' : ''} onClick={() => setActiveTab(camara)} > - {camara === 'diputados' ? 'Cámara de Diputados' : 'Cámara de Senadores'} + {camara === 'diputados' ? 'Diputados Provinciales (92)' : 'Senadores Provinciales (46)'} ))}
@@ -81,16 +84,7 @@ export const BancasManager = () => { {bancadas.map((bancada) => ( - {/* Usamos el NumeroBanca para la etiqueta visual */} - - {`${activeTab.charAt(0).toUpperCase()}-${bancada.numeroBanca}`} - {((activeTab === 'diputados' && bancada.numeroBanca === 92) || - (activeTab === 'senadores' && bancada.numeroBanca === 46)) && ( - - (Presidencia) - - )} - + {`${activeTab.charAt(0).toUpperCase()}-${bancada.numeroBanca}`} { setSelectedEleccion(opt!); setSelectedCategoria(null); }} /> + ({ value: a.id, label: a.nombre, ...a }))} getOptionValue={opt => opt.id} getOptionLabel={opt => opt.nombre} value={selectedAgrupacion} onChange={setSelectedAgrupacion} placeholder="Seleccione Agrupación..." /> + ({ value: p.id, label: p.nombre, ...p }))} getOptionValue={opt => opt.id} getOptionLabel={opt => opt.nombre} value={selectedProvincia} onChange={setSelectedProvincia} placeholder="Seleccione Provincia..." /> + ) :
} + + {selectedAmbitoLevel.value === 'municipio' ? ( + -
-
- - -
-
setNombreCandidato(e.target.value)} style={{ width: '100%' }} disabled={!selectedAgrupacion || !selectedCategoria} />
diff --git a/Elecciones-Web/frontend-admin/src/components/ConfiguracionNacional.tsx b/Elecciones-Web/frontend-admin/src/components/ConfiguracionNacional.tsx new file mode 100644 index 0000000..1b0fa33 --- /dev/null +++ b/Elecciones-Web/frontend-admin/src/components/ConfiguracionNacional.tsx @@ -0,0 +1,110 @@ +// src/components/ConfiguracionNacional.tsx +import { useState, useEffect } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; +import { getAgrupaciones, getConfiguracion, updateConfiguracion } from '../services/apiService'; +import type { AgrupacionPolitica } from '../types'; +import './AgrupacionesManager.css'; + +export const ConfiguracionNacional = () => { + const queryClient = useQueryClient(); + const [agrupaciones, setAgrupaciones] = useState([]); + const [loading, setLoading] = useState(true); + + const [presidenciaDiputadosId, setPresidenciaDiputadosId] = useState(''); + const [presidenciaSenadoId, setPresidenciaSenadoId] = useState(''); + const [modoOficialActivo, setModoOficialActivo] = useState(false); + const [diputadosTipoBanca, setDiputadosTipoBanca] = useState<'ganada' | 'previa'>('ganada'); + // El estado para el tipo de banca del senado ya no es necesario para la UI, + // pero lo mantenemos para no romper el handleSave. + const [senadoTipoBanca, setSenadoTipoBanca] = useState<'ganada' | 'previa'>('ganada'); + + + useEffect(() => { + const loadInitialData = async () => { + try { + setLoading(true); + const [agrupacionesData, configData] = await Promise.all([getAgrupaciones(), getConfiguracion()]); + setAgrupaciones(agrupacionesData); + setPresidenciaDiputadosId(configData.PresidenciaDiputadosNacional || ''); + setPresidenciaSenadoId(configData.PresidenciaSenadoNacional || ''); + setModoOficialActivo(configData.UsarDatosOficialesNacionales === 'true'); + setDiputadosTipoBanca(configData.PresidenciaDiputadosNacional_TipoBanca === 'previa' ? 'previa' : 'ganada'); + setSenadoTipoBanca(configData.PresidenciaSenadoNacional_TipoBanca === 'previa' ? 'previa' : 'ganada'); + } catch (err) { console.error("Error al cargar datos de configuración nacional:", err); } + finally { setLoading(false); } + }; + loadInitialData(); + }, []); + + const handleSave = async () => { + try { + await updateConfiguracion({ + "PresidenciaDiputadosNacional": presidenciaDiputadosId, + "PresidenciaSenadoNacional": presidenciaSenadoId, + "UsarDatosOficialesNacionales": modoOficialActivo.toString(), + "PresidenciaDiputadosNacional_TipoBanca": diputadosTipoBanca, + // Aunque no se muestre, guardamos el valor para consistencia + "PresidenciaSenadoNacional_TipoBanca": senadoTipoBanca, + }); + queryClient.invalidateQueries({ queryKey: ['composicionNacional'] }); + alert('Configuración nacional guardada.'); + } catch { alert('Error al guardar.'); } + }; + + if (loading) return

Cargando...

; + + return ( +
+

Configuración de Widgets Nacionales

+ {/*
+ +

+ Si está activo, los widgets nacionales usarán la composición manual de bancas. Si no, usarán la proyección en tiempo real. +

+
*/} + +
+ {/* Columna Diputados */} +
+ +

+ Este escaño es parte de los 257 diputados y se descuenta del total del partido. +

+ + {presidenciaDiputadosId && ( +
+ + +
+ )} +
+ + {/* Columna Senadores */} +
+ +

+ Este escaño es adicional a los 72 senadores y no se descuenta del total del partido. +

+ +
+
+ + +
+ ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend-admin/src/components/DashboardPage.tsx b/Elecciones-Web/frontend-admin/src/components/DashboardPage.tsx index 5dabdd2..511faa0 100644 --- a/Elecciones-Web/frontend-admin/src/components/DashboardPage.tsx +++ b/Elecciones-Web/frontend-admin/src/components/DashboardPage.tsx @@ -1,43 +1,89 @@ // src/components/DashboardPage.tsx import { useAuth } from '../context/AuthContext'; import { AgrupacionesManager } from './AgrupacionesManager'; -import { OrdenDiputadosManager } from './OrdenDiputadosManager'; -import { OrdenSenadoresManager } from './OrdenSenadoresManager'; -import { ConfiguracionGeneral } from './ConfiguracionGeneral'; -import { BancasManager } from './BancasManager'; +//import { OrdenDiputadosManager } from './OrdenDiputadosManager'; +//import { OrdenSenadoresManager } from './OrdenSenadoresManager'; +//import { ConfiguracionGeneral } from './ConfiguracionGeneral'; import { LogoOverridesManager } from './LogoOverridesManager'; import { CandidatoOverridesManager } from './CandidatoOverridesManager'; import { WorkerManager } from './WorkerManager'; +import { ConfiguracionNacional } from './ConfiguracionNacional'; +import { BancasPreviasManager } from './BancasPreviasManager'; +import { OrdenDiputadosNacionalesManager } from './OrdenDiputadosNacionalesManager'; +import { OrdenSenadoresNacionalesManager } from './OrdenSenadoresNacionalesManager'; +//import { BancasProvincialesManager } from './BancasProvincialesManager'; +//import { BancasNacionalesManager } from './BancasNacionalesManager'; + export const DashboardPage = () => { const { logout } = useAuth(); + const sectionStyle = { + border: '1px solid #dee2e6', + borderRadius: '8px', + padding: '1.5rem', + marginBottom: '2rem', + backgroundColor: '#f8f9fa' + }; + + const sectionTitleStyle = { + marginTop: 0, + borderBottom: '2px solid #007bff', + paddingBottom: '0.5rem', + marginBottom: '1.5rem', + color: '#007bff' + }; + return (
-
+

Panel de Administración Electoral

+
- -
- -
-
- -
-
-
- -
-
- -
+ +
+

Configuración Global

+ + + +
+ +
+

Gestión de Elecciones Nacionales

+ + +
+
+ +
+
+ +
+
+ {/* */} +
+ + {/* +
+

Gestión de Elecciones Provinciales

+ + +
+
+ +
+
+ +
+
+
*/} + +
+

Gestión de Workers y Sistema

+
- - -
-
); diff --git a/Elecciones-Web/frontend-admin/src/components/LogoOverridesManager.tsx b/Elecciones-Web/frontend-admin/src/components/LogoOverridesManager.tsx index c29d7a2..860df5d 100644 --- a/Elecciones-Web/frontend-admin/src/components/LogoOverridesManager.tsx +++ b/Elecciones-Web/frontend-admin/src/components/LogoOverridesManager.tsx @@ -2,83 +2,104 @@ import { useState, useMemo, useEffect } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import Select from 'react-select'; -import { getMunicipiosForAdmin, getAgrupaciones, getLogos, updateLogos } from '../services/apiService'; -import type { MunicipioSimple, AgrupacionPolitica, LogoAgrupacionCategoria } from '../types'; +import { getProvinciasForAdmin, getMunicipiosForAdmin, getAgrupaciones, getLogos, updateLogos } from '../services/apiService'; +import type { MunicipioSimple, AgrupacionPolitica, LogoAgrupacionCategoria, ProvinciaSimple } from '../types'; +import { CATEGORIAS_NACIONALES_OPTIONS, CATEGORIAS_PROVINCIALES_OPTIONS } from '../constants/categorias'; -// --- AÑADIMOS LAS CATEGORÍAS PARA EL SELECTOR --- -const CATEGORIAS_OPTIONS = [ - { value: 5, label: 'Senadores' }, - { value: 6, label: 'Diputados' }, - { value: 7, label: 'Concejales' } +const ELECCION_OPTIONS = [ + { value: 2, label: 'Elecciones Nacionales' }, + { value: 1, label: 'Elecciones Provinciales' } +]; + +const AMBITO_LEVEL_OPTIONS = [ + { value: 'general', label: 'General (Toda la elección)' }, + { value: 'provincia', label: 'Por Provincia' }, + { value: 'municipio', label: 'Por Municipio' } ]; export const LogoOverridesManager = () => { const queryClient = useQueryClient(); - const { data: municipios = [] } = useQuery({ queryKey: ['municipiosForAdmin'], queryFn: getMunicipiosForAdmin }); - const { data: agrupaciones = [] } = useQuery({ queryKey: ['agrupaciones'], queryFn: getAgrupaciones }); - const { data: logos = [] } = useQuery({ queryKey: ['logos'], queryFn: getLogos }); - // --- NUEVO ESTADO PARA LA CATEGORÍA --- + // --- ESTADOS --- + const [selectedEleccion, setSelectedEleccion] = useState(ELECCION_OPTIONS[0]); + const [selectedAmbitoLevel, setSelectedAmbitoLevel] = useState(AMBITO_LEVEL_OPTIONS[0]); + const [selectedProvincia, setSelectedProvincia] = useState(null); + const [selectedMunicipio, setSelectedMunicipio] = useState(null); 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); + const [selectedAgrupacion, setSelectedAgrupacion] = useState(null); const [logoUrl, setLogoUrl] = useState(''); - const municipioOptions = useMemo(() => - [{ value: 'general', label: 'General (Todas las secciones)' }, ...municipios.map(m => ({ value: m.id, label: m.nombre }))] - , [municipios]); - const agrupacionOptions = useMemo(() => agrupaciones.map(a => ({ value: a.id, label: a.nombre })), [agrupaciones]); + // --- QUERIES --- + const { data: provincias = [] } = useQuery({ queryKey: ['provinciasForAdmin'], queryFn: getProvinciasForAdmin }); + const { data: municipios = [] } = useQuery({ queryKey: ['municipiosForAdmin'], queryFn: getMunicipiosForAdmin }); + const { data: agrupaciones = [] } = useQuery({ queryKey: ['agrupaciones'], queryFn: getAgrupaciones }); + const { data: logos = [] } = useQuery({ + queryKey: ['logos', selectedEleccion.value], + queryFn: () => getLogos(selectedEleccion.value) + }); + + // --- LÓGICA DE SELECTORES DINÁMICOS --- + const categoriaOptions = selectedEleccion.value === 2 ? CATEGORIAS_NACIONALES_OPTIONS : CATEGORIAS_PROVINCIALES_OPTIONS; + + const getAmbitoId = () => { + if (selectedAmbitoLevel.value === 'municipio' && selectedMunicipio) return parseInt(selectedMunicipio.id); + if (selectedAmbitoLevel.value === 'provincia' && selectedProvincia) return parseInt(selectedProvincia.id); + return null; + }; const currentLogo = useMemo(() => { - // La búsqueda ahora depende de los 3 selectores - if (!selectedMunicipio || !selectedAgrupacion || !selectedCategoria) return ''; - return logos.find(l => - l.ambitoGeograficoId === parseInt(selectedMunicipio.value) && - l.agrupacionPoliticaId === selectedAgrupacion.value && + if (!selectedAgrupacion || !selectedCategoria) return ''; + const ambitoId = getAmbitoId(); + return logos.find(l => + l.ambitoGeograficoId === ambitoId && + l.agrupacionPoliticaId === selectedAgrupacion.id && l.categoriaId === selectedCategoria.value )?.logoUrl || ''; - }, [logos, selectedMunicipio, selectedAgrupacion, selectedCategoria]); - - useEffect(() => { setLogoUrl(currentLogo) }, [currentLogo]); + }, [logos, selectedAmbitoLevel, selectedProvincia, selectedMunicipio, selectedAgrupacion, selectedCategoria]); + + useEffect(() => { setLogoUrl(currentLogo || ''); }, [currentLogo]); const handleSave = async () => { - if (!selectedMunicipio || !selectedAgrupacion || !selectedCategoria) return; + if (!selectedAgrupacion || !selectedCategoria) return; const newLogoEntry: LogoAgrupacionCategoria = { id: 0, - agrupacionPoliticaId: selectedAgrupacion.value, + eleccionId: selectedEleccion.value, + agrupacionPoliticaId: selectedAgrupacion.id, categoriaId: selectedCategoria.value, - ambitoGeograficoId: parseInt(selectedMunicipio.value), + ambitoGeograficoId: getAmbitoId(), logoUrl: logoUrl || null }; try { await updateLogos([newLogoEntry]); - queryClient.invalidateQueries({ queryKey: ['logos'] }); + queryClient.invalidateQueries({ queryKey: ['logos', selectedEleccion.value] }); alert('Override de logo guardado.'); } catch { alert('Error al guardar.'); } }; - + return (
-

Overrides de Logos por Municipio y Categoría

-

Configure una imagen específica para un partido en un municipio y categoría determinados.

-
+

Overrides de Logos

+

Configure una imagen específica para un partido en un contexto determinado.

+
+ + { setSelectedAmbitoLevel(opt!); setSelectedProvincia(null); setSelectedMunicipio(null); }} /> + + {selectedAmbitoLevel.value === 'provincia' || selectedAmbitoLevel.value === 'municipio' ? ( + ({ value: m.id, label: m.nombre, ...m }))} getOptionValue={opt => opt.id} getOptionLabel={opt => opt.nombre} value={selectedMunicipio} onChange={setSelectedMunicipio} placeholder="Seleccione Municipio..." isDisabled={!selectedProvincia} /> + ) :
} +
+
- - -
-
- - setLogoUrl(e.target.value)} style={{ width: '100%' }} disabled={!selectedMunicipio || !selectedAgrupacion || !selectedCategoria} /> + setLogoUrl(e.target.value)} style={{ width: '100%' }} disabled={!selectedAgrupacion || !selectedCategoria} />
- +
); diff --git a/Elecciones-Web/frontend-admin/src/components/OrdenDiputadosNacionalesManager.tsx b/Elecciones-Web/frontend-admin/src/components/OrdenDiputadosNacionalesManager.tsx new file mode 100644 index 0000000..d9307b7 --- /dev/null +++ b/Elecciones-Web/frontend-admin/src/components/OrdenDiputadosNacionalesManager.tsx @@ -0,0 +1,102 @@ +// src/components/OrdenDiputadosNacionalesManager.tsx +import { useState, useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent } from '@dnd-kit/core'; +import { arrayMove, SortableContext, sortableKeyboardCoordinates, horizontalListSortingStrategy } from '@dnd-kit/sortable'; +import { getAgrupaciones, updateOrden, getComposicionNacional } from '../services/apiService'; +import type { AgrupacionPolitica } from '../types'; +import { SortableItem } from './SortableItem'; +import './AgrupacionesManager.css'; + +const ELECCION_ID_NACIONAL = 2; + +export const OrdenDiputadosNacionalesManager = () => { + // Estado para la lista que el usuario puede ordenar + const [agrupacionesOrdenadas, setAgrupacionesOrdenadas] = useState([]); + + // Query 1: Obtener TODAS las agrupaciones para tener sus datos completos (nombre, etc.) + const { data: todasAgrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery({ + queryKey: ['agrupaciones'], + queryFn: getAgrupaciones, + }); + + // Query 2: Obtener los datos de composición para saber qué partidos tienen bancas + const { data: composicionData, isLoading: isLoadingComposicion } = useQuery({ + queryKey: ['composicionNacional', ELECCION_ID_NACIONAL], + queryFn: () => getComposicionNacional(ELECCION_ID_NACIONAL), + }); + + // Este efecto se ejecuta cuando los datos de las queries estén disponibles + useEffect(() => { + // No hacemos nada hasta que ambas queries hayan cargado sus datos + if (!composicionData || !todasAgrupaciones || todasAgrupaciones.length === 0) { + return; + } + + // Creamos un Set con los IDs de los partidos que tienen al menos una banca de diputado + const partidosConBancasIds = new Set( + composicionData.diputados.partidos + .filter(p => p.bancasTotales > 0) + .map(p => p.id) + ); + + // Filtramos la lista completa de agrupaciones, quedándonos solo con las relevantes + const agrupacionesFiltradas = todasAgrupaciones.filter(a => partidosConBancasIds.has(a.id)); + + // Ordenamos la lista filtrada según el orden guardado en la BD + agrupacionesFiltradas.sort((a, b) => (a.ordenDiputadosNacionales || 999) - (b.ordenDiputadosNacionales || 999)); + + // Actualizamos el estado que se renderiza y que el usuario puede ordenar + setAgrupacionesOrdenadas(agrupacionesFiltradas); + + }, [todasAgrupaciones, composicionData]); // Dependencias: se re-ejecuta si los datos cambian + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) + ); + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + if (over && active.id !== over.id) { + setAgrupacionesOrdenadas((items) => { + const oldIndex = items.findIndex((item) => item.id === active.id); + const newIndex = items.findIndex((item) => item.id === over.id); + return arrayMove(items, oldIndex, newIndex); + }); + } + }; + + const handleSaveOrder = async () => { + const idsOrdenados = agrupacionesOrdenadas.map(a => a.id); + try { + await updateOrden('diputados-nacionales', idsOrdenados); + alert('Orden de Diputados Nacionales guardado con éxito!'); + } catch (error) { + alert('Error al guardar el orden de Diputados Nacionales.'); + } + }; + + const isLoading = isLoadingAgrupaciones || isLoadingComposicion; + if (isLoading) return

Cargando orden de Diputados Nacionales...

; + + return ( +
+

Ordenar Agrupaciones (Diputados Nacionales)

+

Arrastre para reordenar. Solo se muestran los partidos con bancas.

+

Ancla izquierda. Prioridad de izquierda a derecha y de arriba abajo.

+ + a.id)} strategy={horizontalListSortingStrategy}> +
    + {agrupacionesOrdenadas.map(agrupacion => ( + + {agrupacion.nombreCorto || agrupacion.nombre} + + ))} +
+
+
+ +
+ ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend-admin/src/components/OrdenSenadoresNacionalesManager.tsx b/Elecciones-Web/frontend-admin/src/components/OrdenSenadoresNacionalesManager.tsx new file mode 100644 index 0000000..a37fb71 --- /dev/null +++ b/Elecciones-Web/frontend-admin/src/components/OrdenSenadoresNacionalesManager.tsx @@ -0,0 +1,94 @@ +// src/components/OrdenSenadoresNacionalesManager.tsx +import { useState, useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent } from '@dnd-kit/core'; +import { arrayMove, SortableContext, sortableKeyboardCoordinates, horizontalListSortingStrategy } from '@dnd-kit/sortable'; +import { getAgrupaciones, updateOrden, getComposicionNacional } from '../services/apiService'; +import type { AgrupacionPolitica } from '../types'; +import { SortableItem } from './SortableItem'; +import './AgrupacionesManager.css'; + +const ELECCION_ID_NACIONAL = 2; + +export const OrdenSenadoresNacionalesManager = () => { + const [agrupacionesOrdenadas, setAgrupacionesOrdenadas] = useState([]); + + const { data: todasAgrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery({ + queryKey: ['agrupaciones'], + queryFn: getAgrupaciones, + }); + + const { data: composicionData, isLoading: isLoadingComposicion } = useQuery({ + queryKey: ['composicionNacional', ELECCION_ID_NACIONAL], + queryFn: () => getComposicionNacional(ELECCION_ID_NACIONAL), + }); + + useEffect(() => { + if (!composicionData || !todasAgrupaciones || todasAgrupaciones.length === 0) { + return; + } + + // Creamos un Set con los IDs de los partidos que tienen al menos una banca de senador + const partidosConBancasIds = new Set( + composicionData.senadores.partidos + .filter(p => p.bancasTotales > 0) + .map(p => p.id) + ); + + const agrupacionesFiltradas = todasAgrupaciones.filter(a => partidosConBancasIds.has(a.id)); + + agrupacionesFiltradas.sort((a, b) => (a.ordenSenadoresNacionales || 999) - (b.ordenSenadoresNacionales || 999)); + + setAgrupacionesOrdenadas(agrupacionesFiltradas); + + }, [todasAgrupaciones, composicionData]); + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) + ); + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + if (over && active.id !== over.id) { + setAgrupacionesOrdenadas((items) => { + const oldIndex = items.findIndex((item) => item.id === active.id); + const newIndex = items.findIndex((item) => item.id === over.id); + return arrayMove(items, oldIndex, newIndex); + }); + } + }; + + const handleSaveOrder = async () => { + const idsOrdenados = agrupacionesOrdenadas.map(a => a.id); + try { + await updateOrden('senadores-nacionales', idsOrdenados); + alert('Orden de Senadores Nacionales guardado con éxito!'); + } catch (error) { + alert('Error al guardar el orden de Senadores Nacionales.'); + } + }; + + const isLoading = isLoadingAgrupaciones || isLoadingComposicion; + if (isLoading) return

Cargando orden de Senadores Nacionales...

; + + return ( +
+

Ordenar Agrupaciones (Senado de la Nación)

+

Arrastre para reordenar. Solo se muestran los partidos con bancas.

+

Ancla izquierda. Prioridad de izquierda a derecha y de arriba abajo.

+ + a.id)} strategy={horizontalListSortingStrategy}> +
    + {agrupacionesOrdenadas.map(agrupacion => ( + + {agrupacion.nombreCorto || agrupacion.nombre} + + ))} +
+
+
+ +
+ ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend-admin/src/constants/categorias.ts b/Elecciones-Web/frontend-admin/src/constants/categorias.ts new file mode 100644 index 0000000..b27b80d --- /dev/null +++ b/Elecciones-Web/frontend-admin/src/constants/categorias.ts @@ -0,0 +1,23 @@ +// src/constants/categorias.ts + +// Opciones para los selectores en el panel de administración +export const CATEGORIAS_ADMIN_OPTIONS = [ + // Nacionales + { value: 1, label: 'Senadores Nacionales' }, + { value: 2, label: 'Diputados Nacionales' }, + // Provinciales + { value: 5, label: 'Senadores Provinciales' }, + { value: 6, label: 'Diputados Provinciales' }, + { value: 7, label: 'Concejales' }, +]; + +export const CATEGORIAS_NACIONALES_OPTIONS = [ + { value: 1, label: 'Senadores Nacionales' }, + { value: 2, label: 'Diputados Nacionales' }, +]; + +export const CATEGORIAS_PROVINCIALES_OPTIONS = [ + { value: 5, label: 'Senadores Provinciales' }, + { value: 6, label: 'Diputados Provinciales' }, + { value: 7, label: 'Concejales' }, +]; \ No newline at end of file diff --git a/Elecciones-Web/frontend-admin/src/services/apiService.ts b/Elecciones-Web/frontend-admin/src/services/apiService.ts index 27abec8..9a83b0a 100644 --- a/Elecciones-Web/frontend-admin/src/services/apiService.ts +++ b/Elecciones-Web/frontend-admin/src/services/apiService.ts @@ -1,11 +1,12 @@ // src/services/apiService.ts import axios from 'axios'; import { triggerLogout } from '../context/authUtils'; -import type { CandidatoOverride, AgrupacionPolitica, UpdateAgrupacionData, Bancada, LogoAgrupacionCategoria, MunicipioSimple } from '../types'; +import type { CandidatoOverride, AgrupacionPolitica, + UpdateAgrupacionData, Bancada, LogoAgrupacionCategoria, + MunicipioSimple, BancaPrevia, ProvinciaSimple } from '../types'; /** * URL base para las llamadas a la API. - * Se usa para construir las URLs más específicas. */ const API_URL_BASE = import.meta.env.DEV ? 'http://localhost:5217/api' @@ -21,13 +22,19 @@ export const AUTH_API_URL = `${API_URL_BASE}/auth`; */ export const ADMIN_API_URL = `${API_URL_BASE}/admin`; +// Cliente de API para endpoints de administración (requiere token) const adminApiClient = axios.create({ baseURL: ADMIN_API_URL, }); -// --- INTERCEPTORES --- +// Cliente de API para endpoints públicos (no envía token) +const apiClient = axios.create({ + baseURL: API_URL_BASE, + headers: { 'Content-Type': 'application/json' }, +}); -// Interceptor de Peticiones: Añade el token JWT a cada llamada + +// --- INTERCEPTORES (Solo para el cliente de admin) --- adminApiClient.interceptors.request.use( (config) => { const token = localStorage.getItem('admin-jwt-token'); @@ -39,7 +46,6 @@ adminApiClient.interceptors.request.use( (error) => Promise.reject(error) ); -// Interceptor de Respuestas: Maneja la expiración del token (error 401) adminApiClient.interceptors.response.use( (response) => response, (error) => { @@ -51,6 +57,32 @@ adminApiClient.interceptors.response.use( } ); + +// --- INTERFACES PARA COMPOSICIÓN NACIONAL (NECESARIAS PARA EL NUEVO MÉTODO) --- +export interface PartidoComposicionNacional { + id: string; + nombre: string; + nombreCorto: string | null; + color: string | null; + bancasFijos: number; + bancasGanadas: number; + bancasTotales: number; + ordenDiputadosNacionales: number | null; + ordenSenadoresNacionales: number | null; +} +export interface CamaraComposicionNacional { + camaraNombre: string; + totalBancas: number; + bancasEnJuego: number; + partidos: PartidoComposicionNacional[]; + presidenteBancada: { color: string | null; tipoBanca: 'ganada' | 'previa' | null } | null; +} +export interface ComposicionNacionalData { + diputados: CamaraComposicionNacional; + senadores: CamaraComposicionNacional; +} + + // --- SERVICIOS DE API --- // 1. Autenticación @@ -66,7 +98,7 @@ export const loginUser = async (credentials: LoginCredentials): Promise => { const response = await adminApiClient.get('/agrupaciones'); return response.data; @@ -77,14 +109,14 @@ export const updateAgrupacion = async (id: string, data: UpdateAgrupacionData): }; // 3. Ordenamiento de Agrupaciones -export const updateOrden = async (camara: 'diputados' | 'senadores', ids: string[]): Promise => { +export const updateOrden = async (camara: 'diputados' | 'senadores' | 'diputados-nacionales' | 'senadores-nacionales', ids: string[]): Promise => { await adminApiClient.put(`/agrupaciones/orden-${camara}`, ids); }; -// 4. Gestión de Bancas y Ocupantes -export const getBancadas = async (camara: 'diputados' | 'senadores'): Promise => { - const camaraId = camara === 'diputados' ? 0 : 1; - const response = await adminApiClient.get(`/bancadas/${camaraId}`); +// 4. Gestión de Bancas +export const getBancadas = async (camara: 'diputados' | 'senadores', eleccionId: number): Promise => { + const camaraId = (camara === 'diputados') ? 0 : 1; + const response = await adminApiClient.get(`/bancadas/${camaraId}?eleccionId=${eleccionId}`); return response.data; }; @@ -111,38 +143,52 @@ export const updateConfiguracion = async (data: Record): Promise await adminApiClient.put('/configuracion', data); }; -export const getLogos = async (): Promise => { - const response = await adminApiClient.get('/logos'); +// 6. Logos y Candidatos +export const getLogos = async (eleccionId: number): Promise => { + const response = await adminApiClient.get(`/logos?eleccionId=${eleccionId}`); return response.data; }; - export const updateLogos = async (data: LogoAgrupacionCategoria[]): Promise => { await adminApiClient.put('/logos', data); }; - -export const getMunicipiosForAdmin = async (): Promise => { - // Ahora usa adminApiClient, que apunta a /api/admin/ - // La URL final será /api/admin/catalogos/municipios - const response = await adminApiClient.get('/catalogos/municipios'); +export const getCandidatos = async (eleccionId: number): Promise => { + const response = await adminApiClient.get(`/candidatos?eleccionId=${eleccionId}`); return response.data; }; - -// 6. Overrides de Candidatos -export const getCandidatos = async (): Promise => { - const response = await adminApiClient.get('/candidatos'); - return response.data; -}; - export const updateCandidatos = async (data: CandidatoOverride[]): Promise => { await adminApiClient.put('/candidatos', data); }; -// 7. Gestión de Logging -export interface UpdateLoggingLevelData { - level: string; -} +// 7. Catálogos +export const getMunicipiosForAdmin = async (): Promise => { + const response = await adminApiClient.get('/catalogos/municipios'); + return response.data; +}; +// 8. Logging +export interface UpdateLoggingLevelData { level: string; } export const updateLoggingLevel = async (data: UpdateLoggingLevelData): Promise => { - // Este endpoint es específico, no es parte de la configuración general await adminApiClient.put(`/logging-level`, data); +}; + +// 9. Bancas Previas +export const getBancasPrevias = async (eleccionId: number): Promise => { + const response = await adminApiClient.get(`/bancas-previas/${eleccionId}`); + return response.data; +}; +export const updateBancasPrevias = async (eleccionId: number, data: BancaPrevia[]): Promise => { + await adminApiClient.put(`/bancas-previas/${eleccionId}`, data); +}; + +// 10. Obtener Composición Nacional (Endpoint Público) +export const getComposicionNacional = async (eleccionId: number): Promise => { + // Este es un endpoint público, por lo que usamos el cliente sin token de admin. + const response = await apiClient.get(`/elecciones/${eleccionId}/composicion-nacional`); + return response.data; +}; + +// Obtenemos las provincias para el selector de ámbito +export const getProvinciasForAdmin = async (): Promise => { +const response = await adminApiClient.get('/catalogos/provincias'); +return response.data; }; \ No newline at end of file diff --git a/Elecciones-Web/frontend-admin/src/types/index.ts b/Elecciones-Web/frontend-admin/src/types/index.ts index 37d98ff..4d8f00a 100644 --- a/Elecciones-Web/frontend-admin/src/types/index.ts +++ b/Elecciones-Web/frontend-admin/src/types/index.ts @@ -8,6 +8,9 @@ export interface AgrupacionPolitica { color: string | null; ordenDiputados: number | null; ordenSenadores: number | null; + // Añadimos los nuevos campos para el ordenamiento nacional + ordenDiputadosNacionales: number | null; + ordenSenadoresNacionales: number | null; } export interface UpdateAgrupacionData { @@ -30,9 +33,9 @@ export interface OcupanteBanca { periodo: string | null; } -// Nueva interfaz para la Bancada export interface Bancada { id: number; + eleccionId: number; // Clave para diferenciar provinciales de nacionales camara: TipoCamaraValue; numeroBanca: number; agrupacionPoliticaId: string | null; @@ -40,8 +43,20 @@ export interface Bancada { ocupante: OcupanteBanca | null; } +// Nueva interfaz para Bancas Previas +export interface BancaPrevia { + id: number; + eleccionId: number; + camara: TipoCamaraValue; + agrupacionPoliticaId: string; + agrupacionPolitica?: AgrupacionPolitica; // Opcional para la UI + cantidad: number; +} + + export interface LogoAgrupacionCategoria { id: number; + eleccionId: number; // Clave para diferenciar agrupacionPoliticaId: string; categoriaId: number; logoUrl: string | null; @@ -50,8 +65,11 @@ export interface LogoAgrupacionCategoria { export interface MunicipioSimple { id: string; nombre: string; } +export interface ProvinciaSimple { id: string; nombre: string; } + export interface CandidatoOverride { id: number; + eleccionId: number; // Clave para diferenciar agrupacionPoliticaId: string; categoriaId: number; ambitoGeograficoId: number | null; diff --git a/Elecciones-Web/frontend/package-lock.json b/Elecciones-Web/frontend/package-lock.json index a632f0c..9429789 100644 --- a/Elecciones-Web/frontend/package-lock.json +++ b/Elecciones-Web/frontend/package-lock.json @@ -16,6 +16,8 @@ "axios": "^1.11.0", "d3-geo": "^3.1.1", "d3-shape": "^3.2.0", + "highcharts": "^12.4.0", + "highcharts-react-official": "^3.2.2", "react": "^19.1.1", "react-circular-progressbar": "^2.2.0", "react-dom": "^19.1.1", @@ -24,7 +26,8 @@ "react-select": "^5.10.2", "react-simple-maps": "github:ozimmortal/react-simple-maps#feat/react-19-support", "react-tooltip": "^5.29.1", - "topojson-client": "^3.1.0" + "topojson-client": "^3.1.0", + "vite-plugin-svgr": "^4.5.0" }, "devDependencies": { "@eslint/js": "^9.33.0", @@ -47,7 +50,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -75,7 +77,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -85,7 +86,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -132,7 +132,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", @@ -171,7 +170,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -217,7 +215,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -227,7 +224,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", @@ -465,7 +461,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -482,7 +477,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -499,7 +493,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -516,7 +509,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -533,7 +525,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -550,7 +541,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -567,7 +557,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -584,7 +573,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -601,7 +589,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -618,7 +605,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -635,7 +621,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -652,7 +637,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -669,7 +653,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -686,7 +669,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -703,7 +685,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -720,7 +701,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -737,7 +717,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -754,7 +733,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -771,7 +749,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -788,7 +765,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -805,7 +781,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -822,7 +797,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -839,7 +813,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -856,7 +829,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -873,7 +845,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -890,7 +861,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1168,7 +1138,6 @@ "version": "0.3.11", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "dev": true, "license": "MIT", "optional": true, "peer": true, @@ -1720,6 +1689,40 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.47.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", @@ -1727,7 +1730,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1741,7 +1743,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1755,7 +1756,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1769,7 +1769,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1783,7 +1782,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1797,7 +1795,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1811,7 +1808,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1825,7 +1821,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1839,7 +1834,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1853,7 +1847,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1867,7 +1860,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1881,7 +1873,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1895,7 +1886,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1909,7 +1899,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1923,7 +1912,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1937,7 +1925,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1951,7 +1938,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1965,7 +1951,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1979,7 +1964,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1993,13 +1977,251 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, "node_modules/@tanstack/query-core": { "version": "5.85.5", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz", @@ -2147,7 +2369,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/geojson": { @@ -2167,7 +2388,6 @@ "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", - "dev": true, "license": "MIT", "optional": true, "peer": true, @@ -2536,7 +2756,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2592,7 +2812,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/asynckit": { @@ -2662,7 +2881,6 @@ "version": "4.25.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2695,7 +2913,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "license": "MIT", "optional": true, "peer": true @@ -2722,11 +2939,22 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001737", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2818,7 +3046,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/cosmiconfig": { @@ -3131,6 +3358,16 @@ "csstype": "^3.0.2" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3149,9 +3386,20 @@ "version": "1.5.208", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz", "integrity": "sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==", - "dev": true, "license": "ISC" }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3210,7 +3458,6 @@ "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -3252,7 +3499,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3438,6 +3684,12 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3619,7 +3871,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3643,7 +3894,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3780,6 +4030,22 @@ "node": ">= 0.4" } }, + "node_modules/highcharts": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-12.4.0.tgz", + "integrity": "sha512-o6UxxfChSUrvrZUbWrAuqL1HO/+exhAUPcZY6nnqLsadZQlnP16d082sg7DnXKZCk1gtfkyfkp6g3qkIZ9miZg==", + "license": "https://www.highcharts.com/license" + }, + "node_modules/highcharts-react-official": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.2.tgz", + "integrity": "sha512-2kkWOB6RpdR26fmAJkrtJFG9xWFUDGKWyat88tW3fa/3l/Jc7D5ZfwTng2MZsdiKIH32AFy0Pr75udUe7uN6LA==", + "license": "MIT", + "peerDependencies": { + "highcharts": ">=6.0.0", + "react": ">=16.8.0" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -3905,7 +4171,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -3957,7 +4222,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -4037,11 +4301,19 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -4165,7 +4437,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -4187,11 +4458,20 @@ "dev": true, "license": "MIT" }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, "license": "MIT" }, "node_modules/object-assign": { @@ -4353,7 +4633,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4643,7 +4922,6 @@ "version": "4.47.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -4713,7 +4991,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4742,6 +5019,16 @@ "node": ">=8" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -4755,7 +5042,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4765,7 +5051,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "license": "MIT", "optional": true, "peer": true, @@ -4778,7 +5063,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "optional": true, "peer": true, @@ -4830,11 +5114,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "license": "MIT" + }, "node_modules/terser": { "version": "5.43.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", - "dev": true, "license": "BSD-2-Clause", "optional": true, "peer": true, @@ -4855,7 +5144,6 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, "license": "MIT", "optional": true, "peer": true @@ -4870,7 +5158,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -4887,7 +5174,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -4905,7 +5191,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -4960,6 +5245,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4977,7 +5268,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -5015,7 +5306,6 @@ "version": "7.10.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "dev": true, "license": "MIT", "optional": true, "peer": true @@ -5024,7 +5314,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5091,7 +5380,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz", "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -5162,11 +5450,24 @@ } } }, + "node_modules/vite-plugin-svgr": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.5.0.tgz", + "integrity": "sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.2.0", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": ">=2.6.0" + } + }, "node_modules/vite/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -5184,7 +5485,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5232,14 +5532,12 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, "license": "ISC" }, "node_modules/yaml": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, "license": "ISC", "optional": true, "peer": true, diff --git a/Elecciones-Web/frontend/package.json b/Elecciones-Web/frontend/package.json index 69f42be..dd0604a 100644 --- a/Elecciones-Web/frontend/package.json +++ b/Elecciones-Web/frontend/package.json @@ -18,6 +18,8 @@ "axios": "^1.11.0", "d3-geo": "^3.1.1", "d3-shape": "^3.2.0", + "highcharts": "^12.4.0", + "highcharts-react-official": "^3.2.2", "react": "^19.1.1", "react-circular-progressbar": "^2.2.0", "react-dom": "^19.1.1", @@ -26,7 +28,8 @@ "react-select": "^5.10.2", "react-simple-maps": "github:ozimmortal/react-simple-maps#feat/react-19-support", "react-tooltip": "^5.29.1", - "topojson-client": "^3.1.0" + "topojson-client": "^3.1.0", + "vite-plugin-svgr": "^4.5.0" }, "devDependencies": { "@eslint/js": "^9.33.0", diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/buenos_aires.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/buenos_aires.svg new file mode 100644 index 0000000..e359a4a --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/buenos_aires.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/catamarca.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/catamarca.svg new file mode 100644 index 0000000..976bc9f --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/catamarca.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/chaco.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/chaco.svg new file mode 100644 index 0000000..43010a8 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/chaco.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/chubut.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/chubut.svg new file mode 100644 index 0000000..b33be97 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/chubut.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/ciudad_autonoma_de_buenos_aires.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/ciudad_autonoma_de_buenos_aires.svg new file mode 100644 index 0000000..1b67781 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/ciudad_autonoma_de_buenos_aires.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/cordoba.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/cordoba.svg new file mode 100644 index 0000000..3cbf034 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/cordoba.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/corrientes.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/corrientes.svg new file mode 100644 index 0000000..3b687b9 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/corrientes.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/entre_rios.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/entre_rios.svg new file mode 100644 index 0000000..fcd09f8 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/entre_rios.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/formosa.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/formosa.svg new file mode 100644 index 0000000..50559e8 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/formosa.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/jujuy.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/jujuy.svg new file mode 100644 index 0000000..b6ae595 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/jujuy.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/la_pampa.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/la_pampa.svg new file mode 100644 index 0000000..a311019 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/la_pampa.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/la_rioja.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/la_rioja.svg new file mode 100644 index 0000000..17edcb1 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/la_rioja.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/mendoza.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/mendoza.svg new file mode 100644 index 0000000..81660b1 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/mendoza.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/misiones.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/misiones.svg new file mode 100644 index 0000000..1be081b --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/misiones.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/neuquen.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/neuquen.svg new file mode 100644 index 0000000..ddb6733 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/neuquen.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/rio_negro.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/rio_negro.svg new file mode 100644 index 0000000..bc27fe1 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/rio_negro.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/salta.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/salta.svg new file mode 100644 index 0000000..9677ec1 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/salta.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/san_juan.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/san_juan.svg new file mode 100644 index 0000000..4e9f49c --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/san_juan.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/san_luis.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/san_luis.svg new file mode 100644 index 0000000..e1b7ec2 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/san_luis.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/santa_cruz.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/santa_cruz.svg new file mode 100644 index 0000000..d6d482b --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/santa_cruz.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/santa_fe.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/santa_fe.svg new file mode 100644 index 0000000..d7220bb --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/santa_fe.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/santiago_del_estero.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/santiago_del_estero.svg new file mode 100644 index 0000000..2d47949 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/santiago_del_estero.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/tierra_del_fuego.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/tierra_del_fuego.svg new file mode 100644 index 0000000..6318036 --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/tierra_del_fuego.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/public/maps/provincias-svg/tucuman.svg b/Elecciones-Web/frontend/public/maps/provincias-svg/tucuman.svg new file mode 100644 index 0000000..0b3cf6d --- /dev/null +++ b/Elecciones-Web/frontend/public/maps/provincias-svg/tucuman.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Elecciones-Web/frontend/src/apiService.ts b/Elecciones-Web/frontend/src/apiService.ts index b7a58ea..28e6e7c 100644 --- a/Elecciones-Web/frontend/src/apiService.ts +++ b/Elecciones-Web/frontend/src/apiService.ts @@ -1,6 +1,9 @@ // src/apiService.ts import axios from 'axios'; -import type { ApiResponseRankingMunicipio, ApiResponseRankingSeccion, ApiResponseTablaDetallada, ProyeccionBancas, MunicipioSimple, TelegramaData, CatalogoItem, CategoriaResumen, ResultadoTicker, ApiResponseResultadosPorSeccion, PanelElectoralDto } from './types/types'; +import type { ApiResponseRankingMunicipio, ApiResponseRankingSeccion, + ApiResponseTablaDetallada, ProyeccionBancas, MunicipioSimple, + TelegramaData, CatalogoItem, CategoriaResumen, ResultadoTicker, + ApiResponseResultadosPorSeccion, PanelElectoralDto, ResumenProvincia } from './types/types'; /** * URL base para las llamadas a la API. @@ -84,6 +87,32 @@ export interface ResultadoDetalleSeccion { color: string | null; } +export interface PartidoComposicionNacional { + id: string; + nombre: string; + nombreCorto: string | null; + color: string | null; + bancasFijos: number; + bancasGanadas: number; + bancasTotales: number; + ordenDiputadosNacionales: number | null; + ordenSenadoresNacionales: number | null; +} + +export interface CamaraComposicionNacional { + camaraNombre: string; + totalBancas: number; + bancasEnJuego: number; + partidos: PartidoComposicionNacional[]; + presidenteBancada: { color: string | null; tipoBanca: 'ganada' | 'previa' | null } | null; + ultimaActualizacion: string; +} + +export interface ComposicionNacionalData { + diputados: CamaraComposicionNacional; + senadores: CamaraComposicionNacional; +} + export const getResumenProvincial = async (eleccionId: number): Promise => { const response = await apiClient.get(`/elecciones/${eleccionId}/provincia/02`); return response.data; @@ -221,4 +250,16 @@ export const getPanelElectoral = async (eleccionId: number, ambitoId: string | n const { data } = await apiClient.get(url); return data; +}; + +export const getComposicionNacional = async (eleccionId: number): Promise => { + const { data } = await apiClient.get(`/elecciones/${eleccionId}/composicion-nacional`); + return data; +}; + +// 11. Endpoint para el widget de tarjetas nacionales +export const getResumenPorProvincia = async (eleccionId: number): Promise => { + // Usamos el cliente público ya que son datos de resultados + const { data } = await apiClient.get(`/elecciones/${eleccionId}/resumen-por-provincia`); + return data; }; \ No newline at end of file diff --git a/Elecciones-Web/frontend/src/components/common/DiputadosNacionalesLayout.tsx b/Elecciones-Web/frontend/src/components/common/DiputadosNacionalesLayout.tsx new file mode 100644 index 0000000..87e7bb8 --- /dev/null +++ b/Elecciones-Web/frontend/src/components/common/DiputadosNacionalesLayout.tsx @@ -0,0 +1,339 @@ +// src/components/common/DiputadosNacionalesLayout.tsx +import React from 'react'; +import type { PartidoComposicionNacional } from '../../apiService'; + +// --- Interfaces Actualizadas --- +interface DiputadosNacionalesLayoutProps { + partyData: PartidoComposicionNacional[]; + size?: number; + presidenteBancada?: { color: string | null } | null; // <-- Nueva Prop +} + +const PRESIDENTE_SEAT_INDEX = 0; // El escaño 'seat-0' es el del presidente + +export const DiputadosNacionalesLayout: React.FC = ({ + partyData, + size = 800, + presidenteBancada, // <-- Recibimos la nueva prop +}) => { + // --- ARRAY DE 257 ELEMENTOS ORDENADOS POR ID DE "seat-X" --- + const seatElements = [ + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ]; + + let seatIndex = 1; // Empezamos a contar desde 1, ya que el 0 es presidencial + + return ( + + + {/* Renderizamos el escaño presidencial primero y por separado */} + {presidenteBancada && React.cloneElement(seatElements[PRESIDENTE_SEAT_INDEX], { + fill: presidenteBancada.color || '#A9A9A9', + strokeWidth: 0.5, + })} + {partyData.map(partido => { + // Por cada partido, creamos un array combinado de sus escaños + const partySeats = [ + ...Array(partido.bancasFijos).fill({ isNew: false }), + ...Array(partido.bancasGanadas).fill({ isNew: true }) + ]; + + return ( + // Envolvemos todos los escaños de un partido en un + + {partySeats.map((seatInfo, i) => { + // Si ya no hay más plantillas de escaños, no renderizamos nada + if (seatIndex >= seatElements.length) return null; + + const template = seatElements[seatIndex]; + seatIndex++; // Incrementamos el contador para el siguiente escaño + + // Clonamos la plantilla con el estilo apropiado + return React.cloneElement(template, { + key: `${partido.id}-${i}`, + className: 'seat-circle', + fill: partido.color || '#808080', + fillOpacity: seatInfo.isNew ? 1 : 0.3, // Opacidad para bancas previas + stroke: partido.color || '#808080', + strokeWidth: 0.5, + }); + })} + + ); + })} + {/* Renderizamos los escaños vacíos sobrantes */} + {seatIndex < seatElements.length && + seatElements.slice(seatIndex).map((template, i) => + React.cloneElement(template, { + key: `empty-${i}`, + fill: '#E0E0E0', + stroke: '#ffffff', + strokeWidth: 0.5 + }) + ) + } + + + ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend/src/components/common/SenadoresNacionalesLayout.tsx b/Elecciones-Web/frontend/src/components/common/SenadoresNacionalesLayout.tsx new file mode 100644 index 0000000..b665856 --- /dev/null +++ b/Elecciones-Web/frontend/src/components/common/SenadoresNacionalesLayout.tsx @@ -0,0 +1,154 @@ +// src/components/common/SenadoresNacionalesLayout.tsx +import React from 'react'; +import type { PartidoComposicionNacional } from '../../apiService'; + +// Interfaces +interface SenadoresNacionalesLayoutProps { + partyData: PartidoComposicionNacional[]; + size?: number; + presidenteBancada?: { color: string | null } | null; +} + +const PRESIDENTE_SEAT_INDEX = 0; + +export const SenadoresNacionalesLayout: React.FC = ({ + partyData, + size = 800, + presidenteBancada, +}) => { + // --- ARRAY DE 73 ELEMENTOS ORDENADOS POR ID DE "seat-X" --- + // El asiento 0 es el presidencial, los 72 restantes son los senadores. + const seatElements = [ + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ]; + + let seatIndex = 1; // Empezamos desde 1 porque el 0 es para el presidente + + return ( + + + {/* Renderizamos primero el escaño del presidente por separado */} + {presidenteBancada && React.cloneElement(seatElements[PRESIDENTE_SEAT_INDEX], { + fill: presidenteBancada.color || '#A9A9A9', + strokeWidth: 0.5, + })} + + {/* Mapeamos los partidos para crear los bloques */} + {partyData.map(partido => { + const partySeats = [ + ...Array(partido.bancasFijos).fill({ isNew: false }), + ...Array(partido.bancasGanadas).fill({ isNew: true }) + ]; + + return ( + + {partySeats.map((seatInfo, i) => { + if (seatIndex >= seatElements.length) return null; + + const template = seatElements[seatIndex]; + seatIndex++; + + return React.cloneElement(template, { + key: `${partido.id}-${i}`, + className: 'seat-circle', + fill: partido.color || '#808080', + fillOpacity: seatInfo.isNew ? 1 : 0.3, + stroke: partido.color || '#808080', + strokeWidth: 0.5, + }); + })} + + ); + })} + {/* Renderizamos escaños vacíos si sobran */} + {seatIndex < seatElements.length && + seatElements.slice(seatIndex).map((template, i) => + React.cloneElement(template, { + key: `empty-${i}`, + fill: '#E0E0E0', + stroke: '#ffffff', + strokeWidth: 0.5 + }) + ) + } + + + ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend/src/features/legislativas/DevAppLegislativas.tsx b/Elecciones-Web/frontend/src/features/legislativas/DevAppLegislativas.tsx index 9a3512f..207f7cc 100644 --- a/Elecciones-Web/frontend/src/features/legislativas/DevAppLegislativas.tsx +++ b/Elecciones-Web/frontend/src/features/legislativas/DevAppLegislativas.tsx @@ -1,4 +1,6 @@ // src/features/legislativas/rovinciales/DevAppLegislativas.tsx +import { ResultadosNacionalesCardsWidget } from './nacionales/ResultadosNacionalesCardsWidget'; +import { CongresoNacionalWidget } from './nacionales/CongresoNacionalWidget'; import { PanelNacionalWidget } from './nacionales/PanelNacionalWidget'; import './DevAppStyle.css' @@ -6,9 +8,8 @@ export const DevAppLegislativas = () => { return (

Visor de Widgets

- - {/* Le pasamos el ID de la elección que queremos visualizar. - Para tus datos de prueba provinciales, este ID es 1. */} + +
); diff --git a/Elecciones-Web/frontend/src/features/legislativas/nacionales/CongresoNacionalWidget.tsx b/Elecciones-Web/frontend/src/features/legislativas/nacionales/CongresoNacionalWidget.tsx new file mode 100644 index 0000000..514af51 --- /dev/null +++ b/Elecciones-Web/frontend/src/features/legislativas/nacionales/CongresoNacionalWidget.tsx @@ -0,0 +1,162 @@ +// src/features/legislativas/nacionales/CongresoNacionalWidget.tsx +import { useState, Suspense, useMemo } from 'react'; +import { useSuspenseQuery } from '@tanstack/react-query'; +import { Tooltip } from 'react-tooltip'; +import { DiputadosNacionalesLayout } from '../../../components/common/DiputadosNacionalesLayout'; +import { SenadoresNacionalesLayout } from '../../../components/common/SenadoresNacionalesLayout'; +import { getComposicionNacional, type ComposicionNacionalData, type PartidoComposicionNacional } from '../../../apiService'; +import '../provinciales/CongresoWidget.css'; + +interface CongresoNacionalWidgetProps { + eleccionId: number; +} + +const formatTimestamp = (dateString: string) => { + if (!dateString) return '...'; + const date = new Date(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}`; +}; + +const WidgetContent = ({ eleccionId }: CongresoNacionalWidgetProps) => { + const [camaraActiva, setCamaraActiva] = useState<'diputados' | 'senadores'>('diputados'); + const [isHovering, setIsHovering] = useState(false); + + const { data } = useSuspenseQuery({ + queryKey: ['composicionNacional', eleccionId], + queryFn: () => getComposicionNacional(eleccionId), + refetchInterval: 30000, + }); + + const datosCamaraActual = data[camaraActiva]; + + const partidosOrdenados = useMemo(() => { + if (!datosCamaraActual?.partidos) return []; + const partidosACopiar = [...datosCamaraActual.partidos]; + partidosACopiar.sort((a, b) => { + const ordenA = camaraActiva === 'diputados' ? a.ordenDiputadosNacionales : a.ordenSenadoresNacionales; + const ordenB = camaraActiva === 'diputados' ? b.ordenDiputadosNacionales : b.ordenSenadoresNacionales; + return (ordenA ?? 999) - (ordenB ?? 999); + }); + return partidosACopiar; + }, [datosCamaraActual, camaraActiva]); + + const partyDataParaLayout = useMemo(() => { + if (camaraActiva === 'senadores') return partidosOrdenados; + if (!partidosOrdenados || !datosCamaraActual.presidenteBancada?.color) return partidosOrdenados; + const partidoPresidente = partidosOrdenados.find(p => p.color === datosCamaraActual.presidenteBancada!.color); + if (!partidoPresidente) return partidosOrdenados; + + const adjustedPartyData = JSON.parse(JSON.stringify(partidosOrdenados)); + const partidoAjustar = adjustedPartyData.find((p: PartidoComposicionNacional) => p.id === partidoPresidente.id); + + if (partidoAjustar) { + const tipoBanca = datosCamaraActual.presidenteBancada.tipoBanca; + if (tipoBanca === 'ganada' && partidoAjustar.bancasGanadas > 0) { + partidoAjustar.bancasGanadas -= 1; + } else if (tipoBanca === 'previa' && partidoAjustar.bancasFijos > 0) { + partidoAjustar.bancasFijos -= 1; + } else { + if (partidoAjustar.bancasGanadas > 0) { + partidoAjustar.bancasGanadas -= 1; + } else if (partidoAjustar.bancasFijos > 0) { + partidoAjustar.bancasFijos -= 1; + } + } + } + return adjustedPartyData; + }, [partidosOrdenados, datosCamaraActual.presidenteBancada, camaraActiva]); + + return ( +
+
+
setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + > + {camaraActiva === 'diputados' ? + : + + } +
+
+
+
+ {/* Usamos la nueva clase CSS para el círculo sólido */} + + Bancas en juego +
+
+ {/* Reemplazamos el SVG por un span con la nueva clase para el anillo */} + + Bancas previas +
+
+
+ Última Actualización: {formatTimestamp(datosCamaraActual.ultimaActualizacion)} +
+
+
+
+
+ + +
+

{datosCamaraActual.camaraNombre}

+
+ Total de Bancas + {datosCamaraActual.totalBancas} +
+
+ Bancas en Juego + {datosCamaraActual.bancasEnJuego} +
+
+
+
    + {partidosOrdenados + .filter(p => p.bancasTotales > 0) + .map((partido: PartidoComposicionNacional) => ( +
  • + + {partido.nombreCorto || partido.nombre} + + {partido.bancasTotales} + +
  • + ))} +
+
+
+ +
+ ); +}; + +export const CongresoNacionalWidget = ({ eleccionId }: CongresoNacionalWidgetProps) => { + return ( + Cargando composición del congreso...
}> + + + ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend/src/features/legislativas/nacionales/PanelNacional.css b/Elecciones-Web/frontend/src/features/legislativas/nacionales/PanelNacional.css index 837a2b0..bb99c27 100644 --- a/Elecciones-Web/frontend/src/features/legislativas/nacionales/PanelNacional.css +++ b/Elecciones-Web/frontend/src/features/legislativas/nacionales/PanelNacional.css @@ -1,10 +1,11 @@ -/* src/features/legislativas/nacionales/PanelNaciona.css */ +/* src/features/legislativas/nacionales/PanelNacional.css */ .panel-nacional-container { font-family: 'Roboto', sans-serif; max-width: 1200px; margin: auto; border: 1px solid #e0e0e0; border-radius: 8px; + position: relative; } .panel-header { @@ -491,13 +492,11 @@ /* --- NUEVOS ESTILOS PARA EL TOGGLE MÓVIL --- */ .mobile-view-toggle { display: none; - /* Oculto por defecto */ - position: fixed; - bottom: 20px; + position: absolute; /* <-- CAMBIO: De 'fixed' a 'absolute' */ + bottom: 10px; /* <-- AJUSTE: Menos espacio desde abajo */ left: 50%; transform: translateX(-50%); z-index: 100; - background-color: rgba(255, 255, 255, 0.9); border-radius: 30px; padding: 5px; diff --git a/Elecciones-Web/frontend/src/features/legislativas/nacionales/ResultadosNacionalesCardsWidget.css b/Elecciones-Web/frontend/src/features/legislativas/nacionales/ResultadosNacionalesCardsWidget.css new file mode 100644 index 0000000..1090611 --- /dev/null +++ b/Elecciones-Web/frontend/src/features/legislativas/nacionales/ResultadosNacionalesCardsWidget.css @@ -0,0 +1,259 @@ +/* src/features/legislativas/nacionales/ResultadosNacionalesCardsWidget.css */ + +/* --- Variables de Diseño --- */ +:root { + --card-border-color: #e0e0e0; + --card-bg-color: #ffffff; + --card-header-bg-color: #f8f9fa; + --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + --text-primary: #212529; + --text-secondary: #6c757d; + --font-family: "Public Sans", system-ui, sans-serif; + --primary-accent-color: #007bff; +} + +/* --- Contenedor Principal del Widget --- */ +.cards-widget-container { + font-family: var(--font-family); + width: 100%; + max-width: 1200px; + margin: 2rem auto; +} + +.cards-widget-container h2 { + font-size: 1.75rem; + color: var(--text-primary); + margin-bottom: 1.5rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--card-border-color); +} + +/* --- Grilla de Tarjetas --- */ +.cards-grid { + display: grid; + /* Crea columnas flexibles que se ajustan al espacio disponible */ + grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); + gap: 1.5rem; +} + +/* --- Tarjeta Individual --- */ +.provincia-card { + background-color: var(--card-bg-color); + border: 1px solid var(--card-border-color); + border-radius: 8px; + box-shadow: var(--card-shadow); + display: flex; + flex-direction: column; + overflow: hidden; /* Asegura que los bordes redondeados se apliquen al contenido */ +} + +/* --- Cabecera de la Tarjeta --- */ +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + background-color: var(--card-header-bg-color); + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--card-border-color); +} + +.header-info h3 { + margin: 0; + font-size: 1.2rem; + font-weight: 700; + color: var(--text-primary); +} + +.header-info span { + font-size: 0.8rem; + color: var(--text-secondary); + text-transform: uppercase; +} + +.header-map { + width: 90px; + height: 90px; + flex-shrink: 0; + border-radius: 4px; + overflow: hidden; + background-color: #e9ecef; + padding: 0.25rem; + box-sizing: border-box; /* Para que el padding no aumente el tamaño total */ +} + +/* Contenedor del SVG para asegurar que se ajuste al espacio */ +.map-svg-container, .map-placeholder { + width: 100%; + height: 100%; +} + +/* Estilo para el SVG renderizado */ +.map-svg-container svg { + width: 100%; + height: 100%; + object-fit: contain; /* Asegura que el mapa no se deforme */ +} + +/* Placeholder para cuando el mapa no carga */ +.map-placeholder.error { + background-color: #f8d7da; /* Un color de fondo rojizo para indicar un error */ +} + +/* --- Cuerpo de la Tarjeta --- */ +.card-body { + padding: 0.5rem 1rem; +} + +.candidato-row { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 0; + border-bottom: 1px solid #f0f0f0; +} + +.candidato-row:last-child { + border-bottom: none; +} + +.candidato-foto { + width: 45px; + height: 45px; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; +} + +.candidato-data { + flex-grow: 1; + min-width: 0; /* Permite que el texto se trunque si es necesario */ + margin-right: 0.5rem; +} + +.candidato-nombre { + font-weight: 700; + font-size: 0.95rem; + color: var(--text-primary); + display: block; +} + +.candidato-partido { + font-size: 0.75rem; + color: var(--text-secondary); + text-transform: uppercase; + display: block; + margin-bottom: 0.3rem; +} + +.progress-bar-container { + height: 6px; + background-color: #e9ecef; + border-radius: 3px; + overflow: hidden; +} + +.progress-bar { + height: 100%; + border-radius: 3px; + transition: width 0.5s ease-out; +} + +.candidato-stats { + display: flex; + flex-direction: column; + align-items: flex-end; + text-align: right; + flex-shrink: 0; + padding-left: 0.5rem; +} + +.stats-percent { + font-weight: 700; + font-size: 1.1rem; + color: var(--text-primary); +} + +.stats-votos { + font-size: 0.8rem; + color: var(--text-secondary); +} + +.stats-bancas { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex-shrink: 0; + border: 1px solid var(--card-border-color); + border-radius: 6px; + padding: 0.25rem 0.5rem; + margin-left: 0.75rem; + font-weight: 700; + font-size: 1.2rem; + color: var(--primary-accent-color); + min-width: 50px; +} + +.stats-bancas span { + font-size: 0.65rem; + font-weight: 500; + color: var(--text-secondary); + text-transform: uppercase; + margin-top: -4px; +} + + +/* --- Pie de la Tarjeta --- */ +.card-footer { + display: grid; + grid-template-columns: repeat(3, 1fr); + background-color: var(--card-header-bg-color); + border-top: 1px solid var(--card-border-color); + padding: 0.75rem 0; + text-align: center; +} + +.card-footer div { + border-right: 1px solid var(--card-border-color); +} + +.card-footer div:last-child { + border-right: none; +} + +.card-footer span { + display: block; + font-size: 0.75rem; + color: var(--text-secondary); +} + +.card-footer strong { + font-size: 1rem; + font-weight: 700; + color: var(--text-primary); +} + +/* --- Media Query para Móvil --- */ +@media (max-width: 480px) { + .cards-grid { + /* En pantallas muy pequeñas, forzamos una sola columna */ + grid-template-columns: 1fr; + } + + .card-header { + padding: 0.5rem; + } + + .header-info h3 { + font-size: 1rem; + } +} + +/* --- NUEVOS ESTILOS PARA EL NOMBRE DEL PARTIDO CUANDO ES EL TÍTULO PRINCIPAL --- */ +.candidato-partido.main-title { + font-size: 0.95rem; /* Hacemos la fuente más grande */ + font-weight: 700; /* La ponemos en negrita, como el nombre del candidato */ + color: var(--text-primary); /* Usamos el color de texto principal */ + text-transform: none; /* Quitamos el 'uppercase' para que se lea mejor */ + margin-bottom: 0.3rem; +} \ No newline at end of file diff --git a/Elecciones-Web/frontend/src/features/legislativas/nacionales/ResultadosNacionalesCardsWidget.tsx b/Elecciones-Web/frontend/src/features/legislativas/nacionales/ResultadosNacionalesCardsWidget.tsx new file mode 100644 index 0000000..6bb4b53 --- /dev/null +++ b/Elecciones-Web/frontend/src/features/legislativas/nacionales/ResultadosNacionalesCardsWidget.tsx @@ -0,0 +1,30 @@ +// src/features/legislativas/nacionales/ResultadosNacionalesCardsWidget.tsx +import { useQuery } from '@tanstack/react-query'; +import { getResumenPorProvincia } from '../../../apiService'; +import { ProvinciaCard } from './components/ProvinciaCard'; +import './ResultadosNacionalesCardsWidget.css'; + +interface Props { + eleccionId: number; +} + +export const ResultadosNacionalesCardsWidget = ({ eleccionId }: Props) => { + const { data, isLoading, error } = useQuery({ + queryKey: ['resumenPorProvincia', eleccionId], + queryFn: () => getResumenPorProvincia(eleccionId), + }); + + if (isLoading) return
Cargando resultados por provincia...
; + if (error) return
Error al cargar los datos.
; + + return ( +
+

Resultados elecciones nacionales 2025

+
+ {data?.map(provinciaData => ( + + ))} +
+
+ ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend/src/features/legislativas/nacionales/components/MiniMapaSvg.tsx b/Elecciones-Web/frontend/src/features/legislativas/nacionales/components/MiniMapaSvg.tsx new file mode 100644 index 0000000..04bf070 --- /dev/null +++ b/Elecciones-Web/frontend/src/features/legislativas/nacionales/components/MiniMapaSvg.tsx @@ -0,0 +1,64 @@ +// src/features/legislativas/nacionales/components/MiniMapaSvg.tsx +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; +import { useMemo } from 'react'; +import { assetBaseUrl } from '../../../../apiService'; + +interface MiniMapaSvgProps { + provinciaNombre: string; + fillColor: string; +} + +// Función para normalizar el nombre de la provincia y que coincida con el nombre del archivo SVG +const normalizarNombreParaUrl = (nombre: string) => + nombre + .toLowerCase() + .replace(/ /g, '_') // Reemplaza espacios con guiones bajos + .normalize("NFD") // Descompone acentos para eliminarlos en el siguiente paso + .replace(/[\u0300-\u036f]/g, ""); // Elimina los acentos + +export const MiniMapaSvg = ({ provinciaNombre, fillColor }: MiniMapaSvgProps) => { + const nombreNormalizado = normalizarNombreParaUrl(provinciaNombre); + // Asumimos que los SVGs están en /public/maps/provincias-svg/ + const mapFileUrl = `${assetBaseUrl}/maps/provincias-svg/${nombreNormalizado}.svg`; + + // Usamos React Query para fetchear el contenido del SVG como texto + const { data: svgContent, isLoading, isError } = useQuery({ + queryKey: ['svgMapa', nombreNormalizado], + queryFn: async () => { + const response = await axios.get(mapFileUrl, { responseType: 'text' }); + return response.data; + }, + staleTime: Infinity, // Estos archivos son estáticos y no cambian + gcTime: Infinity, + retry: false, // No reintentar si el archivo no existe + }); + + // Usamos useMemo para modificar el SVG solo cuando el contenido o el color cambian + const modifiedSvg = useMemo(() => { + if (!svgContent) return ''; + + // Usamos una expresión regular para encontrar todas las etiquetas + // y añadirles el atributo de relleno con el color del ganador. + // Esto sobrescribirá cualquier 'fill' que ya exista en la etiqueta. + return svgContent.replace(/; + } + + if (isError || !modifiedSvg) { + // Muestra un placeholder si el SVG no se encontró o está vacío + return
; + } + + // Renderizamos el SVG modificado. dangerouslySetInnerHTML es seguro aquí + // porque el contenido proviene de nuestros propios archivos SVG estáticos. + return ( +
+ ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend/src/features/legislativas/nacionales/components/ProvinciaCard.tsx b/Elecciones-Web/frontend/src/features/legislativas/nacionales/components/ProvinciaCard.tsx new file mode 100644 index 0000000..acf70e0 --- /dev/null +++ b/Elecciones-Web/frontend/src/features/legislativas/nacionales/components/ProvinciaCard.tsx @@ -0,0 +1,78 @@ +// src/features/legislativas/nacionales/components/ProvinciaCard.tsx +import type { ResumenProvincia } from '../../../../types/types'; +import { MiniMapaSvg } from './MiniMapaSvg'; +import { ImageWithFallback } from '../../../../components/common/ImageWithFallback'; +import { assetBaseUrl } from '../../../../apiService'; + +interface ProvinciaCardProps { + data: ResumenProvincia; +} + +const formatNumber = (num: number) => num.toLocaleString('es-AR'); +const formatPercent = (num: number) => `${num.toFixed(2).replace('.', ',')}%`; + +export const ProvinciaCard = ({ data }: ProvinciaCardProps) => { + // Determinamos el color del ganador para pasárselo al mapa. + // Si no hay ganador, usamos un color gris por defecto. + const colorGanador = data.resultados[0]?.color || '#d1d1d1'; + + return ( +
+
+
+

{data.provinciaNombre}

+ DIPUTADOS NACIONALES +
+
+ +
+
+
+ {data.resultados.map(res => ( +
+ + +
+ {res.nombreCandidato && ( + {res.nombreCandidato} + )} + + + {res.nombreAgrupacion} + + +
+
+
+
+ +
+ {formatPercent(res.porcentaje)} + {formatNumber(res.votos)} votos +
+
+ +{res.bancasObtenidas} + Bancas +
+
+ ))} +
+
+
+ Participación + {/* Usamos los datos reales del estado de recuento */} + {formatPercent(data.estadoRecuento?.participacionPorcentaje ?? 0)} +
+
+ Mesas escrutadas + {formatPercent(data.estadoRecuento?.mesasTotalizadasPorcentaje ?? 0)} +
+
+ Votos totales + {/* Usamos el nuevo campo cantidadVotantes */} + {formatNumber(data.estadoRecuento?.cantidadVotantes ?? 0)} +
+
+
+ ); +}; \ No newline at end of file diff --git a/Elecciones-Web/frontend/src/features/legislativas/provinciales/CongresoWidget.css b/Elecciones-Web/frontend/src/features/legislativas/provinciales/CongresoWidget.css index 78ebeb0..7fdd57b 100644 --- a/Elecciones-Web/frontend/src/features/legislativas/provinciales/CongresoWidget.css +++ b/Elecciones-Web/frontend/src/features/legislativas/provinciales/CongresoWidget.css @@ -1,27 +1,35 @@ /* src/features/legislativas/provinciales/CongresoWidget.css */ .congreso-container { display: flex; - /* Se reduce ligeramente el espacio entre el gráfico y el panel */ - gap: 1rem; + gap: 1.5rem; background-color: #ffffff; border: 1px solid #e0e0e0; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); padding: 1rem; border-radius: 8px; - max-width: 800px; + max-width: 900px; margin: 20px auto; font-family: "Public Sans", system-ui, sans-serif; color: #333333; - align-items: center; } .congreso-grafico { - /* --- CAMBIO PRINCIPAL: Se aumenta la proporción del gráfico --- */ - flex: 1 1 65%; + flex: 2; min-width: 300px; display: flex; + flex-direction: column; +} + +.congreso-hemiciclo-wrapper { + flex-grow: 1; + display: flex; align-items: center; justify-content: center; + width: 100%; +} + +.congreso-hemiciclo-wrapper.is-hovering .party-block:not(:hover) { + opacity: 0.4; } .congreso-grafico svg { @@ -30,35 +38,139 @@ animation: fadeIn 0.8s ease-in-out; } -@keyframes fadeIn { - from { - opacity: 0; - transform: scale(0.9); - } +/* --- NUEVOS ESTILOS PARA EL FOOTER DEL GRÁFICO --- */ +.congreso-footer { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 1rem 0 1rem; + margin-top: auto; /* Empuja el footer a la parte inferior del contenedor flex */ + font-size: 0.8em; + color: #666; + border-top: 1px solid #eee; +} - to { - opacity: 1; - transform: scale(1); - } +.footer-legend { + display: flex; + gap: 1.5rem; /* Espacio entre los items de la leyenda */ +} + +.footer-legend-item { + display: flex; + align-items: center; + gap: 0.5rem; /* Espacio entre el icono y el texto */ +} + +.footer-timestamp { + font-weight: 500; +} + +/* --- ESTILOS PARA HOVER --- */ + +/* Estilo base para cada círculo de escaño */ +.seat-circle { + transition: all 0.2s ease-in-out; +} + +.party-block { + cursor: pointer; + transition: opacity 0.2s ease-in-out; +} + +.party-block:hover .seat-circle { + stroke: #333 !important; /* Borde oscuro para resaltar */ + stroke-width: 1.5px !important; + stroke-opacity: 1; + filter: brightness(1.1); +} + +/* CORRECCIÓN: El selector ahora apunta al wrapper correcto */ +.congreso-hemiciclo-wrapper.is-hovering .party-block:not(:hover) { + opacity: 0.3; /* Hacemos el desvanecimiento más pronunciado */ +} +.congreso-grafico svg { + width: 100%; + height: auto; + animation: fadeIn 0.8s ease-in-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: scale(0.9); } + to { opacity: 1; transform: scale(1); } +} + +/* --- INICIO DE NUEVOS ESTILOS PARA EL FOOTER DEL GRÁFICO --- */ +.congreso-footer { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 0.5rem 0 0.5rem; + margin-top: auto; + font-size: 0.8em; + color: #666; + border-top: 1px solid #eee; +} + +.footer-legend { + display: flex; + gap: 1.25rem; + align-items: center; +} + +.footer-legend-item { + display: flex; + align-items: center; + gap: 0.6rem; + font-size: 1.1em; +} + +/* Creamos una clase base para ambos iconos para compartir tamaño */ +.legend-icon { + display: inline-block; + width: 14px; /* Tamaño base para ambos iconos */ + height: 14px; + border-radius: 50%; + box-sizing: border-box; +} + +/* Estilo para el icono de "Bancas en juego" (círculo sólido) */ +.legend-icon--solid { + background-color: #888; + border: 1px solid #777; +} + +/* Estilo para el icono de "Bancas previas" (anillo translúcido) */ +.legend-icon--ring { + background-color: rgba(136, 136, 136, 0.3); /* #888 con opacidad */ + border: 1px solid #888; /* Borde sólido del mismo color */ +} + +.footer-timestamp { + font-weight: 500; + font-size: 0.75em; } .congreso-summary { - /* --- CAMBIO PRINCIPAL: Se reduce la proporción del panel de datos --- */ - flex: 1 1 35%; + flex: 1; border-left: 1px solid #e0e0e0; - /* Se reduce el padding para dar aún más espacio al gráfico */ - padding-left: 1rem; + padding-left: 1.25rem; /* Un poco más de padding */ + display: flex; + flex-direction: column; + justify-content: flex-start; } .congreso-summary h3 { margin-top: 0; + margin-bottom: 0.75rem; /* Margen inferior reducido */ font-size: 1.4em; color: #212529; } .chamber-tabs { display: flex; - margin-bottom: 1.5rem; + margin-bottom: 1rem; /* Margen inferior reducido */ border: 1px solid #dee2e6; border-radius: 6px; overflow: hidden; @@ -66,7 +178,7 @@ .chamber-tabs button { flex: 1; - padding: 0.75rem 0.5rem; + padding: 0.5rem 0.5rem; border: none; background-color: #f8f9fa; color: #6c757d; @@ -94,7 +206,7 @@ display: flex; justify-content: space-between; align-items: baseline; - margin-bottom: 0.5rem; + margin-bottom: 0.25rem; /* Margen inferior muy reducido */ font-size: 1.1em; } @@ -107,7 +219,15 @@ .congreso-summary hr { border: none; border-top: 1px solid #e0e0e0; - margin: 1.5rem 0; + margin: 1rem 0; /* Margen vertical reducido */ +} + +/* Contenedor de la lista de partidos para aplicar el scroll */ +.partido-lista-container { + flex-grow: 1; /* Ocupa el espacio vertical disponible */ + overflow-y: auto; /* Muestra el scrollbar si es necesario */ + min-height: 0; /* Truco de Flexbox para que el scroll funcione */ + padding-right: 8px; /* Espacio para el scrollbar */ } .partido-lista { @@ -119,14 +239,14 @@ .partido-lista li { display: flex; align-items: center; - margin-bottom: 0.75rem; + margin-bottom: 0.85rem; /* Un poco más de espacio entre items */ } .partido-color-box { - width: 14px; - height: 14px; - border-radius: 3px; - margin-right: 10px; + width: 16px; /* Cuadro de color más grande */ + height: 16px; + border-radius: 4px; /* Un poco más cuadrado */ + margin-right: 12px; flex-shrink: 0; } @@ -139,19 +259,54 @@ font-size: 1.1em; } -/* --- Media Query para Responsividad Móvil --- */ +/* --- Media Query para Responsividad Móvil (HASTA 768px) --- */ @media (max-width: 768px) { .congreso-container { flex-direction: column; - padding: 1.5rem; + padding: 0.5rem; + height: auto; + max-height: none; } .congreso-summary { border-left: none; padding-left: 0; - margin-top: 2rem; border-top: 1px solid #e0e0e0; - padding-top: 1.5rem; + } + + .partido-lista-container { + overflow-y: visible; + max-height: none; + } + + .congreso-footer { + flex-direction: column; /* Apila la leyenda y el timestamp verticalmente */ + align-items: flex-start; /* Alinea todo a la izquierda */ + gap: 0.5rem; /* Añade un pequeño espacio entre la leyenda y el timestamp */ + padding: 0.75rem 0rem; /* Ajusta el padding para móvil */ + align-items: center; + } + + .footer-legend { + gap: 0.75rem; /* Reduce el espacio entre los items de la leyenda */ + } + + .footer-legend-item{ + font-size: 1em; + } + + .footer-timestamp { + font-size: 0.75em; /* Reduce el tamaño de la fuente para que quepa mejor */ + } +} + + +/* --- Media Query para Escritorio (DESDE 769px en adelante) --- */ +@media (min-width: 769px) { + .congreso-container { + flex-direction: row; + align-items: stretch; + height: 500px; } } diff --git a/Elecciones-Web/frontend/src/types/types.ts b/Elecciones-Web/frontend/src/types/types.ts index d5553ef..2a36896 100644 --- a/Elecciones-Web/frontend/src/types/types.ts +++ b/Elecciones-Web/frontend/src/types/types.ts @@ -245,4 +245,29 @@ export interface PanelElectoralDto { mapaData: ResultadoMapaDto[]; resultadosPanel: ResultadoTicker[]; // Reutilizamos el tipo que ya tienes estadoRecuento: EstadoRecuentoTicker; // Reutilizamos el tipo que ya tienes +} + +// --- TIPOS PARA EL WIDGET DE TARJETAS --- +export interface EstadoRecuentoDto { + participacionPorcentaje: number; + mesasTotalizadasPorcentaje: number; + cantidadVotantes: number; +} + +export interface ResultadoCandidato { + agrupacionId: string; + nombreCandidato: string | null; + nombreAgrupacion: string; + fotoUrl: string | null; + color: string | null; + porcentaje: number; + votos: number; + bancasObtenidas: number; +} + +export interface ResumenProvincia { + provinciaId: string; + provinciaNombre: string; + estadoRecuento: EstadoRecuentoDto | null; + resultados: ResultadoCandidato[]; } \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Api/Controllers/AdminController.cs b/Elecciones-Web/src/Elecciones.Api/Controllers/AdminController.cs index 2607c04..05f79da 100644 --- a/Elecciones-Web/src/Elecciones.Api/Controllers/AdminController.cs +++ b/Elecciones-Web/src/Elecciones.Api/Controllers/AdminController.cs @@ -92,6 +92,36 @@ public class AdminController : ControllerBase return Ok(); } + // --- ENDPOINTS PARA NACIONALES --- + + [HttpPut("agrupaciones/orden-diputados-nacionales")] + public async Task UpdateDiputadosNacionalesOrden([FromBody] List idsAgrupacionesOrdenadas) + { + await _dbContext.AgrupacionesPoliticas.ExecuteUpdateAsync(s => s.SetProperty(a => a.OrdenDiputadosNacionales, (int?)null)); + + for (int i = 0; i < idsAgrupacionesOrdenadas.Count; i++) + { + var agrupacion = await _dbContext.AgrupacionesPoliticas.FindAsync(idsAgrupacionesOrdenadas[i]); + if (agrupacion != null) agrupacion.OrdenDiputadosNacionales = i + 1; + } + await _dbContext.SaveChangesAsync(); + return Ok(); + } + + [HttpPut("agrupaciones/orden-senadores-nacionales")] + public async Task UpdateSenadoresNacionalesOrden([FromBody] List idsAgrupacionesOrdenadas) + { + await _dbContext.AgrupacionesPoliticas.ExecuteUpdateAsync(s => s.SetProperty(a => a.OrdenSenadoresNacionales, (int?)null)); + + for (int i = 0; i < idsAgrupacionesOrdenadas.Count; i++) + { + var agrupacion = await _dbContext.AgrupacionesPoliticas.FindAsync(idsAgrupacionesOrdenadas[i]); + if (agrupacion != null) agrupacion.OrdenSenadoresNacionales = i + 1; + } + await _dbContext.SaveChangesAsync(); + return Ok(); + } + // LEER todas las configuraciones [HttpGet("configuracion")] public async Task GetConfiguracion() @@ -125,14 +155,15 @@ public class AdminController : ControllerBase // LEER: Obtener todas las bancadas para una cámara, con su partido y ocupante actual [HttpGet("bancadas/{camara}")] - public async Task GetBancadas(TipoCamara camara) + public async Task GetBancadas(TipoCamara camara, [FromQuery] int eleccionId) { + // 3. La lógica interna se mantiene igual, ya que filtra por ambos parámetros. var bancadas = await _dbContext.Bancadas .AsNoTracking() .Include(b => b.AgrupacionPolitica) .Include(b => b.Ocupante) - .Where(b => b.Camara == camara) - .OrderBy(b => b.Id) // Ordenar por ID para consistencia + .Where(b => b.EleccionId == eleccionId && b.Camara == camara) + .OrderBy(b => b.NumeroBanca) .ToListAsync(); return Ok(bancadas); @@ -181,10 +212,39 @@ public class AdminController : ControllerBase return NoContent(); } - [HttpGet("logos")] - public async Task GetLogos() + [HttpGet("catalogos/provincias")] + public async Task GetProvinciasForAdmin() { - return Ok(await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync()); + var provincias = await _dbContext.AmbitosGeograficos + .AsNoTracking() + .Where(a => a.NivelId == 10) // Nivel 10 = Provincia + .OrderBy(a => a.Nombre) + .Select(a => new { Id = a.Id.ToString(), Nombre = a.Nombre }) + .ToListAsync(); + return Ok(provincias); + } + + // --- ENDPOINTS MODIFICADOS --- + + [HttpGet("logos")] + public async Task GetLogos([FromQuery] int eleccionId) + { + // Añadimos el filtro por EleccionId + return Ok(await _dbContext.LogosAgrupacionesCategorias + .AsNoTracking() + .Where(l => l.EleccionId == eleccionId) + .ToListAsync()); + } + + [HttpGet("candidatos")] + public async Task GetCandidatos([FromQuery] int eleccionId) + { + // Añadimos el filtro por EleccionId + var candidatos = await _dbContext.CandidatosOverrides + .AsNoTracking() + .Where(c => c.EleccionId == eleccionId) + .ToListAsync(); + return Ok(candidatos); } [HttpPut("logos")] @@ -239,18 +299,6 @@ public class AdminController : ControllerBase return Ok(municipios); } - /// - /// Obtiene todos los overrides de candidatos configurados. - /// - [HttpGet("candidatos")] - public async Task GetCandidatos() - { - var candidatos = await _dbContext.CandidatosOverrides - .AsNoTracking() - .ToListAsync(); - return Ok(candidatos); - } - /// /// Guarda (actualiza o crea) una lista de overrides de candidatos. /// @@ -337,4 +385,40 @@ public class AdminController : ControllerBase _logger.LogWarning("El nivel de logging ha sido cambiado a: {Level}", request.Level); return Ok(new { message = $"Nivel de logging actualizado a '{request.Level}'." }); } + + // LEER todas las bancas previas para una elección + [HttpGet("bancas-previas/{eleccionId}")] + public async Task GetBancasPrevias(int eleccionId) + { + var bancas = await _dbContext.BancasPrevias + .AsNoTracking() + .Where(b => b.EleccionId == eleccionId) + .Include(b => b.AgrupacionPolitica) + .ToListAsync(); + return Ok(bancas); + } + + // GUARDAR (Upsert) una lista de bancas previas + [HttpPut("bancas-previas/{eleccionId}")] + public async Task UpdateBancasPrevias(int eleccionId, [FromBody] List bancas) + { + // Borramos los registros existentes para esta elección para simplificar la lógica + await _dbContext.BancasPrevias.Where(b => b.EleccionId == eleccionId).ExecuteDeleteAsync(); + + // Añadimos los nuevos registros que tienen al menos una banca + foreach (var banca in bancas.Where(b => b.Cantidad > 0)) + { + _dbContext.BancasPrevias.Add(new BancaPrevia + { + EleccionId = eleccionId, + Camara = banca.Camara, + AgrupacionPoliticaId = banca.AgrupacionPoliticaId, + Cantidad = banca.Cantidad + }); + } + + await _dbContext.SaveChangesAsync(); + _logger.LogInformation("Se actualizaron las bancas previas para la EleccionId: {EleccionId}", eleccionId); + return NoContent(); + } } \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Api/Controllers/ResultadosController.cs b/Elecciones-Web/src/Elecciones.Api/Controllers/ResultadosController.cs index 0a13795..a77f3f8 100644 --- a/Elecciones-Web/src/Elecciones.Api/Controllers/ResultadosController.cs +++ b/Elecciones-Web/src/Elecciones.Api/Controllers/ResultadosController.cs @@ -1208,13 +1208,16 @@ public class ResultadosController : ControllerBase [HttpGet("mapa-resultados")] public async Task GetResultadosMapaPorMunicipio( - [FromRoute] int eleccionId, - [FromQuery] int categoriaId, - [FromQuery] string? distritoId = null) +[FromRoute] int eleccionId, +[FromQuery] int categoriaId, +[FromQuery] string? distritoId = null) { if (string.IsNullOrEmpty(distritoId)) { - // --- VISTA NACIONAL (Ya corregida y funcionando) --- + // --- VISTA NACIONAL (LÓGICA CORRECTA Y ROBUSTA) --- + + // PASO 1: Agrupar y sumar los votos por provincia y partido directamente en la BD. + // Esto crea una lista con los totales, que es mucho más pequeña que los datos crudos. var votosAgregadosPorProvincia = await _dbContext.ResultadosVotos .AsNoTracking() .Where(r => r.EleccionId == eleccionId @@ -1224,23 +1227,26 @@ public class ResultadosController : ControllerBase .GroupBy(r => new { r.AmbitoGeografico.DistritoId, r.AgrupacionPoliticaId }) .Select(g => new { - g.Key.DistritoId, - g.Key.AgrupacionPoliticaId, + DistritoId = g.Key.DistritoId!, // Sabemos que no es nulo por el .Where() + AgrupacionPoliticaId = g.Key.AgrupacionPoliticaId, TotalVotos = g.Sum(r => r.CantidadVotos) }) .ToListAsync(); - var agrupacionesInfo = await _dbContext.AgrupacionesPoliticas.AsNoTracking().ToDictionaryAsync(a => a.Id); - var provinciasInfo = await _dbContext.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 10).ToListAsync(); - + // PASO 2: Encontrar el ganador para cada provincia en la memoria de la aplicación. + // Esto es muy rápido porque se hace sobre la lista ya agregada. var ganadoresPorProvincia = votosAgregadosPorProvincia .GroupBy(r => r.DistritoId) .Select(g => g.OrderByDescending(x => x.TotalVotos).First()) .ToList(); + // PASO 3: Obtener los datos adicionales (nombres, colores) para construir la respuesta final. + var agrupacionesInfo = await _dbContext.AgrupacionesPoliticas.AsNoTracking().ToDictionaryAsync(a => a.Id); + var provinciasInfo = await _dbContext.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 10).ToListAsync(); + var mapaDataNacional = ganadoresPorProvincia.Select(g => new ResultadoMapaDto { - AmbitoId = g.DistritoId!, + AmbitoId = g.DistritoId, AmbitoNombre = provinciasInfo.FirstOrDefault(p => p.DistritoId == g.DistritoId)?.Nombre ?? "Desconocido", AgrupacionGanadoraId = g.AgrupacionPoliticaId, ColorGanador = agrupacionesInfo.GetValueOrDefault(g.AgrupacionPoliticaId)?.Color ?? "#808080" @@ -1250,16 +1256,13 @@ public class ResultadosController : ControllerBase } else { - // --- VISTA PROVINCIAL (AHORA CORREGIDA CON LA MISMA LÓGICA) --- - - // PASO 1: Agrupar por IDs y sumar votos en la base de datos. + // --- VISTA PROVINCIAL (SIN CAMBIOS, YA ERA EFICIENTE) --- var votosAgregadosPorMunicipio = await _dbContext.ResultadosVotos .AsNoTracking() .Where(r => r.EleccionId == eleccionId && r.CategoriaId == categoriaId && r.AmbitoGeografico.DistritoId == distritoId && r.AmbitoGeografico.NivelId == 30) - // Agrupamos por los IDs (int y string) .GroupBy(r => new { r.AmbitoGeograficoId, r.AgrupacionPoliticaId }) .Select(g => new { @@ -1269,13 +1272,11 @@ public class ResultadosController : ControllerBase }) .ToListAsync(); - // PASO 2: Encontrar el ganador para cada municipio en memoria. var ganadoresPorMunicipio = votosAgregadosPorMunicipio .GroupBy(r => r.AmbitoGeograficoId) .Select(g => g.OrderByDescending(x => x.TotalVotos).First()) .ToList(); - // PASO 3: Hidratar con los nombres y colores (muy rápido). var idsMunicipios = ganadoresPorMunicipio.Select(g => g.AmbitoGeograficoId).ToList(); var idsAgrupaciones = ganadoresPorMunicipio.Select(g => g.AgrupacionPoliticaId).ToList(); @@ -1285,7 +1286,6 @@ public class ResultadosController : ControllerBase var agrupacionesInfo = await _dbContext.AgrupacionesPoliticas.AsNoTracking() .Where(a => idsAgrupaciones.Contains(a.Id)).ToDictionaryAsync(a => a.Id); - // Mapeo final a DTO. var mapaDataProvincial = ganadoresPorMunicipio.Select(g => new ResultadoMapaDto { AmbitoId = g.AmbitoGeograficoId.ToString(), @@ -1297,4 +1297,193 @@ public class ResultadosController : ControllerBase return Ok(mapaDataProvincial); } } + + [HttpGet("composicion-nacional")] + public async Task GetComposicionNacional([FromRoute] int eleccionId) + { + // 1. Obtener todas las configuraciones relevantes en una sola consulta. + var config = await _dbContext.Configuraciones.AsNoTracking().ToDictionaryAsync(c => c.Clave, c => c.Valor); + + // 2. Obtener todas las agrupaciones políticas en una sola consulta. + var todasAgrupaciones = await _dbContext.AgrupacionesPoliticas.AsNoTracking().ToDictionaryAsync(a => a.Id); + + // 3. Obtener las bancas PREVIAS (las que no están en juego). + var bancasPrevias = await _dbContext.BancasPrevias + .AsNoTracking() + .Where(b => b.EleccionId == eleccionId) + .ToListAsync(); + + // 4. Obtener las bancas EN JUEGO (proyectadas por provincia). + var proyecciones = await _dbContext.ProyeccionesBancas + .AsNoTracking() + .Where(p => p.EleccionId == eleccionId && p.AmbitoGeografico.NivelId == 10) // Nivel 10 = Ámbito Provincial + .ToListAsync(); + + //Calculamos la fecha de la última proyección. + // Si no hay proyecciones aún, usamos la fecha y hora actual como un fallback seguro. + var ultimaActualizacion = proyecciones.Any() + ? proyecciones.Max(p => p.FechaTotalizacion) + : DateTime.UtcNow; + + // 5. Combinar los datos para obtener la composición final de cada partido. + var composicionFinal = todasAgrupaciones.Values.Select(agrupacion => new + { + Agrupacion = agrupacion, + DiputadosFijos = bancasPrevias.FirstOrDefault(b => b.AgrupacionPoliticaId == agrupacion.Id && b.Camara == Core.Enums.TipoCamara.Diputados)?.Cantidad ?? 0, + DiputadosGanados = proyecciones.Where(p => p.AgrupacionPoliticaId == agrupacion.Id && p.CategoriaId == 2).Sum(p => p.NroBancas), + SenadoresFijos = bancasPrevias.FirstOrDefault(b => b.AgrupacionPoliticaId == agrupacion.Id && b.Camara == Core.Enums.TipoCamara.Senadores)?.Cantidad ?? 0, + SenadoresGanados = proyecciones.Where(p => p.AgrupacionPoliticaId == agrupacion.Id && p.CategoriaId == 1).Sum(p => p.NroBancas) + }) + .Select(r => new + { + r.Agrupacion, + r.DiputadosFijos, + r.DiputadosGanados, + DiputadosTotales = r.DiputadosFijos + r.DiputadosGanados, + r.SenadoresFijos, + r.SenadoresGanados, + SenadoresTotales = r.SenadoresFijos + r.SenadoresGanados + }) + .ToList(); + + // 6. Determinar la información de la presidencia para cada cámara. + config.TryGetValue("PresidenciaDiputadosNacional", out var idPartidoPresDip); + var partidoPresidenteDiputados = !string.IsNullOrEmpty(idPartidoPresDip) ? todasAgrupaciones.GetValueOrDefault(idPartidoPresDip) : null; + config.TryGetValue("PresidenciaDiputadosNacional_TipoBanca", out var tipoBancaDip); + + config.TryGetValue("PresidenciaSenadoNacional", out var idPartidoPresSen); + var partidoPresidenteSenadores = !string.IsNullOrEmpty(idPartidoPresSen) ? todasAgrupaciones.GetValueOrDefault(idPartidoPresSen) : null; + config.TryGetValue("PresidenciaSenadoNacional_TipoBanca", out var tipoBancaSen); + + + // 7. Construir el objeto de respuesta final para D Diputados + var diputados = new + { + CamaraNombre = "Cámara de Diputados", + TotalBancas = 257, + BancasEnJuego = 127, + UltimaActualizacion = ultimaActualizacion, + Partidos = composicionFinal + .Where(p => p.DiputadosTotales > 0) + .OrderByDescending(p => p.DiputadosTotales) + .Select(p => new + { + p.Agrupacion.Id, + p.Agrupacion.Nombre, + p.Agrupacion.NombreCorto, + p.Agrupacion.Color, + BancasFijos = p.DiputadosFijos, + BancasGanadas = p.DiputadosGanados, + BancasTotales = p.DiputadosTotales, + p.Agrupacion.OrdenDiputadosNacionales, + p.Agrupacion.OrdenSenadoresNacionales + }).ToList(), + PresidenteBancada = partidoPresidenteDiputados != null + ? new { Color = partidoPresidenteDiputados.Color, TipoBanca = tipoBancaDip ?? "ganada" } + : null + }; + + // 8. Construir el objeto de respuesta final para Senadores + var senadores = new + { + CamaraNombre = "Senado de la Nación", + TotalBancas = 72, + BancasEnJuego = 24, + UltimaActualizacion = ultimaActualizacion, + Partidos = composicionFinal + .Where(p => p.SenadoresTotales > 0) + .OrderByDescending(p => p.SenadoresTotales) + .Select(p => new + { + p.Agrupacion.Id, + p.Agrupacion.Nombre, + p.Agrupacion.NombreCorto, + p.Agrupacion.Color, + BancasFijos = p.SenadoresFijos, + BancasGanadas = p.SenadoresGanados, + BancasTotales = p.SenadoresTotales, + p.Agrupacion.OrdenDiputadosNacionales, + p.Agrupacion.OrdenSenadoresNacionales + }).ToList(), + PresidenteBancada = partidoPresidenteSenadores != null + ? new { Color = partidoPresidenteSenadores.Color, TipoBanca = tipoBancaSen ?? "ganada" } + : null + }; + + return Ok(new { Diputados = diputados, Senadores = senadores }); + } + + [HttpGet("resumen-por-provincia")] + public async Task GetResumenPorProvincia([FromRoute] int eleccionId) + { + const int categoriaDiputadosNacionales = 2; + + var todasLasProyecciones = await _dbContext.ProyeccionesBancas.AsNoTracking() + .Where(p => p.EleccionId == eleccionId && p.CategoriaId == categoriaDiputadosNacionales) + .ToDictionaryAsync(p => p.AmbitoGeograficoId + "_" + p.AgrupacionPoliticaId); + + var todosLosOverrides = await _dbContext.CandidatosOverrides.AsNoTracking() + .Where(c => c.EleccionId == eleccionId && c.CategoriaId == categoriaDiputadosNacionales) + .ToListAsync(); + + var todosLosLogos = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking() + .Where(l => l.EleccionId == eleccionId && l.CategoriaId == categoriaDiputadosNacionales) + .ToListAsync(); + + var datosBrutos = await _dbContext.AmbitosGeograficos.AsNoTracking() + .Where(a => a.NivelId == 10) + .Select(provincia => new + { + ProvinciaAmbitoId = provincia.Id, + ProvinciaDistritoId = provincia.DistritoId!, + ProvinciaNombre = provincia.Nombre, + EstadoRecuento = _dbContext.EstadosRecuentosGenerales + .Where(e => e.EleccionId == eleccionId && e.CategoriaId == categoriaDiputadosNacionales && e.AmbitoGeograficoId == provincia.Id) + .Select(e => new EstadoRecuentoDto { /* ... */ }) + .FirstOrDefault(), + ResultadosBrutos = _dbContext.ResultadosVotos + .Where(r => r.EleccionId == eleccionId && r.CategoriaId == categoriaDiputadosNacionales && r.AmbitoGeografico.DistritoId == provincia.DistritoId) + .GroupBy(r => r.AgrupacionPolitica) + .Select(g => new { Agrupacion = g.Key, Votos = g.Sum(r => r.CantidadVotos) }) + .OrderByDescending(x => x.Votos) + .Take(2) + .ToList() + }) + .OrderBy(p => p.ProvinciaNombre) + .ToListAsync(); + + var resultadosFinales = datosBrutos.Select(provinciaData => + { + var totalVotosProvincia = (decimal)provinciaData.ResultadosBrutos.Sum(r => r.Votos); + return new ResumenProvinciaDto + { + ProvinciaId = provinciaData.ProvinciaDistritoId, + ProvinciaNombre = provinciaData.ProvinciaNombre, + EstadoRecuento = provinciaData.EstadoRecuento, + Resultados = provinciaData.ResultadosBrutos.Select(r => + { + var provinciaAmbitoId = provinciaData.ProvinciaAmbitoId; + return new ResultadoCandidatoDto + { + AgrupacionId = r.Agrupacion.Id, + NombreAgrupacion = r.Agrupacion.NombreCorto ?? r.Agrupacion.Nombre, + Color = r.Agrupacion.Color, + Votos = r.Votos, + NombreCandidato = (todosLosOverrides.FirstOrDefault(c => c.AgrupacionPoliticaId == r.Agrupacion.Id && c.AmbitoGeograficoId == provinciaAmbitoId) + ?? todosLosOverrides.FirstOrDefault(c => c.AgrupacionPoliticaId == r.Agrupacion.Id && c.AmbitoGeograficoId == null)) + ?.NombreCandidato, + FotoUrl = (todosLosLogos.FirstOrDefault(l => l.AgrupacionPoliticaId == r.Agrupacion.Id && l.AmbitoGeograficoId == provinciaAmbitoId) + ?? todosLosLogos.FirstOrDefault(l => l.AgrupacionPoliticaId == r.Agrupacion.Id && l.AmbitoGeograficoId == null)) + ?.LogoUrl, + BancasObtenidas = todasLasProyecciones.ContainsKey(provinciaAmbitoId + "_" + r.Agrupacion.Id) + ? todasLasProyecciones[provinciaAmbitoId + "_" + r.Agrupacion.Id].NroBancas + : 0, + Porcentaje = totalVotosProvincia > 0 ? (r.Votos / totalVotosProvincia) * 100 : 0 + }; + }).ToList() + }; + }).ToList(); + + return Ok(resultadosFinales); + } } \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Api/Program.cs b/Elecciones-Web/src/Elecciones.Api/Program.cs index 717f702..830c934 100644 --- a/Elecciones-Web/src/Elecciones.Api/Program.cs +++ b/Elecciones-Web/src/Elecciones.Api/Program.cs @@ -10,6 +10,8 @@ using Microsoft.IdentityModel.Tokens; using Elecciones.Database.Entities; using System.Text.Json.Serialization; using Microsoft.AspNetCore.HttpOverrides; +using Elecciones.Core.Enums; +using Microsoft.OpenApi.Models; // Esta es la estructura estándar y recomendada. var builder = WebApplication.CreateBuilder(args); @@ -81,8 +83,40 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) }); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +//builder.Services.AddSwaggerGen(); + +builder.Services.AddSwaggerGen(options => +{ + // 1. Definir el esquema de seguridad que usaremos (Bearer Token) + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "Autorización JWT usando el esquema Bearer. Ingresa 'Bearer' [espacio] y luego tu token. Ejemplo: 'Bearer 12345abcdef'", + Name = "Authorization", // El nombre del header HTTP + In = ParameterLocation.Header, // Dónde se ubicará el token (en el header) + Type = SecuritySchemeType.ApiKey, // El tipo de esquema + Scheme = "Bearer" // El nombre del esquema + }); + + // 2. Aplicar este requisito de seguridad a todos los endpoints que lo necesiten + options.AddSecurityRequirement(new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" // Debe coincidir con el nombre que le dimos en AddSecurityDefinition + }, + Scheme = "oauth2", + Name = "Bearer", + In = ParameterLocation.Header, + }, + new List() + } + }); +}); builder.Services.Configure(options => { @@ -153,7 +187,23 @@ using (var scope = app.Services.CreateScope()) } } -// Seeder para las bancas vacías +// --- SEEDER DE ELECCIONES (Añadir para asegurar que existan) --- +using (var scope = app.Services.CreateScope()) +{ + var context = scope.ServiceProvider.GetRequiredService(); + if (!context.Elecciones.Any()) + { + context.Elecciones.AddRange( + new Eleccion { Id = 1, Nombre = "Elecciones Provinciales 2025", Nivel = "Provincial", DistritoId = "02", Fecha = new DateOnly(2025, 10, 26) }, + new Eleccion { Id = 2, Nombre = "Elecciones Nacionales 2025", Nivel = "Nacional", DistritoId = "00", Fecha = new DateOnly(2025, 10, 26) } + ); + context.SaveChanges(); + Console.WriteLine("--> Seeded Eleccion entities."); + } +} + + +// --- SEEDER DE BANCAS (MODIFICADO Y COMPLETADO) --- using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; @@ -161,27 +211,91 @@ using (var scope = app.Services.CreateScope()) if (!context.Bancadas.Any()) { var bancas = new List(); - // 92 bancas de diputados - for (int i = 1; i <= 92; i++) // Bucle de 1 a 92 + + // --- BANCAS PROVINCIALES (EleccionId = 1) --- + // 92 bancas de diputados provinciales + for (int i = 1; i <= 92; i++) { bancas.Add(new Bancada { + EleccionId = 1, Camara = Elecciones.Core.Enums.TipoCamara.Diputados, - NumeroBanca = i // Asignamos el número de banca + NumeroBanca = i }); } - // 46 bancas de senadores - for (int i = 1; i <= 46; i++) // Bucle de 1 a 46 + // 46 bancas de senadores provinciales + for (int i = 1; i <= 46; i++) { bancas.Add(new Bancada { + EleccionId = 1, Camara = Elecciones.Core.Enums.TipoCamara.Senadores, - NumeroBanca = i // Asignamos el número de banca + NumeroBanca = i }); } + + // --- BANCAS NACIONALES (EleccionId = 2) --- + // 257 bancas de diputados nacionales + for (int i = 1; i <= 257; i++) + { + bancas.Add(new Bancada + { + EleccionId = 2, + Camara = TipoCamara.Diputados, + NumeroBanca = i + }); + } + // 72 bancas de senadores nacionales + for (int i = 1; i <= 72; i++) + { + bancas.Add(new Bancada + { + EleccionId = 2, + Camara = TipoCamara.Senadores, + NumeroBanca = i + }); + } + context.Bancadas.AddRange(bancas); context.SaveChanges(); - Console.WriteLine("--> Seeded 138 bancas físicas."); + Console.WriteLine($"--> Seeded {bancas.Count} bancas físicas para ambas elecciones."); + } +} + +// --- Seeder para Proyecciones de Bancas (Elección Nacional) --- +using (var scope = app.Services.CreateScope()) +{ + var services = scope.ServiceProvider; + var context = services.GetRequiredService(); + + const int eleccionNacionalId = 2; + // Categoría 2: Diputados Nacionales, Categoría 1: Senadores Nacionales + if (!context.ProyeccionesBancas.Any(p => p.EleccionId == eleccionNacionalId)) + { + var partidos = await context.AgrupacionesPoliticas.Take(5).ToListAsync(); + var provincia = await context.AmbitosGeograficos.FirstOrDefaultAsync(a => a.NivelId == 10); // Asumimos un ámbito provincial genérico para la proyección total + + if (partidos.Count >= 5 && provincia != null) + { + var proyecciones = new List + { + // -- DIPUTADOS (Se renuevan 127) -- + new() { EleccionId = eleccionNacionalId, CategoriaId = 2, AmbitoGeograficoId = provincia.Id, AgrupacionPoliticaId = partidos[0].Id, NroBancas = 50, FechaTotalizacion = DateTime.UtcNow }, + new() { EleccionId = eleccionNacionalId, CategoriaId = 2, AmbitoGeograficoId = provincia.Id, AgrupacionPoliticaId = partidos[1].Id, NroBancas = 40, FechaTotalizacion = DateTime.UtcNow }, + new() { EleccionId = eleccionNacionalId, CategoriaId = 2, AmbitoGeograficoId = provincia.Id, AgrupacionPoliticaId = partidos[2].Id, NroBancas = 20, FechaTotalizacion = DateTime.UtcNow }, + new() { EleccionId = eleccionNacionalId, CategoriaId = 2, AmbitoGeograficoId = provincia.Id, AgrupacionPoliticaId = partidos[3].Id, NroBancas = 10, FechaTotalizacion = DateTime.UtcNow }, + new() { EleccionId = eleccionNacionalId, CategoriaId = 2, AmbitoGeograficoId = provincia.Id, AgrupacionPoliticaId = partidos[4].Id, NroBancas = 7, FechaTotalizacion = DateTime.UtcNow }, + + // -- SENADORES (Se renuevan 24) -- + new() { EleccionId = eleccionNacionalId, CategoriaId = 1, AmbitoGeograficoId = provincia.Id, AgrupacionPoliticaId = partidos[0].Id, NroBancas = 10, FechaTotalizacion = DateTime.UtcNow }, + new() { EleccionId = eleccionNacionalId, CategoriaId = 1, AmbitoGeograficoId = provincia.Id, AgrupacionPoliticaId = partidos[1].Id, NroBancas = 8, FechaTotalizacion = DateTime.UtcNow }, + new() { EleccionId = eleccionNacionalId, CategoriaId = 1, AmbitoGeograficoId = provincia.Id, AgrupacionPoliticaId = partidos[2].Id, NroBancas = 4, FechaTotalizacion = DateTime.UtcNow }, + new() { EleccionId = eleccionNacionalId, CategoriaId = 1, AmbitoGeograficoId = provincia.Id, AgrupacionPoliticaId = partidos[3].Id, NroBancas = 2, FechaTotalizacion = DateTime.UtcNow }, + }; + await context.ProyeccionesBancas.AddRangeAsync(proyecciones); + await context.SaveChangesAsync(); + Console.WriteLine("--> Seeded Proyecciones de Bancas para la Elección Nacional."); + } } } @@ -200,7 +314,10 @@ using (var scope = app.Services.CreateScope()) { "Worker_Resultados_Activado", "false" }, { "Worker_Bajas_Activado", "false" }, { "Worker_Prioridad", "Resultados" }, - { "Logging_Level", "Information" } + { "Logging_Level", "Information" }, + { "PresidenciaDiputadosNacional", "" }, + { "PresidenciaDiputadosNacional_TipoBanca", "ganada" }, + { "PresidenciaSenadoNacional_TipoBanca", "ganada" } }; foreach (var config in defaultConfiguraciones) @@ -230,7 +347,7 @@ using (var scope = app.Services.CreateScope()) var eleccionNacional = await context.Elecciones.FindAsync(eleccionNacionalId) ?? new Eleccion { Id = eleccionNacionalId, Nombre = "Elecciones Nacionales 2025", Nivel = "Nacional", DistritoId = "00", Fecha = new DateOnly(2025, 10, 26) }; if (!context.Elecciones.Local.Any(e => e.Id == eleccionNacionalId)) context.Elecciones.Add(eleccionNacional); - + var categoriaDiputadosNac = await context.CategoriasElectorales.FindAsync(2) ?? new CategoriaElectoral { Id = 2, Nombre = "DIPUTADOS NACIONALES", Orden = 3 }; if (!context.CategoriasElectorales.Local.Any(c => c.Id == 2)) context.CategoriasElectorales.Add(categoriaDiputadosNac); await context.SaveChangesAsync(); @@ -270,7 +387,8 @@ using (var scope = app.Services.CreateScope()) await context.SaveChangesAsync(); var todosLosPartidos = await context.AgrupacionesPoliticas.Take(5).ToListAsync(); - if (!todosLosPartidos.Any()) { + if (!todosLosPartidos.Any()) + { logger.LogWarning("--> No hay partidos, no se pueden generar votos."); return; } @@ -278,7 +396,7 @@ using (var scope = app.Services.CreateScope()) var nuevosResultados = new List(); var nuevosEstados = new List(); var rand = new Random(); - + long totalVotosNacional = 0; int totalMesasNacional = 0; int totalMesasEscrutadasNacional = 0; @@ -287,9 +405,9 @@ using (var scope = app.Services.CreateScope()) { var municipiosDeProvincia = await context.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 30 && a.DistritoId == provincia.DistritoId).ToListAsync(); if (!municipiosDeProvincia.Any()) continue; - + long totalVotosProvincia = 0; - + int partidoIndex = rand.Next(todosLosPartidos.Count); foreach (var municipio in municipiosDeProvincia) { @@ -299,7 +417,8 @@ using (var scope = app.Services.CreateScope()) totalVotosProvincia += votosGanador; var otrosPartidos = todosLosPartidos.Where(p => p.Id != partidoGanador.Id).OrderBy(p => rand.Next()).Take(rand.Next(3, todosLosPartidos.Count)); - foreach (var competidor in otrosPartidos) { + foreach (var competidor in otrosPartidos) + { var votosCompetidor = rand.Next(1000, 24000); nuevosResultados.Add(new ResultadoVoto { EleccionId = eleccionNacionalId, AmbitoGeograficoId = municipio.Id, CategoriaId = categoriaDiputadosNac.Id, AgrupacionPoliticaId = competidor.Id, CantidadVotos = votosCompetidor }); totalVotosProvincia += votosCompetidor; @@ -312,8 +431,11 @@ using (var scope = app.Services.CreateScope()) var cantidadElectoresProvincia = mesasEsperadasProvincia * 350; var participacionProvincia = (decimal)(rand.Next(65, 85) / 100.0); - nuevosEstados.Add(new EstadoRecuentoGeneral { - EleccionId = eleccionNacionalId, AmbitoGeograficoId = provincia.Id, CategoriaId = categoriaDiputadosNac.Id, + nuevosEstados.Add(new EstadoRecuentoGeneral + { + EleccionId = eleccionNacionalId, + AmbitoGeograficoId = provincia.Id, + CategoriaId = categoriaDiputadosNac.Id, FechaTotalizacion = DateTime.UtcNow, MesasEsperadas = mesasEsperadasProvincia, MesasTotalizadas = mesasTotalizadasProvincia, @@ -322,7 +444,7 @@ using (var scope = app.Services.CreateScope()) CantidadVotantes = (int)(cantidadElectoresProvincia * participacionProvincia), ParticipacionPorcentaje = participacionProvincia * 100 }); - + totalVotosNacional += totalVotosProvincia; totalMesasNacional += mesasEsperadasProvincia; totalMesasEscrutadasNacional += mesasTotalizadasProvincia; @@ -330,14 +452,18 @@ using (var scope = app.Services.CreateScope()) // --- LÓGICA DE DATOS DE RECUENTO A NIVEL NACIONAL --- var ambitoNacional = await context.AmbitosGeograficos.AsNoTracking().FirstOrDefaultAsync(a => a.NivelId == 0); - if (ambitoNacional == null) { + if (ambitoNacional == null) + { ambitoNacional = new AmbitoGeografico { Nombre = "Nacional", NivelId = 0, DistritoId = "00" }; context.AmbitosGeograficos.Add(ambitoNacional); await context.SaveChangesAsync(); } var participacionNacional = (decimal)(rand.Next(70, 88) / 100.0); - nuevosEstados.Add(new EstadoRecuentoGeneral { - EleccionId = eleccionNacionalId, AmbitoGeograficoId = ambitoNacional.Id, CategoriaId = categoriaDiputadosNac.Id, + nuevosEstados.Add(new EstadoRecuentoGeneral + { + EleccionId = eleccionNacionalId, + AmbitoGeograficoId = ambitoNacional.Id, + CategoriaId = categoriaDiputadosNac.Id, FechaTotalizacion = DateTime.UtcNow, MesasEsperadas = totalMesasNacional, MesasTotalizadas = totalMesasEscrutadasNacional, @@ -347,17 +473,56 @@ using (var scope = app.Services.CreateScope()) ParticipacionPorcentaje = participacionNacional * 100 }); - if (nuevosResultados.Any()) { + if (nuevosResultados.Any()) + { await context.ResultadosVotos.AddRangeAsync(nuevosResultados); await context.EstadosRecuentosGenerales.AddRangeAsync(nuevosEstados); await context.SaveChangesAsync(); logger.LogInformation("--> Se generaron {Votos} registros de votos y {Estados} de estados de recuento.", nuevosResultados.Count, nuevosEstados.Count); - } else { + } + else + { logger.LogWarning("--> No se generaron datos de simulación."); } } } +// --- Seeder para Bancas Previas (Composición Nacional 2025) --- +using (var scope = app.Services.CreateScope()) +{ + var services = scope.ServiceProvider; + var context = services.GetRequiredService(); + + const int eleccionNacionalId = 2; + + if (!context.BancasPrevias.Any(b => b.EleccionId == eleccionNacionalId)) + { + var partidos = await context.AgrupacionesPoliticas.Take(5).ToListAsync(); + if (partidos.Count >= 5) + { + var bancasPrevias = new List + { + // -- DIPUTADOS (Total: 257, se renuevan 127, quedan 130) -- + new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = partidos[0].Id, Cantidad = 40 }, + new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = partidos[1].Id, Cantidad = 35 }, + new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = partidos[2].Id, Cantidad = 30 }, + new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = partidos[3].Id, Cantidad = 15 }, + new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = partidos[4].Id, Cantidad = 10 }, + + // -- SENADORES (Total: 72, se renuevan 24, quedan 48) -- + new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = partidos[0].Id, Cantidad = 18 }, + new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = partidos[1].Id, Cantidad = 15 }, + new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = partidos[2].Id, Cantidad = 8 }, + new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = partidos[3].Id, Cantidad = 4 }, + new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = partidos[4].Id, Cantidad = 3 }, + }; + await context.BancasPrevias.AddRangeAsync(bancasPrevias); + await context.SaveChangesAsync(); + Console.WriteLine("--> Seeded Bancas Previas para la Elección Nacional."); + } + } +} + // Configurar el pipeline de peticiones HTTP. // Añadimos el logging de peticiones de Serilog aquí. app.UseSerilogRequestLogging(); diff --git a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs index 4fddcc6..e701eda 100644 --- a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs @@ -14,7 +14,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5a8bee52d57b0f215705f3a7efb654169f85a7ae")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+67634ae947197595f6f644f3a80a982dd3573dfb")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json index fd71178..a53efdc 100644 --- a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json +++ b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","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=","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=","ayv780bSyYJGn9z2hycOzUCHGRbnvrzG/wr0RB8XoSg=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","WbNXPR1x3J5zRGe6yPRR\u002BWmWo3I/jnjzOyd\u002BJP8MhMI="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","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=","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=","0dvJZBTDvT8AWA99AJa8lh9rnQsEsujRTFe1QDxskcw=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","amEOUyqq4sgg/zUP6A7nQMqSHcl7G5zl2HvyHRlhDvU="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json index 8285eb5..88e879a 100644 --- a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json +++ b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","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=","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=","ayv780bSyYJGn9z2hycOzUCHGRbnvrzG/wr0RB8XoSg=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","WbNXPR1x3J5zRGe6yPRR\u002BWmWo3I/jnjzOyd\u002BJP8MhMI="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["TyIJk/eQMWjmB5LsDE\u002BZIJC9P9ciVxd7bnzRiTZsGt4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","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=","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=","0dvJZBTDvT8AWA99AJa8lh9rnQsEsujRTFe1QDxskcw=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","amEOUyqq4sgg/zUP6A7nQMqSHcl7G5zl2HvyHRlhDvU="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Core/DTOs/ApiResponses/ResumenProvinciaDto.cs b/Elecciones-Web/src/Elecciones.Core/DTOs/ApiResponses/ResumenProvinciaDto.cs new file mode 100644 index 0000000..d3fdf68 --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Core/DTOs/ApiResponses/ResumenProvinciaDto.cs @@ -0,0 +1,22 @@ +// src/Elecciones.Core/DTOs/ApiResponses/ResumenProvinciaDto.cs +namespace Elecciones.Core.DTOs.ApiResponses; + +public class ResultadoCandidatoDto +{ + public string AgrupacionId { get; set; } = string.Empty; + public string? NombreCandidato { get; set; } + public string NombreAgrupacion { get; set; } = null!; + public string? FotoUrl { get; set; } + public string? Color { get; set; } + public decimal Porcentaje { get; set; } + public long Votos { get; set; } + public int BancasObtenidas { get; set; } +} + +public class ResumenProvinciaDto +{ + public string ProvinciaId { get; set; } = null!; // Corresponde al DistritoId + public string ProvinciaNombre { get; set; } = null!; + public EstadoRecuentoDto? EstadoRecuento { get; set; } + public List Resultados { get; set; } = new(); +} \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs index d19673c..a4fd2de 100644 --- a/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5a8bee52d57b0f215705f3a7efb654169f85a7ae")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+67634ae947197595f6f644f3a80a982dd3573dfb")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs b/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs index eae02ae..4fdbda5 100644 --- a/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs +++ b/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs @@ -22,6 +22,7 @@ public class EleccionesDbContext(DbContextOptions options) public DbSet LogosAgrupacionesCategorias { get; set; } public DbSet CandidatosOverrides { get; set; } public DbSet Elecciones { get; set; } + public DbSet BancasPrevias { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -90,5 +91,10 @@ public class EleccionesDbContext(DbContextOptions options) entity.HasIndex(c => new { c.AgrupacionPoliticaId, c.CategoriaId, c.AmbitoGeograficoId }) .IsUnique(); }); + modelBuilder.Entity(entity => + { + entity.Property(e => e.AgrupacionPoliticaId) + .UseCollation("Modern_Spanish_CI_AS"); + }); } } \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Database/Entities/AgrupacionPolitica.cs b/Elecciones-Web/src/Elecciones.Database/Entities/AgrupacionPolitica.cs index b0db2dc..4c9fe80 100644 --- a/Elecciones-Web/src/Elecciones.Database/Entities/AgrupacionPolitica.cs +++ b/Elecciones-Web/src/Elecciones.Database/Entities/AgrupacionPolitica.cs @@ -10,8 +10,14 @@ public class AgrupacionPolitica public string IdTelegrama { get; set; } = null!; [Required] public string Nombre { get; set; } = null!; - public string? NombreCorto { get; set; } // Para leyendas y gráficos - public string? Color { get; set; } // Código hexadecimal, ej: "#1f77b4" + public string? NombreCorto { get; set; } + public string? Color { get; set; } + + // --- Campos para Provinciales --- public int? OrdenDiputados { get; set; } public int? OrdenSenadores { get; set; } + + // --- Campos para Nacionales --- + public int? OrdenDiputadosNacionales { get; set; } + public int? OrdenSenadoresNacionales { get; set; } } \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Database/Entities/BancaPrevia.cs b/Elecciones-Web/src/Elecciones.Database/Entities/BancaPrevia.cs new file mode 100644 index 0000000..b14d6d9 --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Database/Entities/BancaPrevia.cs @@ -0,0 +1,28 @@ +// src/Elecciones.Database/Entities/BancaPrevia.cs +using Elecciones.Core.Enums; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Elecciones.Database.Entities; + +public class BancaPrevia +{ + [Key] + public int Id { get; set; } + + [Required] + public int EleccionId { get; set; } + + [Required] + public TipoCamara Camara { get; set; } + + [Required] + public string AgrupacionPoliticaId { get; set; } = null!; + + [ForeignKey("AgrupacionPoliticaId")] + public AgrupacionPolitica AgrupacionPolitica { get; set; } = null!; + + // Cantidad de bancas que el partido retiene (no están en juego) + [Required] + public int Cantidad { get; set; } +} \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs b/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs index 45203ce..c8c47c3 100644 --- a/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs +++ b/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs @@ -7,6 +7,7 @@ namespace Elecciones.Database.Entities; public class EstadoRecuentoGeneral { public int AmbitoGeograficoId { get; set; } + public AmbitoGeografico AmbitoGeografico { get; set; } = null!; public int CategoriaId { get; set; } public DateTime FechaTotalizacion { get; set; } public int MesasEsperadas { get; set; } diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250922213437_AddBancasPreviasTable.Designer.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250922213437_AddBancasPreviasTable.Designer.cs new file mode 100644 index 0000000..7214b4c --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250922213437_AddBancasPreviasTable.Designer.cs @@ -0,0 +1,715 @@ +// +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("20250922213437_AddBancasPreviasTable")] + partial class AddBancasPreviasTable + { + /// + 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("Eleccion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DistritoId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Fecha") + .HasColumnType("date"); + + b.Property("Nivel") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Elecciones"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.AdminUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("AdminUsers"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.AgrupacionPolitica", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Color") + .HasColumnType("nvarchar(max)"); + + b.Property("IdTelegrama") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NombreCorto") + .HasColumnType("nvarchar(max)"); + + b.Property("OrdenDiputados") + .HasColumnType("int"); + + b.Property("OrdenSenadores") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AgrupacionesPoliticas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.AmbitoGeografico", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CircuitoId") + .HasColumnType("nvarchar(max)"); + + b.Property("DistritoId") + .HasColumnType("nvarchar(max)"); + + b.Property("EstablecimientoId") + .HasColumnType("nvarchar(max)"); + + b.Property("MesaId") + .HasColumnType("nvarchar(max)"); + + b.Property("MunicipioId") + .HasColumnType("nvarchar(max)"); + + b.Property("NivelId") + .HasColumnType("int"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SeccionId") + .HasColumnType("nvarchar(max)"); + + b.Property("SeccionProvincialId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AmbitosGeograficos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.BancaPrevia", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .UseCollation("Modern_Spanish_CI_AS"); + + b.Property("Camara") + .HasColumnType("int"); + + b.Property("Cantidad") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.ToTable("BancasPrevias"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .HasColumnType("nvarchar(450)"); + + b.Property("Camara") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("NumeroBanca") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.ToTable("Bancadas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.CandidatoOverride", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("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("Id") + .HasColumnType("int"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Orden") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("CategoriasElectorales"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.Configuracion", b => + { + b.Property("Clave") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Valor") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Clave"); + + b.ToTable("Configuraciones"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b => + { + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("CantidadElectores") + .HasColumnType("int"); + + b.Property("CantidadVotantes") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.Property("MesasEsperadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadasPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("ParticipacionPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("VotosEnBlanco") + .HasColumnType("bigint"); + + b.Property("VotosEnBlancoPorcentaje") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VotosNulos") + .HasColumnType("bigint"); + + b.Property("VotosNulosPorcentaje") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VotosRecurridos") + .HasColumnType("bigint"); + + b.Property("VotosRecurridosPorcentaje") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("AmbitoGeograficoId", "CategoriaId"); + + b.ToTable("EstadosRecuentos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b => + { + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("CantidadElectores") + .HasColumnType("int"); + + b.Property("CantidadVotantes") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.Property("MesasEsperadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadasPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("ParticipacionPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("AmbitoGeograficoId", "CategoriaId"); + + b.HasIndex("CategoriaId"); + + b.ToTable("EstadosRecuentosGenerales"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.LogoAgrupacionCategoria", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BancadaId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("FotoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("NombreOcupante") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Periodo") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BancadaId") + .IsUnique(); + + b.ToTable("OcupantesBancas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.Property("NroBancas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId") + .IsUnique(); + + b.ToTable("ProyeccionesBancas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.ResultadoVoto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CantidadVotos") + .HasColumnType("bigint"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("PorcentajeVotos") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId") + .IsUnique(); + + b.ToTable("ResultadosVotos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("Votos") + .HasColumnType("bigint"); + + b.Property("VotosPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.ToTable("ResumenesVotos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.Telegrama", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("ContenidoBase64") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("FechaEscaneo") + .HasColumnType("datetime2"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Telegramas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.BancaPrevia", 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.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 + } + } +} diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250922213437_AddBancasPreviasTable.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250922213437_AddBancasPreviasTable.cs new file mode 100644 index 0000000..e3e4d17 --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250922213437_AddBancasPreviasTable.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elecciones.Database.Migrations +{ + /// + public partial class AddBancasPreviasTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BancasPrevias", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + EleccionId = table.Column(type: "int", nullable: false), + Camara = table.Column(type: "int", nullable: false), + AgrupacionPoliticaId = table.Column(type: "nvarchar(450)", nullable: false, collation: "Modern_Spanish_CI_AS"), + Cantidad = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BancasPrevias", x => x.Id); + table.ForeignKey( + name: "FK_BancasPrevias_AgrupacionesPoliticas_AgrupacionPoliticaId", + column: x => x.AgrupacionPoliticaId, + principalTable: "AgrupacionesPoliticas", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BancasPrevias_AgrupacionPoliticaId", + table: "BancasPrevias", + column: "AgrupacionPoliticaId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BancasPrevias"); + } + } +} diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250924000007_AddOrdenNacionalToAgrupaciones.Designer.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250924000007_AddOrdenNacionalToAgrupaciones.Designer.cs new file mode 100644 index 0000000..6629c87 --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250924000007_AddOrdenNacionalToAgrupaciones.Designer.cs @@ -0,0 +1,721 @@ +// +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("20250924000007_AddOrdenNacionalToAgrupaciones")] + partial class AddOrdenNacionalToAgrupaciones + { + /// + 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("Eleccion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DistritoId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Fecha") + .HasColumnType("date"); + + b.Property("Nivel") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Elecciones"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.AdminUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("AdminUsers"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.AgrupacionPolitica", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Color") + .HasColumnType("nvarchar(max)"); + + b.Property("IdTelegrama") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NombreCorto") + .HasColumnType("nvarchar(max)"); + + b.Property("OrdenDiputados") + .HasColumnType("int"); + + b.Property("OrdenDiputadosNacionales") + .HasColumnType("int"); + + b.Property("OrdenSenadores") + .HasColumnType("int"); + + b.Property("OrdenSenadoresNacionales") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AgrupacionesPoliticas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.AmbitoGeografico", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CircuitoId") + .HasColumnType("nvarchar(max)"); + + b.Property("DistritoId") + .HasColumnType("nvarchar(max)"); + + b.Property("EstablecimientoId") + .HasColumnType("nvarchar(max)"); + + b.Property("MesaId") + .HasColumnType("nvarchar(max)"); + + b.Property("MunicipioId") + .HasColumnType("nvarchar(max)"); + + b.Property("NivelId") + .HasColumnType("int"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SeccionId") + .HasColumnType("nvarchar(max)"); + + b.Property("SeccionProvincialId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AmbitosGeograficos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.BancaPrevia", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .UseCollation("Modern_Spanish_CI_AS"); + + b.Property("Camara") + .HasColumnType("int"); + + b.Property("Cantidad") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.ToTable("BancasPrevias"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .HasColumnType("nvarchar(450)"); + + b.Property("Camara") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("NumeroBanca") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.ToTable("Bancadas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.CandidatoOverride", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("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("Id") + .HasColumnType("int"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Orden") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("CategoriasElectorales"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.Configuracion", b => + { + b.Property("Clave") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Valor") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Clave"); + + b.ToTable("Configuraciones"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b => + { + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("CantidadElectores") + .HasColumnType("int"); + + b.Property("CantidadVotantes") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.Property("MesasEsperadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadasPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("ParticipacionPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("VotosEnBlanco") + .HasColumnType("bigint"); + + b.Property("VotosEnBlancoPorcentaje") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VotosNulos") + .HasColumnType("bigint"); + + b.Property("VotosNulosPorcentaje") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VotosRecurridos") + .HasColumnType("bigint"); + + b.Property("VotosRecurridosPorcentaje") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("AmbitoGeograficoId", "CategoriaId"); + + b.ToTable("EstadosRecuentos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b => + { + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("CantidadElectores") + .HasColumnType("int"); + + b.Property("CantidadVotantes") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.Property("MesasEsperadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadasPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("ParticipacionPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("AmbitoGeograficoId", "CategoriaId"); + + b.HasIndex("CategoriaId"); + + b.ToTable("EstadosRecuentosGenerales"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.LogoAgrupacionCategoria", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BancadaId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("FotoUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("NombreOcupante") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Periodo") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BancadaId") + .IsUnique(); + + b.ToTable("OcupantesBancas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.Property("NroBancas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId") + .IsUnique(); + + b.ToTable("ProyeccionesBancas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.ResultadoVoto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CantidadVotos") + .HasColumnType("bigint"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("PorcentajeVotos") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId") + .IsUnique(); + + b.ToTable("ResultadosVotos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("Votos") + .HasColumnType("bigint"); + + b.Property("VotosPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.ToTable("ResumenesVotos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.Telegrama", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("ContenidoBase64") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.Property("FechaEscaneo") + .HasColumnType("datetime2"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Telegramas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.BancaPrevia", 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.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 + } + } +} diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250924000007_AddOrdenNacionalToAgrupaciones.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250924000007_AddOrdenNacionalToAgrupaciones.cs new file mode 100644 index 0000000..f4d3cad --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250924000007_AddOrdenNacionalToAgrupaciones.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elecciones.Database.Migrations +{ + /// + public partial class AddOrdenNacionalToAgrupaciones : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OrdenDiputadosNacionales", + table: "AgrupacionesPoliticas", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "OrdenSenadoresNacionales", + table: "AgrupacionesPoliticas", + type: "int", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "OrdenDiputadosNacionales", + table: "AgrupacionesPoliticas"); + + migrationBuilder.DropColumn( + name: "OrdenSenadoresNacionales", + table: "AgrupacionesPoliticas"); + } + } +} diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs index 687297e..3d0fb7b 100644 --- a/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs @@ -99,9 +99,15 @@ namespace Elecciones.Database.Migrations b.Property("OrdenDiputados") .HasColumnType("int"); + b.Property("OrdenDiputadosNacionales") + .HasColumnType("int"); + b.Property("OrdenSenadores") .HasColumnType("int"); + b.Property("OrdenSenadoresNacionales") + .HasColumnType("int"); + b.HasKey("Id"); b.ToTable("AgrupacionesPoliticas"); @@ -148,6 +154,35 @@ namespace Elecciones.Database.Migrations b.ToTable("AmbitosGeograficos"); }); + modelBuilder.Entity("Elecciones.Database.Entities.BancaPrevia", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .UseCollation("Modern_Spanish_CI_AS"); + + b.Property("Camara") + .HasColumnType("int"); + + b.Property("Cantidad") + .HasColumnType("int"); + + b.Property("EleccionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.ToTable("BancasPrevias"); + }); + modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => { b.Property("Id") @@ -546,6 +581,17 @@ namespace Elecciones.Database.Migrations b.ToTable("Telegramas"); }); + modelBuilder.Entity("Elecciones.Database.Entities.BancaPrevia", 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.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") diff --git a/Elecciones-Web/src/Elecciones.Database/obj/Debug/net9.0/Elecciones.Database.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Database/obj/Debug/net9.0/Elecciones.Database.AssemblyInfo.cs index 992c5ce..05b6ea1 100644 --- a/Elecciones-Web/src/Elecciones.Database/obj/Debug/net9.0/Elecciones.Database.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Database/obj/Debug/net9.0/Elecciones.Database.AssemblyInfo.cs @@ -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+5a8bee52d57b0f215705f3a7efb654169f85a7ae")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+67634ae947197595f6f644f3a80a982dd3573dfb")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs index 836e4be..32d05c9 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5a8bee52d57b0f215705f3a7efb654169f85a7ae")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+67634ae947197595f6f644f3a80a982dd3573dfb")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]