diff --git a/Elecciones-Web/frontend-admin/public/vite.svg b/Elecciones-Web/frontend-admin/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/Elecciones-Web/frontend-admin/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ 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 88ea33d..5dabdd2 100644 --- a/Elecciones-Web/frontend-admin/src/components/DashboardPage.tsx +++ b/Elecciones-Web/frontend-admin/src/components/DashboardPage.tsx @@ -7,6 +7,7 @@ import { ConfiguracionGeneral } from './ConfiguracionGeneral'; import { BancasManager } from './BancasManager'; import { LogoOverridesManager } from './LogoOverridesManager'; import { CandidatoOverridesManager } from './CandidatoOverridesManager'; +import { WorkerManager } from './WorkerManager'; export const DashboardPage = () => { const { logout } = useAuth(); @@ -35,6 +36,8 @@ export const DashboardPage = () => { +
+ ); diff --git a/Elecciones-Web/frontend-admin/src/components/WorkerManager.tsx b/Elecciones-Web/frontend-admin/src/components/WorkerManager.tsx new file mode 100644 index 0000000..57b3f57 --- /dev/null +++ b/Elecciones-Web/frontend-admin/src/components/WorkerManager.tsx @@ -0,0 +1,140 @@ +import { useState, useEffect } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { getConfiguracion, updateConfiguracion, updateLoggingLevel } from '../services/apiService'; +import type { ConfiguracionResponse } from '../services/apiService'; + +// --- Componente de Switch reutilizable para la UI --- +const Switch = ({ label, isChecked, onChange }: { label: string, isChecked: boolean, onChange: (checked: boolean) => void }) => ( + +); + +export const WorkerManager = () => { + const queryClient = useQueryClient(); + + // Estados locales para manejar los valores de la UI + const [resultadosActivado, setResultadosActivado] = useState(true); + const [bajasActivado, setBajasActivado] = useState(true); + const [prioridad, setPrioridad] = useState('Resultados'); + const [loggingLevel, setLoggingLevel] = useState('Information'); + + // Query para obtener la configuración actual desde la API + const { data: configData, isLoading } = useQuery({ + queryKey: ['configuracion'], + queryFn: getConfiguracion, + }); + + // useEffect para sincronizar el estado local con los datos de la API una vez cargados + useEffect(() => { + if (configData) { + setResultadosActivado(configData.Worker_Resultados_Activado === 'true'); + setBajasActivado(configData.Worker_Bajas_Activado === 'true'); + setPrioridad(configData.Worker_Prioridad || 'Resultados'); + setLoggingLevel(configData.Logging_Level || 'Information'); + } + }, [configData]); + + const handleSave = async () => { + try { + // Creamos dos promesas separadas, una para la config general y otra para el logging + const configPromise = updateConfiguracion({ + ...configData, + 'Worker_Resultados_Activado': resultadosActivado.toString(), + 'Worker_Bajas_Activado': bajasActivado.toString(), + 'Worker_Prioridad': prioridad, + 'Logging_Level': loggingLevel, + }); + + // La llamada al endpoint de logging-level es la que cambia el nivel EN VIVO. + const loggingPromise = updateLoggingLevel({ level: loggingLevel }); + + // Ejecutamos ambas en paralelo + await Promise.all([configPromise, loggingPromise]); + + queryClient.invalidateQueries({ queryKey: ['configuracion'] }); + alert('Configuración de Workers y Logging guardada.'); + + } catch (error) { + console.error("Error al guardar la configuración:", error); + alert('Error al guardar la configuración.'); + } + }; + + const isPrioridadDisabled = !resultadosActivado || !bajasActivado; + + if (isLoading) { + return

Gestión de Workers

Cargando configuración...

; + } + + return ( +
+

Gestión de Workers

+

Controla el comportamiento de los procesos de captura de datos.

+
+ + {/* --- Switches On/Off --- */} +
+ + +
+ + {/* --- Contenedor para Selectores --- */} +
+ {/* --- Selector de Prioridad --- */} +
+ + + {isPrioridadDisabled && Activar ambos workers para elegir prioridad.} +
+ + {/* --- NUEVO: Selector de Nivel de Logging --- */} +
+ + + Cambia el nivel de log en tiempo real. +
+
+ + {/* --- Botón de Guardar --- */} +
+ +
+
+
+ ); +}; \ 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 7c41b09..27abec8 100644 --- a/Elecciones-Web/frontend-admin/src/services/apiService.ts +++ b/Elecciones-Web/frontend-admin/src/services/apiService.ts @@ -121,10 +121,10 @@ export const updateLogos = async (data: LogoAgrupacionCategoria[]): Promise => { - // Ahora usa adminApiClient, que apunta a /api/admin/ - // La URL final será /api/admin/catalogos/municipios - const response = await adminApiClient.get('/catalogos/municipios'); - return response.data; + // Ahora usa adminApiClient, que apunta a /api/admin/ + // La URL final será /api/admin/catalogos/municipios + const response = await adminApiClient.get('/catalogos/municipios'); + return response.data; }; // 6. Overrides de Candidatos @@ -135,4 +135,14 @@ export const getCandidatos = async (): Promise => { export const updateCandidatos = async (data: CandidatoOverride[]): Promise => { await adminApiClient.put('/candidatos', data); +}; + +// 7. Gestión de 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); }; \ 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 ab54177..2607c04 100644 --- a/Elecciones-Web/src/Elecciones.Api/Controllers/AdminController.cs +++ b/Elecciones-Web/src/Elecciones.Api/Controllers/AdminController.cs @@ -6,6 +6,7 @@ using Elecciones.Core.DTOs.ApiRequests; using Elecciones.Database.Entities; using Microsoft.AspNetCore.Authorization; using Elecciones.Core.Enums; +using Elecciones.Infrastructure.Services; namespace Elecciones.Api.Controllers; @@ -16,11 +17,13 @@ public class AdminController : ControllerBase { private readonly EleccionesDbContext _dbContext; private readonly ILogger _logger; + private readonly LoggingSwitchService _loggingSwitchService; - public AdminController(EleccionesDbContext dbContext, ILogger logger) + public AdminController(EleccionesDbContext dbContext, ILogger logger, LoggingSwitchService loggingSwitchService) { _dbContext = dbContext; _logger = logger; + _loggingSwitchService = loggingSwitchService; } // Endpoint para obtener todas las agrupaciones para el panel de admin @@ -297,4 +300,41 @@ public class AdminController : ControllerBase await _dbContext.SaveChangesAsync(); return NoContent(); } + + /// + /// Actualiza el nivel mínimo de logging en tiempo real y guarda la configuración en la BD. + /// + /// Un objeto que contiene el nuevo nivel de logging. + [HttpPut("logging-level")] + public async Task UpdateLoggingLevel([FromBody] UpdateLoggingLevelRequest request) + { + if (string.IsNullOrWhiteSpace(request.Level)) + { + return BadRequest("El nivel de logging no puede estar vacío."); + } + + // 1. Intentamos actualizar el interruptor de Serilog en memoria. + bool success = _loggingSwitchService.SetLoggingLevel(request.Level); + + if (!success) + { + return BadRequest($"El nivel de logging '{request.Level}' no es válido. Los valores posibles son: Verbose, Debug, Information, Warning, Error, Fatal."); + } + + // 2. Si el cambio fue exitoso, guardamos el nuevo valor en la base de datos. + var config = await _dbContext.Configuraciones.FindAsync("Logging_Level"); + if (config == null) + { + _dbContext.Configuraciones.Add(new Configuracion { Clave = "Logging_Level", Valor = request.Level }); + } + else + { + config.Valor = request.Level; + } + + await _dbContext.SaveChangesAsync(); + + _logger.LogWarning("El nivel de logging ha sido cambiado a: {Level}", request.Level); + return Ok(new { message = $"Nivel de logging actualizado a '{request.Level}'." }); + } } \ 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 ddfe2f3..6014382 100644 --- a/Elecciones-Web/src/Elecciones.Api/Program.cs +++ b/Elecciones-Web/src/Elecciones.Api/Program.cs @@ -1,3 +1,4 @@ +//Elecciones.Api/Program.cs using Elecciones.Database; using Microsoft.EntityFrameworkCore; using Serilog; @@ -13,13 +14,24 @@ using Microsoft.AspNetCore.HttpOverrides; // Esta es la estructura estándar y recomendada. var builder = WebApplication.CreateBuilder(args); -// 1. Configurar Serilog. Esta es la forma correcta de integrarlo. -builder.Host.UseSerilog((context, services, configuration) => configuration - .ReadFrom.Configuration(context.Configuration) - .ReadFrom.Services(services) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.File("logs/api-.log", rollingInterval: RollingInterval.Day)); +// 1. Registra el servicio del interruptor como un Singleton. +// Esto asegura que toda la aplicación comparta la MISMA instancia del interruptor. +builder.Services.AddSingleton(); + +builder.Host.UseSerilog((context, services, configuration) => +{ + // 2. Obtenemos la instancia del interruptor que acabamos de registrar. + var loggingSwitch = services.GetRequiredService(); + + configuration + .ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + // 3. Establecemos el nivel mínimo de logging controlado por el interruptor. + .MinimumLevel.ControlledBy(loggingSwitch.LevelSwitch) + .WriteTo.Console() + .WriteTo.File("logs/api-.log", rollingInterval: RollingInterval.Day); // o "logs/worker-.log" +}); // 2. Añadir servicios al contenedor. var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); @@ -83,6 +95,34 @@ builder.Services.Configure(options => // 3. Construir la aplicación. var app = builder.Build(); +// --- LÓGICA PARA LEER EL NIVEL DE LOGGING AL INICIO --- +// Creamos un scope temporal para leer la configuración de la BD +using (var scope = app.Services.CreateScope()) // O 'host.Services.CreateScope()' +{ + var services = scope.ServiceProvider; + try + { + // El resto de la lógica no cambia + var dbContext = services.GetRequiredService(); + var loggingSwitchService = services.GetRequiredService(); + + var logLevelConfig = await dbContext.Configuraciones + .AsNoTracking() + .FirstOrDefaultAsync(c => c.Clave == "Logging_Level"); + + if (logLevelConfig != null) + { + loggingSwitchService.SetLoggingLevel(logLevelConfig.Valor); + Console.WriteLine($"--> Nivel de logging inicial establecido desde la BD a: {logLevelConfig.Valor}"); + } + } + catch (Exception ex) + { + // Si hay un error (ej. la BD no está disponible al arrancar), se usará el nivel por defecto 'Information'. + Console.WriteLine($"--> No se pudo establecer el nivel de logging desde la BD: {ex.Message}"); + } +} + app.UseForwardedHeaders(); // Seeder para el usuario admin @@ -150,19 +190,29 @@ using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; var context = services.GetRequiredService(); - if (!context.Configuraciones.Any(c => c.Clave == "MostrarOcupantes")) + + // Lista de configuraciones por defecto a asegurar + var defaultConfiguraciones = new Dictionary { - context.Configuraciones.Add(new Configuracion { Clave = "MostrarOcupantes", Valor = "true" }); - context.SaveChanges(); - Console.WriteLine("--> Seeded default configuration 'MostrarOcupantes'."); - } - if (!context.Configuraciones.Any(c => c.Clave == "TickerResultadosCantidad")) + { "MostrarOcupantes", "true" }, + { "TickerResultadosCantidad", "3" }, + { "ConcejalesResultadosCantidad", "5" }, + { "Worker_Resultados_Activado", "false" }, + { "Worker_Bajas_Activado", "false" }, + { "Worker_Prioridad", "Resultados" }, + { "Logging_Level", "Information" } + }; + + foreach (var config in defaultConfiguraciones) { - context.Configuraciones.Add(new Configuracion { Clave = "TickerResultadosCantidad", Valor = "3" }); - context.Configuraciones.Add(new Configuracion { Clave = "ConcejalesResultadosCantidad", Valor = "5" }); - context.SaveChanges(); - Console.WriteLine("--> Seeded default configuration 'TickerResultadosCantidad'."); + if (!context.Configuraciones.Any(c => c.Clave == config.Key)) + { + context.Configuraciones.Add(new Configuracion { Clave = config.Key, Valor = config.Value }); + } } + + context.SaveChanges(); + Console.WriteLine("--> Seeded default configurations."); } // Configurar el pipeline de peticiones HTTP. diff --git a/Elecciones-Web/src/Elecciones.Api/bin/Debug/net9.0/Elecciones.Api.deps.json b/Elecciones-Web/src/Elecciones.Api/bin/Debug/net9.0/Elecciones.Api.deps.json index ada48fa..f402857 100644 --- a/Elecciones-Web/src/Elecciones.Api/bin/Debug/net9.0/Elecciones.Api.deps.json +++ b/Elecciones-Web/src/Elecciones.Api/bin/Debug/net9.0/Elecciones.Api.deps.json @@ -859,17 +859,17 @@ } } }, - "Serilog/4.2.0": { + "Serilog/4.3.0": { "runtime": { "lib/net9.0/Serilog.dll": { - "assemblyVersion": "4.2.0.0", - "fileVersion": "4.2.0.0" + "assemblyVersion": "4.3.0.0", + "fileVersion": "4.3.0.0" } } }, "Serilog.AspNetCore/9.0.0": { "dependencies": { - "Serilog": "4.2.0", + "Serilog": "4.3.0", "Serilog.Extensions.Hosting": "9.0.0", "Serilog.Formatting.Compact": "3.0.0", "Serilog.Settings.Configuration": "9.0.0", @@ -889,7 +889,7 @@ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8", "Microsoft.Extensions.Hosting.Abstractions": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.8", - "Serilog": "4.2.0", + "Serilog": "4.3.0", "Serilog.Extensions.Logging": "9.0.0" }, "runtime": { @@ -902,7 +902,7 @@ "Serilog.Extensions.Logging/9.0.0": { "dependencies": { "Microsoft.Extensions.Logging": "9.0.8", - "Serilog": "4.2.0" + "Serilog": "4.3.0" }, "runtime": { "lib/net9.0/Serilog.Extensions.Logging.dll": { @@ -913,7 +913,7 @@ }, "Serilog.Formatting.Compact/3.0.0": { "dependencies": { - "Serilog": "4.2.0" + "Serilog": "4.3.0" }, "runtime": { "lib/net8.0/Serilog.Formatting.Compact.dll": { @@ -926,7 +926,7 @@ "dependencies": { "Microsoft.Extensions.Configuration.Binder": "9.0.8", "Microsoft.Extensions.DependencyModel": "9.0.8", - "Serilog": "4.2.0" + "Serilog": "4.3.0" }, "runtime": { "lib/net9.0/Serilog.Settings.Configuration.dll": { @@ -937,7 +937,7 @@ }, "Serilog.Sinks.Console/6.0.0": { "dependencies": { - "Serilog": "4.2.0" + "Serilog": "4.3.0" }, "runtime": { "lib/net8.0/Serilog.Sinks.Console.dll": { @@ -948,7 +948,7 @@ }, "Serilog.Sinks.Debug/3.0.0": { "dependencies": { - "Serilog": "4.2.0" + "Serilog": "4.3.0" }, "runtime": { "lib/net8.0/Serilog.Sinks.Debug.dll": { @@ -959,7 +959,7 @@ }, "Serilog.Sinks.File/7.0.0": { "dependencies": { - "Serilog": "4.2.0" + "Serilog": "4.3.0" }, "runtime": { "lib/net9.0/Serilog.Sinks.File.dll": { @@ -1294,8 +1294,10 @@ "Elecciones.Infrastructure/1.0.0": { "dependencies": { "Elecciones.Core": "1.0.0", + "Elecciones.Database": "1.0.0", "Microsoft.Extensions.Configuration.Abstractions": "9.0.8", "Microsoft.Extensions.Http": "9.0.8", + "Serilog": "4.3.0", "System.Threading.RateLimiting": "9.0.8" }, "runtime": { @@ -1691,12 +1693,12 @@ "path": "mono.texttemplating/3.0.0", "hashPath": "mono.texttemplating.3.0.0.nupkg.sha512" }, - "Serilog/4.2.0": { + "Serilog/4.3.0": { "type": "package", "serviceable": true, - "sha512": "sha512-gmoWVOvKgbME8TYR+gwMf7osROiWAURterc6Rt2dQyX7wtjZYpqFiA/pY6ztjGQKKV62GGCyOcmtP1UKMHgSmA==", - "path": "serilog/4.2.0", - "hashPath": "serilog.4.2.0.nupkg.sha512" + "sha512": "sha512-+cDryFR0GRhsGOnZSKwaDzRRl4MupvJ42FhCE4zhQRVanX0Jpg6WuCBk59OVhVDPmab1bB+nRykAnykYELA9qQ==", + "path": "serilog/4.3.0", + "hashPath": "serilog.4.3.0.nupkg.sha512" }, "Serilog.AspNetCore/9.0.0": { "type": "package", 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 b14e07d..f7f9ddf 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+d78a02a0ebc4c70ea01e48821db963110e7ce280")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f384a640f36be1289d652dc85e78ebdcef30968a")] [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 a0c7263..7339b10 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=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Kt4ImnGs0wklEJp/6NxrhrTvGLQxPfYUAB5LMWAnz10=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","v1SBeIVg8rE3EddYwnvF/EsPYr2F5GAppt/Egvdtr/0="],"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=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","UucupTplk47jbYuQLLfpsVglReDmh1hUE6oD0OEv\u002BsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","nor5YuoHu4p8qlladsJ2COw4pycCja0XN1sckUrKV/w="],"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 bc46cfc..b502b63 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=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Kt4ImnGs0wklEJp/6NxrhrTvGLQxPfYUAB5LMWAnz10=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","v1SBeIVg8rE3EddYwnvF/EsPYr2F5GAppt/Egvdtr/0="],"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=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","UucupTplk47jbYuQLLfpsVglReDmh1hUE6oD0OEv\u002BsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","nor5YuoHu4p8qlladsJ2COw4pycCja0XN1sckUrKV/w="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Api/obj/Elecciones.Api.csproj.nuget.dgspec.json b/Elecciones-Web/src/Elecciones.Api/obj/Elecciones.Api.csproj.nuget.dgspec.json index 3c95727..f074615 100644 --- a/Elecciones-Web/src/Elecciones.Api/obj/Elecciones.Api.csproj.nuget.dgspec.json +++ b/Elecciones-Web/src/Elecciones.Api/obj/Elecciones.Api.csproj.nuget.dgspec.json @@ -287,6 +287,9 @@ "projectReferences": { "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Core\\Elecciones.Core.csproj": { "projectPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Core\\Elecciones.Core.csproj" + }, + "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Database\\Elecciones.Database.csproj": { + "projectPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Database\\Elecciones.Database.csproj" } } } @@ -315,6 +318,10 @@ "target": "Package", "version": "[9.0.8, )" }, + "Serilog": { + "target": "Package", + "version": "[4.3.0, )" + }, "System.Threading.RateLimiting": { "target": "Package", "version": "[9.0.8, )" diff --git a/Elecciones-Web/src/Elecciones.Core/DTOs/ApiRequests/UpdateLoggingLevelRequest.cs b/Elecciones-Web/src/Elecciones.Core/DTOs/ApiRequests/UpdateLoggingLevelRequest.cs new file mode 100644 index 0000000..712ed0c --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Core/DTOs/ApiRequests/UpdateLoggingLevelRequest.cs @@ -0,0 +1,7 @@ +using System.ComponentModel.DataAnnotations; + +public class UpdateLoggingLevelRequest +{ + [Required] + public string Level { get; set; } = null!; +} \ 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 824a746..ba0c393 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+d78a02a0ebc4c70ea01e48821db963110e7ce280")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f384a640f36be1289d652dc85e78ebdcef30968a")] [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/obj/Debug/net9.0/Elecciones.Database.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Database/obj/Debug/net9.0/Elecciones.Database.AssemblyInfo.cs index ff3c604..2bb0575 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+d78a02a0ebc4c70ea01e48821db963110e7ce280")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f384a640f36be1289d652dc85e78ebdcef30968a")] [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/Elecciones.Infrastructure.csproj b/Elecciones-Web/src/Elecciones.Infrastructure/Elecciones.Infrastructure.csproj index 83ba870..3437279 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/Elecciones.Infrastructure.csproj +++ b/Elecciones-Web/src/Elecciones.Infrastructure/Elecciones.Infrastructure.csproj @@ -2,11 +2,13 @@ + + diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/Services/LoggingSwitchService.cs b/Elecciones-Web/src/Elecciones.Infrastructure/Services/LoggingSwitchService.cs new file mode 100644 index 0000000..4aa42a0 --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Infrastructure/Services/LoggingSwitchService.cs @@ -0,0 +1,31 @@ +using Serilog.Core; +using Serilog.Events; + +namespace Elecciones.Infrastructure.Services; + +public class LoggingSwitchService +{ + // El interruptor de nivel de logging de Serilog. + // Lo inicializamos en 'Information' por defecto. + public LoggingLevelSwitch LevelSwitch { get; } = new(LogEventLevel.Information); + + /// + /// Cambia el nivel mínimo de logging dinámicamente. + /// + /// El nuevo nivel de logging como string (ej. "Information", "Warning", "Verbose"). + /// True si el nivel se cambió con éxito, false si el string no es válido. + public bool SetLoggingLevel(string level) + { + // Usamos Enum.TryParse para convertir el string a un valor del enum LogEventLevel. + // El 'true' ignora mayúsculas/minúsculas. + if (Enum.TryParse(level, true, out var newLevel)) + { + // Si la conversión es exitosa, actualizamos el interruptor. + LevelSwitch.MinimumLevel = newLevel; + return true; + } + + // Si el string no corresponde a ningún nivel válido, no hacemos nada y devolvemos false. + return false; + } +} \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/Services/WorkerConfigService.cs b/Elecciones-Web/src/Elecciones.Infrastructure/Services/WorkerConfigService.cs new file mode 100644 index 0000000..16cb8f4 --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Infrastructure/Services/WorkerConfigService.cs @@ -0,0 +1,80 @@ +using Elecciones.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Elecciones.Infrastructure.Services; + +public class WorkerSettings +{ + public bool ResultadosActivado { get; set; } = true; + public bool BajasActivado { get; set; } = true; + public string Prioridad { get; set; } = "Resultados"; +} + +public class WorkerConfigService +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + private WorkerSettings _cachedSettings = new(); + private DateTime _lastFetchTime = DateTime.MinValue; + private readonly TimeSpan _cacheDuration = TimeSpan.FromSeconds(25); // Cachear por 25 segundos + private readonly SemaphoreSlim _semaphore = new(1, 1); + + public WorkerConfigService(IServiceProvider serviceProvider, ILogger logger) + { + _serviceProvider = serviceProvider; + _logger = logger; + } + + public async Task GetSettingsAsync() + { + // Si el caché es válido, lo devolvemos inmediatamente. + if (DateTime.UtcNow < _lastFetchTime + _cacheDuration) + { + return _cachedSettings; + } + + // Si el caché ha expirado, intentamos obtener el control para actualizarlo. + await _semaphore.WaitAsync(); + try + { + // Volvemos a comprobar por si otra tarea ya actualizó el caché mientras esperábamos. + if (DateTime.UtcNow < _lastFetchTime + _cacheDuration) + { + return _cachedSettings; + } + + _logger.LogInformation("Caché de configuración del worker expirado. Actualizando desde la BD..."); + + using var scope = _serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var configMap = await dbContext.Configuraciones.AsNoTracking().ToDictionaryAsync(c => c.Clave, c => c.Valor); + + _cachedSettings = new WorkerSettings + { + ResultadosActivado = configMap.GetValueOrDefault("Worker_Resultados_Activado", "true") == "true", + BajasActivado = configMap.GetValueOrDefault("Worker_Bajas_Activado", "true") == "true", + Prioridad = configMap.GetValueOrDefault("Worker_Prioridad", "Resultados") ?? "Resultados" + }; + + _lastFetchTime = DateTime.UtcNow; + _logger.LogInformation("Configuración del worker actualizada: Resultados={res}, Bajas={bajas}, Prioridad={prio}", + _cachedSettings.ResultadosActivado, _cachedSettings.BajasActivado, _cachedSettings.Prioridad); + } + catch (Exception ex) + { + _logger.LogError(ex, "No se pudo actualizar la configuración del worker desde la BD. Usando la última configuración cacheada."); + } + finally + { + _semaphore.Release(); + } + + return _cachedSettings; + } +} \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/bin/Debug/net9.0/Elecciones.Infrastructure.deps.json b/Elecciones-Web/src/Elecciones.Infrastructure/bin/Debug/net9.0/Elecciones.Infrastructure.deps.json index 50deac3..9f8745b 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/bin/Debug/net9.0/Elecciones.Infrastructure.deps.json +++ b/Elecciones-Web/src/Elecciones.Infrastructure/bin/Debug/net9.0/Elecciones.Infrastructure.deps.json @@ -9,14 +9,201 @@ "Elecciones.Infrastructure/1.0.0": { "dependencies": { "Elecciones.Core": "1.0.0", + "Elecciones.Database": "1.0.0", "Microsoft.Extensions.Configuration.Abstractions": "9.0.8", "Microsoft.Extensions.Http": "9.0.8", + "Serilog": "4.3.0", "System.Threading.RateLimiting": "9.0.8" }, "runtime": { "Elecciones.Infrastructure.dll": {} } }, + "Azure.Core/1.38.0": { + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "1.1.1", + "System.ClientModel": "1.0.0", + "System.Diagnostics.DiagnosticSource": "6.0.1", + "System.Memory.Data": "1.0.2", + "System.Numerics.Vectors": "4.5.0", + "System.Text.Encodings.Web": "6.0.0", + "System.Text.Json": "9.0.8", + "System.Threading.Tasks.Extensions": "4.5.4" + }, + "runtime": { + "lib/net6.0/Azure.Core.dll": { + "assemblyVersion": "1.38.0.0", + "fileVersion": "1.3800.24.12602" + } + } + }, + "Azure.Identity/1.11.4": { + "dependencies": { + "Azure.Core": "1.38.0", + "Microsoft.Identity.Client": "4.61.3", + "Microsoft.Identity.Client.Extensions.Msal": "4.61.3", + "System.Memory": "4.5.4", + "System.Security.Cryptography.ProtectedData": "6.0.0", + "System.Text.Json": "9.0.8", + "System.Threading.Tasks.Extensions": "4.5.4" + }, + "runtime": { + "lib/netstandard2.0/Azure.Identity.dll": { + "assemblyVersion": "1.11.4.0", + "fileVersion": "1.1100.424.31005" + } + } + }, + "Microsoft.Bcl.AsyncInterfaces/1.1.1": { + "runtime": { + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "4.700.20.21406" + } + } + }, + "Microsoft.CSharp/4.5.0": {}, + "Microsoft.Data.SqlClient/5.1.6": { + "dependencies": { + "Azure.Identity": "1.11.4", + "Microsoft.Data.SqlClient.SNI.runtime": "5.1.1", + "Microsoft.Identity.Client": "4.61.3", + "Microsoft.IdentityModel.JsonWebTokens": "6.35.0", + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.35.0", + "Microsoft.SqlServer.Server": "1.0.0", + "System.Configuration.ConfigurationManager": "6.0.1", + "System.Diagnostics.DiagnosticSource": "6.0.1", + "System.Runtime.Caching": "6.0.0", + "System.Security.Cryptography.Cng": "5.0.0", + "System.Security.Principal.Windows": "5.0.0", + "System.Text.Encoding.CodePages": "6.0.0", + "System.Text.Encodings.Web": "6.0.0" + }, + "runtime": { + "lib/net6.0/Microsoft.Data.SqlClient.dll": { + "assemblyVersion": "5.0.0.0", + "fileVersion": "5.16.24240.5" + } + }, + "runtimeTargets": { + "runtimes/unix/lib/net6.0/Microsoft.Data.SqlClient.dll": { + "rid": "unix", + "assetType": "runtime", + "assemblyVersion": "5.0.0.0", + "fileVersion": "5.16.24240.5" + }, + "runtimes/win/lib/net6.0/Microsoft.Data.SqlClient.dll": { + "rid": "win", + "assetType": "runtime", + "assemblyVersion": "5.0.0.0", + "fileVersion": "5.16.24240.5" + } + } + }, + "Microsoft.Data.SqlClient.SNI.runtime/5.1.1": { + "runtimeTargets": { + "runtimes/win-arm/native/Microsoft.Data.SqlClient.SNI.dll": { + "rid": "win-arm", + "assetType": "native", + "fileVersion": "5.1.1.0" + }, + "runtimes/win-arm64/native/Microsoft.Data.SqlClient.SNI.dll": { + "rid": "win-arm64", + "assetType": "native", + "fileVersion": "5.1.1.0" + }, + "runtimes/win-x64/native/Microsoft.Data.SqlClient.SNI.dll": { + "rid": "win-x64", + "assetType": "native", + "fileVersion": "5.1.1.0" + }, + "runtimes/win-x86/native/Microsoft.Data.SqlClient.SNI.dll": { + "rid": "win-x86", + "assetType": "native", + "fileVersion": "5.1.1.0" + } + } + }, + "Microsoft.EntityFrameworkCore/9.0.8": { + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "9.0.8", + "Microsoft.EntityFrameworkCore.Analyzers": "9.0.8", + "Microsoft.Extensions.Caching.Memory": "9.0.8", + "Microsoft.Extensions.Logging": "9.0.8" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.dll": { + "assemblyVersion": "9.0.8.0", + "fileVersion": "9.0.825.36802" + } + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.8": { + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "assemblyVersion": "9.0.8.0", + "fileVersion": "9.0.825.36802" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.8": {}, + "Microsoft.EntityFrameworkCore.Relational/9.0.8": { + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.8", + "Microsoft.Extensions.Caching.Memory": "9.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.8", + "Microsoft.Extensions.Logging": "9.0.8" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "assemblyVersion": "9.0.8.0", + "fileVersion": "9.0.825.36802" + } + } + }, + "Microsoft.EntityFrameworkCore.SqlServer/9.0.8": { + "dependencies": { + "Microsoft.Data.SqlClient": "5.1.6", + "Microsoft.EntityFrameworkCore.Relational": "9.0.8", + "Microsoft.Extensions.Caching.Memory": "9.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.8", + "Microsoft.Extensions.Logging": "9.0.8", + "System.Formats.Asn1": "9.0.8", + "System.Text.Json": "9.0.8" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.SqlServer.dll": { + "assemblyVersion": "9.0.8.0", + "fileVersion": "9.0.825.36802" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.8": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.8" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.825.36511" + } + } + }, + "Microsoft.Extensions.Caching.Memory/9.0.8": { + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "9.0.8", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8", + "Microsoft.Extensions.Logging.Abstractions": "9.0.8", + "Microsoft.Extensions.Options": "9.0.8", + "Microsoft.Extensions.Primitives": "9.0.8" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.825.36511" + } + } + }, "Microsoft.Extensions.Configuration/9.0.8": { "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "9.0.8", @@ -170,6 +357,308 @@ } } }, + "Microsoft.Identity.Client/4.61.3": { + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "6.35.0", + "System.Diagnostics.DiagnosticSource": "6.0.1" + }, + "runtime": { + "lib/net6.0/Microsoft.Identity.Client.dll": { + "assemblyVersion": "4.61.3.0", + "fileVersion": "4.61.3.0" + } + } + }, + "Microsoft.Identity.Client.Extensions.Msal/4.61.3": { + "dependencies": { + "Microsoft.Identity.Client": "4.61.3", + "System.Security.Cryptography.ProtectedData": "6.0.0" + }, + "runtime": { + "lib/net6.0/Microsoft.Identity.Client.Extensions.Msal.dll": { + "assemblyVersion": "4.61.3.0", + "fileVersion": "4.61.3.0" + } + } + }, + "Microsoft.IdentityModel.Abstractions/6.35.0": { + "runtime": { + "lib/net6.0/Microsoft.IdentityModel.Abstractions.dll": { + "assemblyVersion": "6.35.0.0", + "fileVersion": "6.35.0.41201" + } + } + }, + "Microsoft.IdentityModel.JsonWebTokens/6.35.0": { + "dependencies": { + "Microsoft.IdentityModel.Tokens": "6.35.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encodings.Web": "6.0.0", + "System.Text.Json": "9.0.8" + }, + "runtime": { + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll": { + "assemblyVersion": "6.35.0.0", + "fileVersion": "6.35.0.41201" + } + } + }, + "Microsoft.IdentityModel.Logging/6.35.0": { + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "6.35.0" + }, + "runtime": { + "lib/net6.0/Microsoft.IdentityModel.Logging.dll": { + "assemblyVersion": "6.35.0.0", + "fileVersion": "6.35.0.41201" + } + } + }, + "Microsoft.IdentityModel.Protocols/6.35.0": { + "dependencies": { + "Microsoft.IdentityModel.Logging": "6.35.0", + "Microsoft.IdentityModel.Tokens": "6.35.0" + }, + "runtime": { + "lib/net6.0/Microsoft.IdentityModel.Protocols.dll": { + "assemblyVersion": "6.35.0.0", + "fileVersion": "6.35.0.41201" + } + } + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect/6.35.0": { + "dependencies": { + "Microsoft.IdentityModel.Protocols": "6.35.0", + "System.IdentityModel.Tokens.Jwt": "6.35.0" + }, + "runtime": { + "lib/net6.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": { + "assemblyVersion": "6.35.0.0", + "fileVersion": "6.35.0.41201" + } + } + }, + "Microsoft.IdentityModel.Tokens/6.35.0": { + "dependencies": { + "Microsoft.CSharp": "4.5.0", + "Microsoft.IdentityModel.Logging": "6.35.0", + "System.Security.Cryptography.Cng": "5.0.0" + }, + "runtime": { + "lib/net6.0/Microsoft.IdentityModel.Tokens.dll": { + "assemblyVersion": "6.35.0.0", + "fileVersion": "6.35.0.41201" + } + } + }, + "Microsoft.NETCore.Platforms/1.1.0": {}, + "Microsoft.NETCore.Targets/1.1.0": {}, + "Microsoft.SqlServer.Server/1.0.0": { + "runtime": { + "lib/netstandard2.0/Microsoft.SqlServer.Server.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "Microsoft.Win32.SystemEvents/6.0.0": { + "runtime": { + "lib/net6.0/Microsoft.Win32.SystemEvents.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + }, + "runtimeTargets": { + "runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll": { + "rid": "win", + "assetType": "runtime", + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + } + }, + "Serilog/4.3.0": { + "runtime": { + "lib/net9.0/Serilog.dll": { + "assemblyVersion": "4.3.0.0", + "fileVersion": "4.3.0.0" + } + } + }, + "System.ClientModel/1.0.0": { + "dependencies": { + "System.Memory.Data": "1.0.2", + "System.Text.Json": "9.0.8" + }, + "runtime": { + "lib/net6.0/System.ClientModel.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.24.5302" + } + } + }, + "System.Configuration.ConfigurationManager/6.0.1": { + "dependencies": { + "System.Security.Cryptography.ProtectedData": "6.0.0", + "System.Security.Permissions": "6.0.0" + }, + "runtime": { + "lib/net6.0/System.Configuration.ConfigurationManager.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.922.41905" + } + } + }, + "System.Diagnostics.DiagnosticSource/6.0.1": { + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Drawing.Common/6.0.0": { + "dependencies": { + "Microsoft.Win32.SystemEvents": "6.0.0" + }, + "runtime": { + "lib/net6.0/System.Drawing.Common.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + }, + "runtimeTargets": { + "runtimes/unix/lib/net6.0/System.Drawing.Common.dll": { + "rid": "unix", + "assetType": "runtime", + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + }, + "runtimes/win/lib/net6.0/System.Drawing.Common.dll": { + "rid": "win", + "assetType": "runtime", + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + } + }, + "System.Formats.Asn1/9.0.8": { + "runtime": { + "lib/net9.0/System.Formats.Asn1.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.825.36511" + } + } + }, + "System.IdentityModel.Tokens.Jwt/6.35.0": { + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "6.35.0", + "Microsoft.IdentityModel.Tokens": "6.35.0" + }, + "runtime": { + "lib/net6.0/System.IdentityModel.Tokens.Jwt.dll": { + "assemblyVersion": "6.35.0.0", + "fileVersion": "6.35.0.41201" + } + } + }, + "System.Memory/4.5.4": {}, + "System.Memory.Data/1.0.2": { + "dependencies": { + "System.Text.Encodings.Web": "6.0.0", + "System.Text.Json": "9.0.8" + }, + "runtime": { + "lib/netstandard2.0/System.Memory.Data.dll": { + "assemblyVersion": "1.0.2.0", + "fileVersion": "1.0.221.20802" + } + } + }, + "System.Numerics.Vectors/4.5.0": {}, + "System.Runtime/4.3.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Runtime.Caching/6.0.0": { + "dependencies": { + "System.Configuration.ConfigurationManager": "6.0.1" + }, + "runtime": { + "lib/net6.0/System.Runtime.Caching.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.21.52210" + } + }, + "runtimeTargets": { + "runtimes/win/lib/net6.0/System.Runtime.Caching.dll": { + "rid": "win", + "assetType": "runtime", + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.21.52210" + } + } + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": {}, + "System.Security.AccessControl/6.0.0": {}, + "System.Security.Cryptography.Cng/5.0.0": { + "dependencies": { + "System.Formats.Asn1": "9.0.8" + } + }, + "System.Security.Cryptography.ProtectedData/6.0.0": { + "runtime": { + "lib/net6.0/System.Security.Cryptography.ProtectedData.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + }, + "runtimeTargets": { + "runtimes/win/lib/net6.0/System.Security.Cryptography.ProtectedData.dll": { + "rid": "win", + "assetType": "runtime", + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + } + }, + "System.Security.Permissions/6.0.0": { + "dependencies": { + "System.Security.AccessControl": "6.0.0", + "System.Windows.Extensions": "6.0.0" + }, + "runtime": { + "lib/net6.0/System.Security.Permissions.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + } + }, + "System.Security.Principal.Windows/5.0.0": {}, + "System.Text.Encoding/4.3.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.CodePages/6.0.0": { + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Text.Encodings.Web/6.0.0": { + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Text.Json/9.0.8": { + "runtime": { + "lib/net9.0/System.Text.Json.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.825.36511" + } + } + }, "System.Threading.RateLimiting/9.0.8": { "runtime": { "lib/net9.0/System.Threading.RateLimiting.dll": { @@ -178,6 +667,26 @@ } } }, + "System.Threading.Tasks.Extensions/4.5.4": {}, + "System.Windows.Extensions/6.0.0": { + "dependencies": { + "System.Drawing.Common": "6.0.0" + }, + "runtime": { + "lib/net6.0/System.Windows.Extensions.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + }, + "runtimeTargets": { + "runtimes/win/lib/net6.0/System.Windows.Extensions.dll": { + "rid": "win", + "assetType": "runtime", + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + } + }, "Elecciones.Core/1.0.0": { "runtime": { "Elecciones.Core.dll": { @@ -185,6 +694,18 @@ "fileVersion": "1.0.0.0" } } + }, + "Elecciones.Database/1.0.0": { + "dependencies": { + "Elecciones.Core": "1.0.0", + "Microsoft.EntityFrameworkCore.SqlServer": "9.0.8" + }, + "runtime": { + "Elecciones.Database.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } } } }, @@ -194,6 +715,97 @@ "serviceable": false, "sha512": "" }, + "Azure.Core/1.38.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IuEgCoVA0ef7E4pQtpC3+TkPbzaoQfa77HlfJDmfuaJUCVJmn7fT0izamZiryW5sYUFKizsftIxMkXKbgIcPMQ==", + "path": "azure.core/1.38.0", + "hashPath": "azure.core.1.38.0.nupkg.sha512" + }, + "Azure.Identity/1.11.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Sf4BoE6Q3jTgFkgBkx7qztYOFELBCo+wQgpYDwal/qJ1unBH73ywPztIJKXBXORRzAeNijsuxhk94h0TIMvfYg==", + "path": "azure.identity/1.11.4", + "hashPath": "azure.identity.1.11.4.nupkg.sha512" + }, + "Microsoft.Bcl.AsyncInterfaces/1.1.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-yuvf07qFWFqtK3P/MRkEKLhn5r2UbSpVueRziSqj0yJQIKFwG1pq9mOayK3zE5qZCTs0CbrwL9M6R8VwqyGy2w==", + "path": "microsoft.bcl.asyncinterfaces/1.1.1", + "hashPath": "microsoft.bcl.asyncinterfaces.1.1.1.nupkg.sha512" + }, + "Microsoft.CSharp/4.5.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-kaj6Wb4qoMuH3HySFJhxwQfe8R/sJsNJnANrvv8WdFPMoNbKY5htfNscv+LHCu5ipz+49m2e+WQXpLXr9XYemQ==", + "path": "microsoft.csharp/4.5.0", + "hashPath": "microsoft.csharp.4.5.0.nupkg.sha512" + }, + "Microsoft.Data.SqlClient/5.1.6": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+pz7gIPh5ydsBcQvivt4R98PwJXer86fyQBBToIBLxZ5kuhW4N13Ijz87s9WpuPtF1vh4JesYCgpDPAOgkMhdg==", + "path": "microsoft.data.sqlclient/5.1.6", + "hashPath": "microsoft.data.sqlclient.5.1.6.nupkg.sha512" + }, + "Microsoft.Data.SqlClient.SNI.runtime/5.1.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-wNGM5ZTQCa2blc9ikXQouybGiyMd6IHPVJvAlBEPtr6JepZEOYeDxGyprYvFVeOxlCXs7avridZQ0nYkHzQWCQ==", + "path": "microsoft.data.sqlclient.sni.runtime/5.1.1", + "hashPath": "microsoft.data.sqlclient.sni.runtime.5.1.1.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore/9.0.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-bNGdPhN762+BIIO5MFYLjafRqkSS1MqLOc/erd55InvLnFxt9H3N5JNsuag1ZHyBor1VtD42U0CHpgqkWeAYgQ==", + "path": "microsoft.entityframeworkcore/9.0.8", + "hashPath": "microsoft.entityframeworkcore.9.0.8.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-B2yfAIQRRAQ4zvvWqh+HudD+juV3YoLlpXnrog3tU0PM9AFpuq6xo0+mEglN1P43WgdcUiF+65CWBcZe35s15Q==", + "path": "microsoft.entityframeworkcore.abstractions/9.0.8", + "hashPath": "microsoft.entityframeworkcore.abstractions.9.0.8.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2EYStCXt4Hi9p3J3EYMQbItJDtASJd064Kcs8C8hj8Jt5srILrR9qlaL0Ryvk8NrWQoCQvIELsmiuqLEZMLvGA==", + "path": "microsoft.entityframeworkcore.analyzers/9.0.8", + "hashPath": "microsoft.entityframeworkcore.analyzers.9.0.8.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-OVhfyxiHxMvYpwQ8Jy3YZi4koy6TK5/Q7C1oq3z6db+HEGuu6x9L1BX5zDIdJxxlRePMyO4D8ORiXj/D7+MUqw==", + "path": "microsoft.entityframeworkcore.relational/9.0.8", + "hashPath": "microsoft.entityframeworkcore.relational.9.0.8.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.SqlServer/9.0.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-yNZJIdLQTTHj6FTv9+IUQwmQvOwvUanTBOG1ibeTaaB1zfTtOqrSFQnjMOkcKOgxu+ofsBEDcuctb/f5xj/Oog==", + "path": "microsoft.entityframeworkcore.sqlserver/9.0.8", + "hashPath": "microsoft.entityframeworkcore.sqlserver.9.0.8.nupkg.sha512" + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-4h7bsVoKoiK+SlPM+euX/ayGnKZhl47pPCidLTiio9xyG+vgVVfcYxcYQgjm0SCrdSxjG0EGIAKF8EFr3G8Ifw==", + "path": "microsoft.extensions.caching.abstractions/9.0.8", + "hashPath": "microsoft.extensions.caching.abstractions.9.0.8.nupkg.sha512" + }, + "Microsoft.Extensions.Caching.Memory/9.0.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-grR+oPyj8HVn4DT8CFUUdSw2pZZKS13KjytFe4txpHQliGM1GEDotohmjgvyl3hm7RFB3FRqvbouEX3/1ewp5A==", + "path": "microsoft.extensions.caching.memory/9.0.8", + "hashPath": "microsoft.extensions.caching.memory.9.0.8.nupkg.sha512" + }, "Microsoft.Extensions.Configuration/9.0.8": { "type": "package", "serviceable": true, @@ -285,6 +897,244 @@ "path": "microsoft.extensions.primitives/9.0.8", "hashPath": "microsoft.extensions.primitives.9.0.8.nupkg.sha512" }, + "Microsoft.Identity.Client/4.61.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-naJo/Qm35Caaoxp5utcw+R8eU8ZtLz2ALh8S+gkekOYQ1oazfCQMWVT4NJ/FnHzdIJlm8dMz0oMpMGCabx5odA==", + "path": "microsoft.identity.client/4.61.3", + "hashPath": "microsoft.identity.client.4.61.3.nupkg.sha512" + }, + "Microsoft.Identity.Client.Extensions.Msal/4.61.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-PWnJcznrSGr25MN8ajlc2XIDW4zCFu0U6FkpaNLEWLgd1NgFCp5uDY3mqLDgM8zCN8hqj8yo5wHYfLB2HjcdGw==", + "path": "microsoft.identity.client.extensions.msal/4.61.3", + "hashPath": "microsoft.identity.client.extensions.msal.4.61.3.nupkg.sha512" + }, + "Microsoft.IdentityModel.Abstractions/6.35.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-xuR8E4Rd96M41CnUSCiOJ2DBh+z+zQSmyrYHdYhD6K4fXBcQGVnRCFQ0efROUYpP+p0zC1BLKr0JRpVuujTZSg==", + "path": "microsoft.identitymodel.abstractions/6.35.0", + "hashPath": "microsoft.identitymodel.abstractions.6.35.0.nupkg.sha512" + }, + "Microsoft.IdentityModel.JsonWebTokens/6.35.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-9wxai3hKgZUb4/NjdRKfQd0QJvtXKDlvmGMYACbEC8DFaicMFCFhQFZq9ZET1kJLwZahf2lfY5Gtcpsx8zYzbg==", + "path": "microsoft.identitymodel.jsonwebtokens/6.35.0", + "hashPath": "microsoft.identitymodel.jsonwebtokens.6.35.0.nupkg.sha512" + }, + "Microsoft.IdentityModel.Logging/6.35.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jePrSfGAmqT81JDCNSY+fxVWoGuJKt9e6eJ+vT7+quVS55nWl//jGjUQn4eFtVKt4rt5dXaleZdHRB9J9AJZ7Q==", + "path": "microsoft.identitymodel.logging/6.35.0", + "hashPath": "microsoft.identitymodel.logging.6.35.0.nupkg.sha512" + }, + "Microsoft.IdentityModel.Protocols/6.35.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-BPQhlDzdFvv1PzaUxNSk+VEPwezlDEVADIKmyxubw7IiELK18uJ06RQ9QKKkds30XI+gDu9n8j24XQ8w7fjWcg==", + "path": "microsoft.identitymodel.protocols/6.35.0", + "hashPath": "microsoft.identitymodel.protocols.6.35.0.nupkg.sha512" + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect/6.35.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-LMtVqnECCCdSmyFoCOxIE5tXQqkOLrvGrL7OxHg41DIm1bpWtaCdGyVcTAfOQpJXvzND9zUKIN/lhngPkYR8vg==", + "path": "microsoft.identitymodel.protocols.openidconnect/6.35.0", + "hashPath": "microsoft.identitymodel.protocols.openidconnect.6.35.0.nupkg.sha512" + }, + "Microsoft.IdentityModel.Tokens/6.35.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-RN7lvp7s3Boucg1NaNAbqDbxtlLj5Qeb+4uSS1TeK5FSBVM40P4DKaTKChT43sHyKfh7V0zkrMph6DdHvyA4bg==", + "path": "microsoft.identitymodel.tokens/6.35.0", + "hashPath": "microsoft.identitymodel.tokens.6.35.0.nupkg.sha512" + }, + "Microsoft.NETCore.Platforms/1.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", + "path": "microsoft.netcore.platforms/1.1.0", + "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" + }, + "Microsoft.NETCore.Targets/1.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", + "path": "microsoft.netcore.targets/1.1.0", + "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" + }, + "Microsoft.SqlServer.Server/1.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-N4KeF3cpcm1PUHym1RmakkzfkEv3GRMyofVv40uXsQhCQeglr2OHNcUk2WOG51AKpGO8ynGpo9M/kFXSzghwug==", + "path": "microsoft.sqlserver.server/1.0.0", + "hashPath": "microsoft.sqlserver.server.1.0.0.nupkg.sha512" + }, + "Microsoft.Win32.SystemEvents/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==", + "path": "microsoft.win32.systemevents/6.0.0", + "hashPath": "microsoft.win32.systemevents.6.0.0.nupkg.sha512" + }, + "Serilog/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+cDryFR0GRhsGOnZSKwaDzRRl4MupvJ42FhCE4zhQRVanX0Jpg6WuCBk59OVhVDPmab1bB+nRykAnykYELA9qQ==", + "path": "serilog/4.3.0", + "hashPath": "serilog.4.3.0.nupkg.sha512" + }, + "System.ClientModel/1.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-I3CVkvxeqFYjIVEP59DnjbeoGNfo/+SZrCLpRz2v/g0gpCHaEMPtWSY0s9k/7jR1rAsLNg2z2u1JRB76tPjnIw==", + "path": "system.clientmodel/1.0.0", + "hashPath": "system.clientmodel.1.0.0.nupkg.sha512" + }, + "System.Configuration.ConfigurationManager/6.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jXw9MlUu/kRfEU0WyTptAVueupqIeE3/rl0EZDMlf8pcvJnitQ8HeVEp69rZdaStXwTV72boi/Bhw8lOeO+U2w==", + "path": "system.configuration.configurationmanager/6.0.1", + "hashPath": "system.configuration.configurationmanager.6.0.1.nupkg.sha512" + }, + "System.Diagnostics.DiagnosticSource/6.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==", + "path": "system.diagnostics.diagnosticsource/6.0.1", + "hashPath": "system.diagnostics.diagnosticsource.6.0.1.nupkg.sha512" + }, + "System.Drawing.Common/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", + "path": "system.drawing.common/6.0.0", + "hashPath": "system.drawing.common.6.0.0.nupkg.sha512" + }, + "System.Formats.Asn1/9.0.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-gGL0gt2nAArsF2oOMFzClll6QN2FhtooTxEQ+K26uer4lrhahnYIo/qOn5HUSfjHlM91646L5/7dYIMJ86fHkQ==", + "path": "system.formats.asn1/9.0.8", + "hashPath": "system.formats.asn1.9.0.8.nupkg.sha512" + }, + "System.IdentityModel.Tokens.Jwt/6.35.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-yxGIQd3BFK7F6S62/7RdZk3C/mfwyVxvh6ngd1VYMBmbJ1YZZA9+Ku6suylVtso0FjI0wbElpJ0d27CdsyLpBQ==", + "path": "system.identitymodel.tokens.jwt/6.35.0", + "hashPath": "system.identitymodel.tokens.jwt.6.35.0.nupkg.sha512" + }, + "System.Memory/4.5.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==", + "path": "system.memory/4.5.4", + "hashPath": "system.memory.4.5.4.nupkg.sha512" + }, + "System.Memory.Data/1.0.2": { + "type": "package", + "serviceable": true, + "sha512": "sha512-JGkzeqgBsiZwKJZ1IxPNsDFZDhUvuEdX8L8BDC8N3KOj+6zMcNU28CNN59TpZE/VJYy9cP+5M+sbxtWJx3/xtw==", + "path": "system.memory.data/1.0.2", + "hashPath": "system.memory.data.1.0.2.nupkg.sha512" + }, + "System.Numerics.Vectors/4.5.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==", + "path": "system.numerics.vectors/4.5.0", + "hashPath": "system.numerics.vectors.4.5.0.nupkg.sha512" + }, + "System.Runtime/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "path": "system.runtime/4.3.0", + "hashPath": "system.runtime.4.3.0.nupkg.sha512" + }, + "System.Runtime.Caching/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-E0e03kUp5X2k+UAoVl6efmI7uU7JRBWi5EIdlQ7cr0NpBGjHG4fWII35PgsBY9T4fJQ8E4QPsL0rKksU9gcL5A==", + "path": "system.runtime.caching/6.0.0", + "hashPath": "system.runtime.caching.6.0.0.nupkg.sha512" + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + "path": "system.runtime.compilerservices.unsafe/6.0.0", + "hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512" + }, + "System.Security.AccessControl/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==", + "path": "system.security.accesscontrol/6.0.0", + "hashPath": "system.security.accesscontrol.6.0.0.nupkg.sha512" + }, + "System.Security.Cryptography.Cng/5.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jIMXsKn94T9JY7PvPq/tMfqa6GAaHpElRDpmG+SuL+D3+sTw2M8VhnibKnN8Tq+4JqbPJ/f+BwtLeDMEnzAvRg==", + "path": "system.security.cryptography.cng/5.0.0", + "hashPath": "system.security.cryptography.cng.5.0.0.nupkg.sha512" + }, + "System.Security.Cryptography.ProtectedData/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==", + "path": "system.security.cryptography.protecteddata/6.0.0", + "hashPath": "system.security.cryptography.protecteddata.6.0.0.nupkg.sha512" + }, + "System.Security.Permissions/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", + "path": "system.security.permissions/6.0.0", + "hashPath": "system.security.permissions.6.0.0.nupkg.sha512" + }, + "System.Security.Principal.Windows/5.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==", + "path": "system.security.principal.windows/5.0.0", + "hashPath": "system.security.principal.windows.5.0.0.nupkg.sha512" + }, + "System.Text.Encoding/4.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "path": "system.text.encoding/4.3.0", + "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" + }, + "System.Text.Encoding.CodePages/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==", + "path": "system.text.encoding.codepages/6.0.0", + "hashPath": "system.text.encoding.codepages.6.0.0.nupkg.sha512" + }, + "System.Text.Encodings.Web/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", + "path": "system.text.encodings.web/6.0.0", + "hashPath": "system.text.encodings.web.6.0.0.nupkg.sha512" + }, + "System.Text.Json/9.0.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-mIQir9jBqk0V7X0Nw5hzPJZC8DuGdf+2DS3jAVsr6rq5+/VyH5rza0XGcONJUWBrZ+G6BCwNyjWYd9lncBu48A==", + "path": "system.text.json/9.0.8", + "hashPath": "system.text.json.9.0.8.nupkg.sha512" + }, "System.Threading.RateLimiting/9.0.8": { "type": "package", "serviceable": true, @@ -292,10 +1142,29 @@ "path": "system.threading.ratelimiting/9.0.8", "hashPath": "system.threading.ratelimiting.9.0.8.nupkg.sha512" }, + "System.Threading.Tasks.Extensions/4.5.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "path": "system.threading.tasks.extensions/4.5.4", + "hashPath": "system.threading.tasks.extensions.4.5.4.nupkg.sha512" + }, + "System.Windows.Extensions/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", + "path": "system.windows.extensions/6.0.0", + "hashPath": "system.windows.extensions.6.0.0.nupkg.sha512" + }, "Elecciones.Core/1.0.0": { "type": "project", "serviceable": false, "sha512": "" + }, + "Elecciones.Database/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" } } } \ No newline at end of file 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 78a7d65..5202628 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+d78a02a0ebc4c70ea01e48821db963110e7ce280")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f384a640f36be1289d652dc85e78ebdcef30968a")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.csproj.FileListAbsolute.txt b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.csproj.FileListAbsolute.txt index e2fca6c..f605610 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.csproj.FileListAbsolute.txt +++ b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.csproj.FileListAbsolute.txt @@ -13,3 +13,5 @@ E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\obj\Debug\net9.0 E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\obj\Debug\net9.0\refint\Elecciones.Infrastructure.dll E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\obj\Debug\net9.0\Elecciones.Infrastructure.pdb E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\obj\Debug\net9.0\ref\Elecciones.Infrastructure.dll +E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\bin\Debug\net9.0\Elecciones.Database.dll +E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\bin\Debug\net9.0\Elecciones.Database.pdb diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.dgspec.json b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.dgspec.json index 3924bca..9e1f16b 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.dgspec.json +++ b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.dgspec.json @@ -69,14 +69,14 @@ } } }, - "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Infrastructure\\Elecciones.Infrastructure.csproj": { + "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Database\\Elecciones.Database.csproj": { "version": "1.0.0", "restore": { - "projectUniqueName": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Infrastructure\\Elecciones.Infrastructure.csproj", - "projectName": "Elecciones.Infrastructure", - "projectPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Infrastructure\\Elecciones.Infrastructure.csproj", + "projectUniqueName": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Database\\Elecciones.Database.csproj", + "projectName": "Elecciones.Database", + "projectPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Database\\Elecciones.Database.csproj", "packagesPath": "C:\\Users\\dmolinari\\.nuget\\packages\\", - "outputPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Infrastructure\\obj\\", + "outputPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Database\\obj\\", "projectStyle": "PackageReference", "fallbackFolders": [ "D:\\Microsoft\\VisualStudio\\Microsoft Visual Studio\\Shared\\NuGetPackages" @@ -115,6 +115,90 @@ }, "SdkAnalysisLevel": "9.0.300" }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "dependencies": { + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[9.0.8, )" + }, + "Microsoft.EntityFrameworkCore.SqlServer": { + "target": "Package", + "version": "[9.0.8, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.300/PortableRuntimeIdentifierGraph.json" + } + } + }, + "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Infrastructure\\Elecciones.Infrastructure.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Infrastructure\\Elecciones.Infrastructure.csproj", + "projectName": "Elecciones.Infrastructure", + "projectPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Infrastructure\\Elecciones.Infrastructure.csproj", + "packagesPath": "C:\\Users\\dmolinari\\.nuget\\packages\\", + "outputPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Infrastructure\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "D:\\Microsoft\\VisualStudio\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\dmolinari\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Core\\Elecciones.Core.csproj": { + "projectPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Core\\Elecciones.Core.csproj" + }, + "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Database\\Elecciones.Database.csproj": { + "projectPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Database\\Elecciones.Database.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, "frameworks": { "net9.0": { "targetAlias": "net9.0", @@ -127,6 +211,10 @@ "target": "Package", "version": "[9.0.8, )" }, + "Serilog": { + "target": "Package", + "version": "[4.3.0, )" + }, "System.Threading.RateLimiting": { "target": "Package", "version": "[9.0.8, )" diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.g.props b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.g.props index 438e8fa..9d03a18 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.g.props +++ b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.g.props @@ -13,4 +13,7 @@ + + + \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.g.targets b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.g.targets index a20fd89..890be06 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.g.targets +++ b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Elecciones.Infrastructure.csproj.nuget.g.targets @@ -1,6 +1,8 @@  + + diff --git a/Elecciones-Web/src/Elecciones.Worker/CriticalDataWorker.cs b/Elecciones-Web/src/Elecciones.Worker/CriticalDataWorker.cs index a0ce962..7af492b 100644 --- a/Elecciones-Web/src/Elecciones.Worker/CriticalDataWorker.cs +++ b/Elecciones-Web/src/Elecciones.Worker/CriticalDataWorker.cs @@ -1,3 +1,4 @@ +//Elecciones.Worker/CriticalDataWorker.cs using Elecciones.Database; using Elecciones.Database.Entities; using Elecciones.Infrastructure.Services; @@ -12,17 +13,20 @@ public class CriticalDataWorker : BackgroundService private readonly SharedTokenService _tokenService; private readonly IServiceProvider _serviceProvider; private readonly IElectoralApiService _apiService; + private readonly WorkerConfigService _configService; public CriticalDataWorker( ILogger logger, SharedTokenService tokenService, IServiceProvider serviceProvider, - IElectoralApiService apiService) + IElectoralApiService apiService, + WorkerConfigService configService) { _logger = logger; _tokenService = tokenService; _serviceProvider = serviceProvider; _apiService = apiService; + _configService = configService; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -50,9 +54,25 @@ public class CriticalDataWorker : BackgroundService continue; } - await SondearResultadosMunicipalesAsync(authToken, stoppingToken); - await SondearResumenProvincialAsync(authToken, stoppingToken); - await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken); + var settings = await _configService.GetSettingsAsync(); + + if (settings.Prioridad == "Resultados" && settings.ResultadosActivado) + { + _logger.LogInformation("Ejecutando tareas de Resultados en alta prioridad."); + await SondearResultadosMunicipalesAsync(authToken, stoppingToken); + await SondearResumenProvincialAsync(authToken, stoppingToken); + await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken); + } + else if (settings.Prioridad == "Telegramas" && settings.BajasActivado) + { + _logger.LogInformation("Ejecutando tareas de Baja Prioridad en alta prioridad."); + await SondearProyeccionBancasAsync(authToken, stoppingToken); + await SondearNuevosTelegramasAsync(authToken, stoppingToken); + } + else + { + _logger.LogInformation("Worker de alta prioridad inactivo según la configuración."); + } var cicloFin = DateTime.UtcNow; var duracionCiclo = cicloFin - cicloInicio; @@ -69,6 +89,252 @@ public class CriticalDataWorker : BackgroundService } } + /// + /// Sondea la proyección de bancas a nivel Provincial y por Sección Electoral. + /// Esta versión es completamente robusta: maneja respuestas de API vacías o con fechas mal formadas, + /// guarda la CategoriaId y usa una transacción atómica para la escritura en base de datos. + /// + /// El token de autenticación válido para la sesión. + /// El token de cancelación para detener la operación. + private async Task SondearProyeccionBancasAsync(string authToken, CancellationToken stoppingToken) + { + try + { + using var scope = _serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var categoriasDeBancas = await dbContext.CategoriasElectorales + .AsNoTracking() + .Where(c => c.Nombre.Contains("SENADORES") || c.Nombre.Contains("DIPUTADOS")) + .ToListAsync(stoppingToken); + + var provincia = await dbContext.AmbitosGeograficos + .AsNoTracking() + .FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken); + + var seccionesElectorales = await dbContext.AmbitosGeograficos + .AsNoTracking() + .Where(a => a.NivelId == 20 && a.DistritoId != null && a.SeccionProvincialId != null) + .ToListAsync(stoppingToken); + + if (!categoriasDeBancas.Any() || provincia == null) + { + _logger.LogWarning("No se encontraron categorías de bancas o el ámbito provincial en la BD. Omitiendo sondeo de bancas."); + return; + } + + _logger.LogInformation("Iniciando sondeo de Bancas a nivel Provincial y para {count} Secciones Electorales...", seccionesElectorales.Count); + + var todasLasProyecciones = new List(); + bool hasReceivedAnyNewData = false; + + // Bucle para el nivel Provincial + foreach (var categoria in categoriasDeBancas) + { + if (stoppingToken.IsCancellationRequested) break; + var repartoBancasDto = await _apiService.GetBancasAsync(authToken, provincia.DistritoId!, null, categoria.Id); + + if (repartoBancasDto?.RepartoBancas is { Count: > 0 } bancas) + { + hasReceivedAnyNewData = true; + + // --- SEGURIDAD: Usar TryParse para la fecha --- + DateTime fechaTotalizacion; + if (!DateTime.TryParse(repartoBancasDto.FechaTotalizacion, out var parsedDate)) + { + // Si la fecha es inválida (nula, vacía, mal formada), lo registramos y usamos la hora actual como respaldo. + _logger.LogWarning("No se pudo parsear FechaTotalizacion ('{dateString}') para bancas provinciales. Usando la hora actual.", repartoBancasDto.FechaTotalizacion); + fechaTotalizacion = DateTime.UtcNow; + } + else + { + fechaTotalizacion = parsedDate.ToUniversalTime(); + } + + foreach (var banca in bancas) + { + todasLasProyecciones.Add(new ProyeccionBanca + { + AmbitoGeograficoId = provincia.Id, + AgrupacionPoliticaId = banca.IdAgrupacion, + NroBancas = banca.NroBancas, + CategoriaId = categoria.Id, + FechaTotalizacion = fechaTotalizacion + }); + } + } + } + + // Bucle para el nivel de Sección Electoral + foreach (var seccion in seccionesElectorales) + { + if (stoppingToken.IsCancellationRequested) break; + foreach (var categoria in categoriasDeBancas) + { + if (stoppingToken.IsCancellationRequested) break; + var repartoBancasDto = await _apiService.GetBancasAsync(authToken, seccion.DistritoId!, seccion.SeccionProvincialId!, categoria.Id); + + if (repartoBancasDto?.RepartoBancas is { Count: > 0 } bancas) + { + hasReceivedAnyNewData = true; + + // --- APLICAMOS LA MISMA SEGURIDAD AQUÍ --- + DateTime fechaTotalizacion; + if (!DateTime.TryParse(repartoBancasDto.FechaTotalizacion, out var parsedDate)) + { + _logger.LogWarning("No se pudo parsear FechaTotalizacion ('{dateString}') para bancas de sección. Usando la hora actual.", repartoBancasDto.FechaTotalizacion); + fechaTotalizacion = DateTime.UtcNow; + } + else + { + fechaTotalizacion = parsedDate.ToUniversalTime(); + } + + foreach (var banca in bancas) + { + todasLasProyecciones.Add(new ProyeccionBanca + { + AmbitoGeograficoId = seccion.Id, + AgrupacionPoliticaId = banca.IdAgrupacion, + NroBancas = banca.NroBancas, + CategoriaId = categoria.Id, + FechaTotalizacion = fechaTotalizacion + }); + } + } + } + } + + if (hasReceivedAnyNewData) + { + _logger.LogInformation("Se recibieron datos válidos de bancas. Procediendo a actualizar la base de datos..."); + await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken); + + await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ProyeccionesBancas", stoppingToken); + await dbContext.ProyeccionesBancas.AddRangeAsync(todasLasProyecciones, stoppingToken); + await dbContext.SaveChangesAsync(stoppingToken); + await transaction.CommitAsync(stoppingToken); + + _logger.LogInformation("La tabla de proyecciones ha sido actualizada con {count} registros.", todasLasProyecciones.Count); + } + else + { + _logger.LogInformation("Sondeo de Bancas completado. No se encontraron datos nuevos de proyección, la tabla no fue modificada."); + } + } + catch (OperationCanceledException) + { + _logger.LogInformation("Sondeo de bancas cancelado."); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Bancas."); + } + } + + /// + /// Busca y descarga nuevos telegramas de forma masiva y concurrente. + /// Este método crea una lista de todas las combinaciones de Partido/Categoría, + /// las consulta a la API con un grado de paralelismo controlado, y cada tarea concurrente + /// maneja su propia lógica de descarga y guardado en la base de datos. + /// + /// El token de autenticación válido para la sesión. + /// El token de cancelación para detener la operación. + private async Task SondearNuevosTelegramasAsync(string authToken, CancellationToken stoppingToken) + { + try + { + _logger.LogInformation("--- Iniciando sondeo de Nuevos Telegramas (modo de bajo perfil) ---"); + + using var scope = _serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var partidos = await dbContext.AmbitosGeograficos + .AsNoTracking() + .Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null) + .ToListAsync(stoppingToken); + + var categorias = await dbContext.CategoriasElectorales + .AsNoTracking() + .ToListAsync(stoppingToken); + + if (!partidos.Any() || !categorias.Any()) return; + + foreach (var partido in partidos) + { + foreach (var categoria in categorias) + { + if (stoppingToken.IsCancellationRequested) return; + + var listaTelegramasApi = await _apiService.GetTelegramasTotalizadosAsync(authToken, partido.DistritoId!, partido.SeccionId!, categoria.Id); + + if (listaTelegramasApi is { Count: > 0 }) + { + using var innerScope = _serviceProvider.CreateScope(); + var innerDbContext = innerScope.ServiceProvider.GetRequiredService(); + + var idsYaEnDb = await innerDbContext.Telegramas + .Where(t => listaTelegramasApi.Contains(t.Id)) + .Select(t => t.Id) + .ToListAsync(stoppingToken); + + var nuevosTelegramasIds = listaTelegramasApi.Except(idsYaEnDb).ToList(); + + if (nuevosTelegramasIds.Any()) + { + _logger.LogInformation("Se encontraron {count} telegramas nuevos en '{partido}' para '{cat}'. Descargando...", nuevosTelegramasIds.Count, partido.Nombre, categoria.Nombre); + + foreach (var mesaId in nuevosTelegramasIds) + { + if (stoppingToken.IsCancellationRequested) return; + + var telegramaFile = await _apiService.GetTelegramaFileAsync(authToken, mesaId); + if (telegramaFile != null) + { + // 1. Buscamos el AmbitoGeografico específico de la MESA que estamos procesando. + var ambitoMesa = await innerDbContext.AmbitosGeograficos + .AsNoTracking() + .FirstOrDefaultAsync(a => a.MesaId == mesaId, stoppingToken); + + // 2. Solo guardamos el telegrama si encontramos su ámbito de mesa correspondiente. + if (ambitoMesa != null) + { + var nuevoTelegrama = new Telegrama + { + Id = telegramaFile.NombreArchivo, + // 3. Usamos el ID del ÁMBITO DE LA MESA, no el del municipio. + AmbitoGeograficoId = ambitoMesa.Id, + ContenidoBase64 = telegramaFile.Imagen, + FechaEscaneo = DateTime.Parse(telegramaFile.FechaEscaneo).ToUniversalTime(), + FechaTotalizacion = DateTime.Parse(telegramaFile.FechaTotalizacion).ToUniversalTime() + }; + await innerDbContext.Telegramas.AddAsync(nuevoTelegrama, stoppingToken); + } + else + { + _logger.LogWarning("No se encontró un ámbito geográfico para la mesa con MesaId {MesaId}. El telegrama no será guardado.", mesaId); + } + } + await Task.Delay(250, stoppingToken); + } + await innerDbContext.SaveChangesAsync(stoppingToken); + } + } + await Task.Delay(100, stoppingToken); + } + } + _logger.LogInformation("Sondeo de Telegramas completado."); + } + catch (OperationCanceledException) + { + _logger.LogInformation("Sondeo de telegramas cancelado."); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Telegramas."); + } + } + private async Task SondearResultadosMunicipalesAsync(string authToken, CancellationToken stoppingToken) { try diff --git a/Elecciones-Web/src/Elecciones.Worker/LowPriorityDataWorker.cs b/Elecciones-Web/src/Elecciones.Worker/LowPriorityDataWorker.cs index 5158a6e..33dca54 100644 --- a/Elecciones-Web/src/Elecciones.Worker/LowPriorityDataWorker.cs +++ b/Elecciones-Web/src/Elecciones.Worker/LowPriorityDataWorker.cs @@ -1,3 +1,4 @@ +//Elecciones.Worker/LowPriorityDataWorker.cs using Elecciones.Database; using Elecciones.Database.Entities; using Elecciones.Infrastructure.Services; @@ -11,6 +12,7 @@ public class LowPriorityDataWorker : BackgroundService private readonly SharedTokenService _tokenService; private readonly IServiceProvider _serviceProvider; private readonly IElectoralApiService _apiService; + private readonly WorkerConfigService _configService; // Una variable para rastrear la tarea de telegramas, si está en ejecución. private Task? _telegramasTask; @@ -19,12 +21,14 @@ public class LowPriorityDataWorker : BackgroundService ILogger logger, SharedTokenService tokenService, IServiceProvider serviceProvider, - IElectoralApiService apiService) + IElectoralApiService apiService, + WorkerConfigService configService) { _logger = logger; _tokenService = tokenService; _serviceProvider = serviceProvider; _apiService = apiService; + _configService = configService; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -46,30 +50,25 @@ public class LowPriorityDataWorker : BackgroundService continue; } - // --- LÓGICA DE EJECUCIÓN INDEPENDIENTE --- + var settings = await _configService.GetSettingsAsync(); - // 1. TAREA DE BANCAS: Siempre se ejecuta y se espera. Es rápida. - _logger.LogInformation("Iniciando sondeo de Bancas..."); - await SondearProyeccionBancasAsync(authToken, stoppingToken); - _logger.LogInformation("Sondeo de Bancas completado."); - - // 2. TAREA DE TELEGRAMAS: "Dispara y Olvida" de forma segura. - // Comprobamos si la tarea anterior de telegramas ya ha terminado. - if (_telegramasTask == null || _telegramasTask.IsCompleted) + if (settings.Prioridad == "Telegramas" && settings.ResultadosActivado) { - _logger.LogInformation("Iniciando sondeo de Telegramas en segundo plano..."); - // Lanzamos la tarea de telegramas pero NO la esperamos con 'await'. - // Guardamos una referencia a la tarea en nuestra variable de estado. - _telegramasTask = SondearNuevosTelegramasAsync(authToken, stoppingToken); + _logger.LogInformation("Ejecutando tareas de Resultados en baja prioridad."); + await SondearResultadosMunicipalesAsync(authToken, stoppingToken); + await SondearResumenProvincialAsync(authToken, stoppingToken); + await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken); + } + else if (settings.Prioridad == "Resultados" && settings.BajasActivado) + { + _logger.LogInformation("Ejecutando tareas de Baja Prioridad en baja prioridad."); + await SondearProyeccionBancasAsync(authToken, stoppingToken); + await SondearNuevosTelegramasAsync(authToken, stoppingToken); } else { - // Si la descarga anterior todavía está en curso, nos saltamos este sondeo - // para no acumular tareas y sobrecargar el sistema. - _logger.LogInformation("El sondeo de telegramas anterior sigue en ejecución. Se omitirá en este ciclo."); + _logger.LogInformation("Worker de baja prioridad inactivo según la configuración."); } - - _logger.LogInformation("--- Ciclo de Datos de Baja Prioridad completado. Esperando 5 minutos. ---"); try { await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); @@ -81,6 +80,337 @@ public class LowPriorityDataWorker : BackgroundService } } + private async Task SondearResultadosMunicipalesAsync(string authToken, CancellationToken stoppingToken) + { + try + { + using var scope = _serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var municipiosASondear = await dbContext.AmbitosGeograficos + .AsNoTracking() + .Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null) + .ToListAsync(stoppingToken); + + var todasLasCategorias = await dbContext.CategoriasElectorales + .AsNoTracking() + .ToListAsync(stoppingToken); + + if (!municipiosASondear.Any() || !todasLasCategorias.Any()) + { + _logger.LogWarning("No se encontraron Partidos (NivelId 30) o Categorías para sondear resultados."); + return; + } + + _logger.LogInformation("Iniciando sondeo de resultados para {m} municipios y {c} categorías...", municipiosASondear.Count, todasLasCategorias.Count); + + foreach (var municipio in municipiosASondear) + { + if (stoppingToken.IsCancellationRequested) break; + + var tareasCategoria = todasLasCategorias.Select(async categoria => + { + var resultados = await _apiService.GetResultadosAsync(authToken, municipio.DistritoId!, municipio.SeccionId!, null, categoria.Id); + + if (resultados != null) + { + using var innerScope = _serviceProvider.CreateScope(); + var innerDbContext = innerScope.ServiceProvider.GetRequiredService(); + + // --- LLAMADA CORRECTA --- + await GuardarResultadosDeAmbitoAsync(innerDbContext, municipio.Id, categoria.Id, resultados, stoppingToken); + } + }); + + await Task.WhenAll(tareasCategoria); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Ocurrió un error inesperado durante el sondeo de resultados municipales."); + } + } + + private async Task GuardarResultadosDeAmbitoAsync( + EleccionesDbContext dbContext, int ambitoId, int categoriaId, + Elecciones.Core.DTOs.ResultadosDto resultadosDto, CancellationToken stoppingToken) + { + var estadoRecuento = await dbContext.EstadosRecuentos.FindAsync(new object[] { ambitoId, categoriaId }, stoppingToken); + + if (estadoRecuento == null) + { + estadoRecuento = new EstadoRecuento { AmbitoGeograficoId = ambitoId, CategoriaId = categoriaId }; + dbContext.EstadosRecuentos.Add(estadoRecuento); + } + + estadoRecuento.FechaTotalizacion = DateTime.Parse(resultadosDto.FechaTotalizacion).ToUniversalTime(); + estadoRecuento.MesasEsperadas = resultadosDto.EstadoRecuento.MesasEsperadas; + estadoRecuento.MesasTotalizadas = resultadosDto.EstadoRecuento.MesasTotalizadas; + estadoRecuento.MesasTotalizadasPorcentaje = resultadosDto.EstadoRecuento.MesasTotalizadasPorcentaje; + estadoRecuento.CantidadElectores = resultadosDto.EstadoRecuento.CantidadElectores; + estadoRecuento.CantidadVotantes = resultadosDto.EstadoRecuento.CantidadVotantes; + estadoRecuento.ParticipacionPorcentaje = resultadosDto.EstadoRecuento.ParticipacionPorcentaje; + + if (resultadosDto.ValoresTotalizadosOtros != null) + { + estadoRecuento.VotosEnBlanco = resultadosDto.ValoresTotalizadosOtros.VotosEnBlanco; + estadoRecuento.VotosEnBlancoPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosEnBlancoPorcentaje; + estadoRecuento.VotosNulos = resultadosDto.ValoresTotalizadosOtros.VotosNulos; + estadoRecuento.VotosNulosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosNulosPorcentaje; + estadoRecuento.VotosRecurridos = resultadosDto.ValoresTotalizadosOtros.VotosRecurridos; + estadoRecuento.VotosRecurridosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosRecurridosPorcentaje; + } + + foreach (var votoPositivoDto in resultadosDto.ValoresTotalizadosPositivos) + { + var resultadoVoto = await dbContext.ResultadosVotos.FirstOrDefaultAsync( + rv => rv.AmbitoGeograficoId == ambitoId && + rv.CategoriaId == categoriaId && + rv.AgrupacionPoliticaId == votoPositivoDto.IdAgrupacion, + stoppingToken + ); + + if (resultadoVoto == null) + { + resultadoVoto = new ResultadoVoto + { + AmbitoGeograficoId = ambitoId, + CategoriaId = categoriaId, + AgrupacionPoliticaId = votoPositivoDto.IdAgrupacion + }; + dbContext.ResultadosVotos.Add(resultadoVoto); + } + resultadoVoto.CantidadVotos = votoPositivoDto.Votos; + resultadoVoto.PorcentajeVotos = votoPositivoDto.VotosPorcentaje; + } + + try + { + await dbContext.SaveChangesAsync(stoppingToken); + } + catch (DbUpdateException ex) + { + _logger.LogError(ex, "DbUpdateException al guardar resultados para AmbitoId {ambitoId} y CategoriaId {categoriaId}", ambitoId, categoriaId); + } + } + + /// + /// Obtiene y actualiza el resumen de votos y el estado del recuento a nivel provincial. + /// Esta versión actualizada guarda tanto los votos por agrupación (en ResumenesVotos) + /// como el estado general del recuento, incluyendo la fecha de totalización (en EstadosRecuentosGenerales), + /// asegurando que toda la operación sea atómica mediante una transacción de base de datos. + /// + /// El token de autenticación válido para la sesión. + /// El token de cancelación para detener la operación. + private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken) + { + try + { + // Creamos un scope de DbContext para esta operación. + using var scope = _serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + // Obtenemos el registro de la Provincia (NivelId 10). + var provincia = await dbContext.AmbitosGeograficos + .AsNoTracking() + .FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken); + + // Si no encontramos el ámbito de la provincia, no podemos continuar. + if (provincia == null) + { + _logger.LogWarning("No se encontró el ámbito 'Provincia' (NivelId 10) para el sondeo de resumen."); + return; + } + + // Llamamos a la API para obtener el resumen de datos provincial. + var resumenDto = await _apiService.GetResumenAsync(authToken, provincia.DistritoId!); + + // Solo procedemos si la API devolvió una respuesta válida y no nula. + if (resumenDto != null) + { + // Iniciamos una transacción explícita. Esto garantiza que todas las operaciones de base de datos + // dentro de este bloque (el DELETE, los INSERTs y los UPDATEs) se completen con éxito, + // o si algo falla, se reviertan todas, manteniendo la consistencia de los datos. + await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken); + + // --- 1. ACTUALIZAR LA TABLA 'ResumenesVotos' --- + + // Verificamos si la respuesta contiene una lista de votos positivos. + if (resumenDto.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos) + { + // Estrategia "Borrar y Reemplazar": vaciamos la tabla antes de insertar los nuevos datos. + await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ResumenesVotos", stoppingToken); + + // Añadimos cada nuevo registro de voto al DbContext. + foreach (var voto in nuevosVotos) + { + dbContext.ResumenesVotos.Add(new ResumenVoto + { + AmbitoGeograficoId = provincia.Id, + AgrupacionPoliticaId = voto.IdAgrupacion, + Votos = voto.Votos, + VotosPorcentaje = voto.VotosPorcentaje + }); + } + } + + // --- 2. ACTUALIZAR LA TABLA 'EstadosRecuentosGenerales' --- + + // El endpoint de Resumen no especifica una categoría, por lo que aplicamos sus datos de estado de recuento + // a todas las categorías que tenemos en nuestra base de datos. + var todasLasCategorias = await dbContext.CategoriasElectorales.AsNoTracking().ToListAsync(stoppingToken); + foreach (var categoria in todasLasCategorias) + { + // Buscamos el registro existente usando la clave primaria compuesta. + var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { provincia.Id, categoria.Id }, stoppingToken); + + // Si no existe, lo creamos. + if (registroDb == null) + { + registroDb = new EstadoRecuentoGeneral { AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id }; + dbContext.EstadosRecuentosGenerales.Add(registroDb); + } + + // Parseamos la fecha de forma segura para evitar errores con cadenas vacías o nulas. + if (DateTime.TryParse(resumenDto.FechaTotalizacion, out var parsedDate)) + { + registroDb.FechaTotalizacion = parsedDate.ToUniversalTime(); + } + + // Mapeamos el resto de los datos del estado del recuento. + registroDb.MesasEsperadas = resumenDto.EstadoRecuento.MesasEsperadas; + registroDb.MesasTotalizadas = resumenDto.EstadoRecuento.MesasTotalizadas; + registroDb.MesasTotalizadasPorcentaje = resumenDto.EstadoRecuento.MesasTotalizadasPorcentaje; + registroDb.CantidadElectores = resumenDto.EstadoRecuento.CantidadElectores; + registroDb.CantidadVotantes = resumenDto.EstadoRecuento.CantidadVotantes; + registroDb.ParticipacionPorcentaje = resumenDto.EstadoRecuento.ParticipacionPorcentaje; + } + + // 3. CONFIRMAR Y GUARDAR + // Guardamos todos los cambios preparados (DELETEs, INSERTs, UPDATEs) en la base de datos. + await dbContext.SaveChangesAsync(stoppingToken); + // Confirmamos la transacción para hacer los cambios permanentes. + await transaction.CommitAsync(stoppingToken); + + _logger.LogInformation("Sondeo de Resumen Provincial completado. Las tablas han sido actualizadas."); + } + else + { + // Si la API no devolvió datos (ej. devuelve null), no hacemos nada en la BD. + _logger.LogInformation("Sondeo de Resumen Provincial completado. No se recibieron datos nuevos."); + } + } + catch (OperationCanceledException) + { + _logger.LogInformation("Sondeo de resumen provincial cancelado."); + } + catch (Exception ex) + { + // Capturamos cualquier otro error inesperado para que el worker no se detenga. + _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Resumen Provincial."); + } + } + + /// + /// Obtiene y actualiza el estado general del recuento a nivel provincial para CADA categoría electoral. + /// Esta versión es robusta: consulta dinámicamente las categorías, usa la clave primaria compuesta + /// de la base de datos y guarda todos los cambios en una única transacción al final. + /// + /// El token de autenticación válido para la sesión. + /// El token de cancelación para detener la operación. + private async Task SondearEstadoRecuentoGeneralAsync(string authToken, CancellationToken stoppingToken) + { + try + { + // PASO 1: Crear un "scope" para obtener una instancia fresca de DbContext. + // Esto es una práctica recomendada para servicios de larga duración para evitar problemas de concurrencia. + using var scope = _serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + // PASO 2: Obtener el ámbito geográfico de la Provincia. + // Necesitamos este objeto para obtener su 'DistritoId' ("02"), que es requerido por la API. + var provincia = await dbContext.AmbitosGeograficos + .AsNoTracking() // Optimización: Solo necesitamos leer datos, no modificarlos. + .FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken); + + // Comprobación de seguridad: Si la sincronización inicial falló y no tenemos el registro de la provincia, + // no podemos continuar. Registramos una advertencia y salimos del método. + if (provincia == null) + { + _logger.LogWarning("No se encontró el ámbito 'Provincia' (NivelId 10) en la BD. Omitiendo sondeo de estado general."); + return; + } + + // PASO 3: Obtener todas las categorías electorales disponibles desde nuestra base de datos. + // Esto hace que el método sea dinámico y no dependa de IDs fijos en el código. + var categoriasParaSondear = await dbContext.CategoriasElectorales + .AsNoTracking() + .ToListAsync(stoppingToken); + + if (!categoriasParaSondear.Any()) + { + _logger.LogWarning("No hay categorías en la BD para sondear el estado general del recuento."); + return; + } + + _logger.LogInformation("Iniciando sondeo de Estado Recuento General para {count} categorías...", categoriasParaSondear.Count); + + // PASO 4: Iterar sobre cada categoría para obtener su estado de recuento individual. + foreach (var categoria in categoriasParaSondear) + { + // Salimos limpiamente del bucle si la aplicación se está deteniendo. + if (stoppingToken.IsCancellationRequested) break; + + // Llamamos a la API con el distrito y la CATEGORÍA ACTUAL del bucle. + var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id); + + // Solo procedemos si la API devolvió datos válidos. + if (estadoDto != null) + { + // Lógica "Upsert" (Update or Insert): + // Buscamos un registro existente usando la CLAVE PRIMARIA COMPUESTA. + var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync( + new object[] { provincia.Id, categoria.Id }, + cancellationToken: stoppingToken + ); + + // Si no se encuentra (FindAsync devuelve null), es un registro nuevo. + if (registroDb == null) + { + // Creamos una nueva instancia de la entidad. + registroDb = new EstadoRecuentoGeneral + { + AmbitoGeograficoId = provincia.Id, + CategoriaId = categoria.Id // Asignamos ambas partes de la clave primaria. + }; + // Y la añadimos al ChangeTracker de EF para que la inserte en la BD. + dbContext.EstadosRecuentosGenerales.Add(registroDb); + } + + // Mapeamos los datos del DTO de la API a nuestra entidad de base de datos. + // Esto se hace tanto para registros nuevos como para los existentes que se van a actualizar. + registroDb.MesasEsperadas = estadoDto.MesasEsperadas; + registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas; + registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje; + registroDb.CantidadElectores = estadoDto.CantidadElectores; + registroDb.CantidadVotantes = estadoDto.CantidadVotantes; + registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje; + } + } + + // PASO 5: Guardar todos los cambios en la base de datos. + // Al llamar a SaveChangesAsync UNA SOLA VEZ fuera del bucle, EF Core agrupa + // todas las inserciones y actualizaciones en una única transacción eficiente. + await dbContext.SaveChangesAsync(stoppingToken); + _logger.LogInformation("Sondeo de Estado Recuento General completado para todas las categorías."); + } + catch (Exception ex) + { + // Capturamos cualquier excepción inesperada para que no detenga el worker y la registramos. + _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General."); + } + } + /// /// Descarga y sincroniza los catálogos base (Categorías, Ámbitos, Agrupaciones) /// desde la API a la base de datos local. Se ejecuta una sola vez al iniciar el worker. @@ -286,7 +616,7 @@ public class LowPriorityDataWorker : BackgroundService { hasReceivedAnyNewData = true; - // --- CORRECCIÓN DE SEGURIDAD: Usar TryParse para la fecha --- + // --- SEGURIDAD: Usar TryParse para la fecha --- DateTime fechaTotalizacion; if (!DateTime.TryParse(repartoBancasDto.FechaTotalizacion, out var parsedDate)) { @@ -326,7 +656,7 @@ public class LowPriorityDataWorker : BackgroundService { hasReceivedAnyNewData = true; - // --- APLICAMOS LA MISMA CORRECCIÓN DE SEGURIDAD AQUÍ --- + // --- APLICAMOS SEGURIDAD AQUÍ --- DateTime fechaTotalizacion; if (!DateTime.TryParse(repartoBancasDto.FechaTotalizacion, out var parsedDate)) { @@ -439,7 +769,6 @@ public class LowPriorityDataWorker : BackgroundService var telegramaFile = await _apiService.GetTelegramaFileAsync(authToken, mesaId); if (telegramaFile != null) { - // --- INICIO DE LA CORRECCIÓN --- // 1. Buscamos el AmbitoGeografico específico de la MESA que estamos procesando. var ambitoMesa = await innerDbContext.AmbitosGeograficos .AsNoTracking() @@ -463,7 +792,6 @@ public class LowPriorityDataWorker : BackgroundService { _logger.LogWarning("No se encontró un ámbito geográfico para la mesa con MesaId {MesaId}. El telegrama no será guardado.", mesaId); } - // --- FIN DE LA CORRECCIÓN --- } await Task.Delay(250, stoppingToken); } diff --git a/Elecciones-Web/src/Elecciones.Worker/Program.cs b/Elecciones-Web/src/Elecciones.Worker/Program.cs index 1006b47..572067b 100644 --- a/Elecciones-Web/src/Elecciones.Worker/Program.cs +++ b/Elecciones-Web/src/Elecciones.Worker/Program.cs @@ -1,12 +1,10 @@ +//Elecciones.Worker/Program.cs using Elecciones.Database; using Elecciones.Infrastructure.Services; using Elecciones.Worker; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using System.Net; using Serilog; -using System.Net.Http; using System.Net.Security; using System.Security.Authentication; using Polly; @@ -21,12 +19,20 @@ Log.Information("Iniciando Elecciones.Worker Host..."); var builder = Host.CreateApplicationBuilder(args); -builder.Services.AddSerilog(config => - config +// 1. Registra el servicio del interruptor como siempre. +builder.Services.AddSingleton(); + +// 2. Configura Serilog usando AddSerilog. +builder.Services.AddSerilog((services, configuration) => { + var loggingSwitch = services.GetRequiredService(); + configuration .ReadFrom.Configuration(builder.Configuration) + .ReadFrom.Services(services) .Enrich.FromLogContext() + .MinimumLevel.ControlledBy(loggingSwitch.LevelSwitch) .WriteTo.Console() - .WriteTo.File("logs/worker-.log", rollingInterval: RollingInterval.Day)); + .WriteTo.File("logs/worker-.log", rollingInterval: RollingInterval.Day); +}); // --- Configuración de Servicios --- var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); @@ -93,16 +99,47 @@ builder.Services.AddSingleton(sp => QueueProcessingOrder = QueueProcessingOrder.OldestFirst })); -builder.Services.AddScoped(); +builder.Services.AddScoped(); // Registramos el servicio de token como un Singleton para que sea compartido. builder.Services.AddSingleton(); +// Registramos el servicio de configuraciones de workers como un Singleton para que sea compartido. +builder.Services.AddSingleton(); // Registramos ambos workers. El framework se encargará de iniciarlos y detenerlos. builder.Services.AddHostedService(); builder.Services.AddHostedService(); //builder.Services.AddHostedService(); +// --- LÓGICA PARA LEER EL NIVEL DE LOGGING AL INICIO --- +// Creamos un scope temporal para leer la configuración de la BD +using (var scope = builder.Services.BuildServiceProvider().CreateScope()) +{ + var services = scope.ServiceProvider; + try + { + var dbContext = services.GetRequiredService(); + var loggingSwitchService = services.GetRequiredService(); + + // Buscamos el nivel de logging guardado en la BD + var logLevelConfig = await dbContext.Configuraciones + .AsNoTracking() + .FirstOrDefaultAsync(c => c.Clave == "Logging_Level"); + + if (logLevelConfig != null) + { + // Si lo encontramos, lo aplicamos al interruptor + loggingSwitchService.SetLoggingLevel(logLevelConfig.Valor); + Console.WriteLine($"--> Nivel de logging inicial establecido desde la BD a: {logLevelConfig.Valor}"); + } + } + catch (Exception ex) + { + // Si hay un error (ej. la BD no está disponible al arrancar), se usará el nivel por defecto 'Information'. + Console.WriteLine($"--> No se pudo establecer el nivel de logging desde la BD: {ex.Message}"); + } +} + var host = builder.Build(); try diff --git a/Elecciones-Web/src/Elecciones.Worker/obj/Debug/net9.0/Elecciones.Worker.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Worker/obj/Debug/net9.0/Elecciones.Worker.AssemblyInfo.cs index d23efa7..5691888 100644 --- a/Elecciones-Web/src/Elecciones.Worker/obj/Debug/net9.0/Elecciones.Worker.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Worker/obj/Debug/net9.0/Elecciones.Worker.AssemblyInfo.cs @@ -14,7 +14,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Worker")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+55954e18a797dce22f76f00b645832f361d97362")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f384a640f36be1289d652dc85e78ebdcef30968a")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Worker")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Worker")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Elecciones-Web/src/Elecciones.Worker/obj/Elecciones.Worker.csproj.nuget.dgspec.json b/Elecciones-Web/src/Elecciones.Worker/obj/Elecciones.Worker.csproj.nuget.dgspec.json index f8a0b75..87d57fb 100644 --- a/Elecciones-Web/src/Elecciones.Worker/obj/Elecciones.Worker.csproj.nuget.dgspec.json +++ b/Elecciones-Web/src/Elecciones.Worker/obj/Elecciones.Worker.csproj.nuget.dgspec.json @@ -180,6 +180,9 @@ "projectReferences": { "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Core\\Elecciones.Core.csproj": { "projectPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Core\\Elecciones.Core.csproj" + }, + "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Database\\Elecciones.Database.csproj": { + "projectPath": "E:\\Elecciones-2025\\Elecciones-Web\\src\\Elecciones.Database\\Elecciones.Database.csproj" } } } @@ -208,6 +211,10 @@ "target": "Package", "version": "[9.0.8, )" }, + "Serilog": { + "target": "Package", + "version": "[4.3.0, )" + }, "System.Threading.RateLimiting": { "target": "Package", "version": "[9.0.8, )" diff --git a/docker-compose.yml b/docker-compose.yml index 61c067c..c07eb25 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,17 +14,17 @@ services: - shared-net # Servicio del Worker (sin cambios) -# elecciones-worker: -# build: -# context: ./Elecciones-Web -# dockerfile: src/Elecciones.Worker/Dockerfile -# container_name: elecciones-worker -# restart: unless-stopped -# env_file: ./.env -# networks: -# - shared-net -# volumes: -# - ./logs-worker:/app/logs + elecciones-worker: + build: + context: ./Elecciones-Web + dockerfile: src/Elecciones.Worker/Dockerfile + container_name: elecciones-worker + restart: unless-stopped + env_file: ./.env + networks: + - shared-net + volumes: + - ./logs-worker:/app/logs # Servicio del Frontend Público (sin cambios) elecciones-frontend: