//Elecciones.Api/Program.cs using Elecciones.Database; using Microsoft.EntityFrameworkCore; using Serilog; using Elecciones.Core.Services; using Elecciones.Infrastructure.Services; using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Elecciones.Database.Entities; using System.Text.Json.Serialization; using Microsoft.AspNetCore.HttpOverrides; using Elecciones.Core.Enums; using Microsoft.OpenApi.Models; // Esta es la estructura estándar y recomendada. var builder = WebApplication.CreateBuilder(args); // 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"); builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); builder.Services.AddScoped(); builder.Services.AddControllers().AddJsonOptions(options => { // Esto le dice al serializador que maneje las referencias circulares options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; }); var MyAllowSpecificOrigins = "_myAllowSpecificOrigins"; builder.Services.AddCors(options => { options.AddPolicy(name: MyAllowSpecificOrigins, policy => { policy.WithOrigins( "http://localhost:5173", "http://localhost:5174", "http://192.168.5.128:8700", "https://www.eldia.com", "https://extras.eldia.com", "https://eldia.mustang.cloud" ) .AllowAnyHeader() .AllowAnyMethod(); }); }); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)) }; }); builder.Services.AddEndpointsApiExplorer(); //builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen(options => { // 1. Definir el esquema de seguridad que usaremos (Bearer Token) options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "Autorización JWT usando el esquema Bearer. Ingresa 'Bearer' [espacio] y luego tu token. Ejemplo: 'Bearer 12345abcdef'", Name = "Authorization", // El nombre del header HTTP In = ParameterLocation.Header, // Dónde se ubicará el token (en el header) Type = SecuritySchemeType.ApiKey, // El tipo de esquema Scheme = "Bearer" // El nombre del esquema }); // 2. Aplicar este requisito de seguridad a todos los endpoints que lo necesiten options.AddSecurityRequirement(new OpenApiSecurityRequirement() { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" // Debe coincidir con el nombre que le dimos en AddSecurityDefinition }, Scheme = "oauth2", Name = "Bearer", In = ParameterLocation.Header, }, new List() } }); }); builder.Services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); // 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(); // --- INICIO DEL BLOQUE DE SEEDERS UNIFICADO Y CORREGIDO --- using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; var context = services.GetRequiredService(); var logger = services.GetRequiredService>(); var hasher = services.GetRequiredService(); // --- PASO 1: Añadir esta bandera de control --- bool generarDatosDeEjemplo = false; // <-- Poner en 'false' para deshabilitar // --- SEEDER 1: DATOS ESTRUCTURALES BÁSICOS (se ejecutan una sola vez si la BD está vacía) --- // Estos son los datos maestros que NUNCA cambian. // Usuario Admin if (!await context.AdminUsers.AnyAsync()) { var (hash, salt) = hasher.HashPassword("PTP847elec"); context.AdminUsers.Add(new AdminUser { Username = "admin", PasswordHash = hash, PasswordSalt = salt }); await context.SaveChangesAsync(); logger.LogInformation("--> Admin user seeded."); } // Elecciones if (!await context.Elecciones.AnyAsync()) { context.Elecciones.AddRange( new Eleccion { Id = 1, Nombre = "Elecciones Provinciales 2025", Nivel = "Provincial", DistritoId = "02", Fecha = new DateOnly(2025, 10, 26) }, new Eleccion { Id = 2, Nombre = "Elecciones Nacionales 2025", Nivel = "Nacional", DistritoId = "00", Fecha = new DateOnly(2025, 10, 26) } ); await context.SaveChangesAsync(); logger.LogInformation("--> Seeded Eleccion entities."); } // Bancas Físicas if (!await context.Bancadas.AnyAsync()) { var bancas = new List(); for (int i = 1; i <= 92; i++) { bancas.Add(new Bancada { EleccionId = 1, Camara = TipoCamara.Diputados, NumeroBanca = i }); } for (int i = 1; i <= 46; i++) { bancas.Add(new Bancada { EleccionId = 1, Camara = TipoCamara.Senadores, NumeroBanca = i }); } for (int i = 1; i <= 257; i++) { bancas.Add(new Bancada { EleccionId = 2, Camara = TipoCamara.Diputados, NumeroBanca = i }); } for (int i = 1; i <= 72; i++) { bancas.Add(new Bancada { EleccionId = 2, Camara = TipoCamara.Senadores, NumeroBanca = i }); } await context.Bancadas.AddRangeAsync(bancas); await context.SaveChangesAsync(); logger.LogInformation("--> Seeded {Count} bancas físicas para ambas elecciones.", bancas.Count); } // Configuraciones por Defecto var defaultConfiguraciones = new Dictionary { { "MostrarOcupantes", "true" }, { "TickerResultadosCantidad", "3" }, { "ConcejalesResultadosCantidad", "5" }, { "Worker_Resultados_Activado", "false" }, { "Worker_Bajas_Activado", "false" }, { "Worker_Prioridad", "Resultados" }, { "Logging_Level", "Information" }, { "PresidenciaDiputadosNacional", "" }, { "PresidenciaDiputadosNacional_TipoBanca", "ganada" }, { "PresidenciaSenadoNacional_TipoBanca", "ganada" } }; foreach (var config in defaultConfiguraciones) { if (!await context.Configuraciones.AnyAsync(c => c.Clave == config.Key)) context.Configuraciones.Add(new Configuracion { Clave = config.Key, Valor = config.Value }); } await context.SaveChangesAsync(); logger.LogInformation("--> Default configurations verified/seeded."); // --- PASO 2: Envolver todo el bloque del Seeder 2 en esta condición --- if (generarDatosDeEjemplo) { // --- SEEDER 2: DATOS DE EJEMPLO PARA ELECCIÓN NACIONAL (se ejecuta solo si faltan sus votos) --- const int eleccionNacionalId = 2; if (!await context.ResultadosVotos.AnyAsync(r => r.EleccionId == eleccionNacionalId)) { logger.LogInformation("--> No se encontraron datos de votos para la elección nacional ID {EleccionId}. Generando datos de simulación...", eleccionNacionalId); // PASO A: VERIFICAR/CREAR DEPENDENCIAS (Ámbitos, Categorías) if (!await context.CategoriasElectorales.AnyAsync(c => c.Id == 1)) context.CategoriasElectorales.Add(new CategoriaElectoral { Id = 1, Nombre = "SENADORES NACIONALES", Orden = 2 }); if (!await context.CategoriasElectorales.AnyAsync(c => c.Id == 2)) context.CategoriasElectorales.Add(new CategoriaElectoral { Id = 2, Nombre = "DIPUTADOS NACIONALES", Orden = 3 }); var provinciasMaestras = new Dictionary { { "01", "CIUDAD AUTONOMA DE BUENOS AIRES" }, { "02", "BUENOS AIRES" }, { "03", "CATAMARCA" }, { "04", "CORDOBA" }, { "05", "CORRIENTES" }, { "06", "CHACO" }, { "07", "CHUBUT" }, { "08", "ENTRE RIOS" }, { "09", "FORMOSA" }, { "10", "JUJUY" }, { "11", "LA PAMPA" }, { "12", "LA RIOJA" }, { "13", "MENDOZA" }, { "14", "MISIONES" }, { "15", "NEUQUEN" }, { "16", "RIO NEGRO" }, { "17", "SALTA" }, { "18", "SAN JUAN" }, { "19", "SAN LUIS" }, { "20", "SANTA CRUZ" }, { "21", "SANTA FE" }, { "22", "SANTIAGO DEL ESTERO" }, { "23", "TIERRA DEL FUEGO" }, { "24", "TUCUMAN" } }; foreach (var p in provinciasMaestras) { if (!await context.AmbitosGeograficos.AnyAsync(a => a.NivelId == 10 && a.DistritoId == p.Key)) context.AmbitosGeograficos.Add(new AmbitoGeografico { Nombre = p.Value, NivelId = 10, DistritoId = p.Key }); } await context.SaveChangesAsync(); var provinciasEnDb = await context.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 10).ToListAsync(); foreach (var provincia in provinciasEnDb) { if (!await context.AmbitosGeograficos.AnyAsync(a => a.NivelId == 30 && a.DistritoId == provincia.DistritoId)) { for (int i = 1; i <= 5; i++) context.AmbitosGeograficos.Add(new AmbitoGeografico { Nombre = $"{provincia.Nombre} - Depto. {i}", NivelId = 30, DistritoId = provincia.DistritoId }); } } await context.SaveChangesAsync(); logger.LogInformation("--> Datos maestros para Elección Nacional (Ámbitos, Categorías) verificados/creados."); // PASO B: GENERAR DATOS TRANSACCIONALES (Votos, Recuentos, etc.) var todosLosPartidos = await context.AgrupacionesPoliticas.Take(5).ToListAsync(); if (!todosLosPartidos.Any()) { logger.LogWarning("--> No hay partidos en la BD, no se pueden generar votos de ejemplo."); return; // Salir si no hay partidos para evitar errores } // (La lógica interna de generación de votos y recuentos que ya tenías y funcionaba) // ... (el código de generación de `nuevosResultados` y `nuevosEstados` va aquí, sin cambios) var nuevosResultados = new List(); var nuevosEstados = new List(); var rand = new Random(); var provinciasQueRenuevanSenadores = new HashSet { "01", "06", "08", "15", "16", "17", "22", "23" }; var categoriaDiputadosNac = await context.CategoriasElectorales.FindAsync(2); var categoriaSenadoresNac = await context.CategoriasElectorales.FindAsync(1); long totalVotosNacionalDip = 0, totalVotosNacionalSen = 0; int totalMesasNacionalDip = 0, totalMesasNacionalSen = 0; int totalMesasEscrutadasNacionalDip = 0, totalMesasEscrutadasNacionalSen = 0; foreach (var provincia in provinciasEnDb) { var municipiosDeProvincia = await context.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 30 && a.DistritoId == provincia.DistritoId).ToListAsync(); if (!municipiosDeProvincia.Any()) continue; var categoriasParaProcesar = new List { categoriaDiputadosNac! }; if (provinciasQueRenuevanSenadores.Contains(provincia.DistritoId!)) categoriasParaProcesar.Add(categoriaSenadoresNac!); foreach (var categoria in categoriasParaProcesar) { long totalVotosProvinciaCategoria = 0; int partidoIndex = rand.Next(todosLosPartidos.Count); foreach (var municipio in municipiosDeProvincia) { var partidoGanador = todosLosPartidos[partidoIndex++ % todosLosPartidos.Count]; var votosGanador = rand.Next(25000, 70000); nuevosResultados.Add(new ResultadoVoto { EleccionId = eleccionNacionalId, AmbitoGeograficoId = municipio.Id, CategoriaId = categoria.Id, AgrupacionPoliticaId = partidoGanador.Id, CantidadVotos = votosGanador }); totalVotosProvinciaCategoria += votosGanador; var otrosPartidos = todosLosPartidos.Where(p => p.Id != partidoGanador.Id).OrderBy(p => rand.Next()).Take(rand.Next(3, todosLosPartidos.Count)); foreach (var competidor in otrosPartidos) { var votosCompetidor = rand.Next(1000, 24000); nuevosResultados.Add(new ResultadoVoto { EleccionId = eleccionNacionalId, AmbitoGeograficoId = municipio.Id, CategoriaId = categoria.Id, AgrupacionPoliticaId = competidor.Id, CantidadVotos = votosCompetidor }); totalVotosProvinciaCategoria += votosCompetidor; } } var mesasEsperadasProvincia = municipiosDeProvincia.Count * rand.Next(15, 30); var mesasTotalizadasProvincia = (int)(mesasEsperadasProvincia * (rand.Next(75, 99) / 100.0)); var cantidadElectoresProvincia = mesasEsperadasProvincia * 350; var participacionProvincia = (decimal)(rand.Next(65, 85) / 100.0); nuevosEstados.Add(new EstadoRecuentoGeneral { EleccionId = eleccionNacionalId, AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id, FechaTotalizacion = DateTime.UtcNow, MesasEsperadas = mesasEsperadasProvincia, MesasTotalizadas = mesasTotalizadasProvincia, MesasTotalizadasPorcentaje = mesasEsperadasProvincia > 0 ? (decimal)mesasTotalizadasProvincia * 100 / mesasEsperadasProvincia : 0, CantidadElectores = cantidadElectoresProvincia, CantidadVotantes = (int)(cantidadElectoresProvincia * participacionProvincia), ParticipacionPorcentaje = participacionProvincia * 100 }); if (categoriaDiputadosNac != null && categoria.Id == categoriaDiputadosNac.Id) { totalVotosNacionalDip += totalVotosProvinciaCategoria; totalMesasNacionalDip += mesasEsperadasProvincia; totalMesasEscrutadasNacionalDip += mesasTotalizadasProvincia; } else { totalVotosNacionalSen += totalVotosProvinciaCategoria; totalMesasNacionalSen += mesasEsperadasProvincia; totalMesasEscrutadasNacionalSen += mesasTotalizadasProvincia; } } } var ambitoNacional = await context.AmbitosGeograficos.AsNoTracking().FirstOrDefaultAsync(a => a.NivelId == 0); if (ambitoNacional != null && categoriaDiputadosNac != null && categoriaSenadoresNac != null) { var participacionNacionalDip = (decimal)(rand.Next(70, 88) / 100.0); nuevosEstados.Add(new EstadoRecuentoGeneral { EleccionId = eleccionNacionalId, AmbitoGeograficoId = ambitoNacional.Id, CategoriaId = categoriaDiputadosNac.Id, FechaTotalizacion = DateTime.UtcNow, MesasEsperadas = totalMesasNacionalDip, MesasTotalizadas = totalMesasEscrutadasNacionalDip, MesasTotalizadasPorcentaje = totalMesasNacionalDip > 0 ? (decimal)totalMesasEscrutadasNacionalDip * 100 / totalMesasNacionalDip : 0, CantidadElectores = totalMesasNacionalDip * 350, CantidadVotantes = (int)((totalMesasNacionalDip * 350) * participacionNacionalDip), ParticipacionPorcentaje = participacionNacionalDip * 100 }); var participacionNacionalSen = (decimal)(rand.Next(70, 88) / 100.0); nuevosEstados.Add(new EstadoRecuentoGeneral { EleccionId = eleccionNacionalId, AmbitoGeograficoId = ambitoNacional.Id, CategoriaId = categoriaSenadoresNac.Id, FechaTotalizacion = DateTime.UtcNow, MesasEsperadas = totalMesasNacionalSen, MesasTotalizadas = totalMesasEscrutadasNacionalSen, MesasTotalizadasPorcentaje = totalMesasNacionalSen > 0 ? (decimal)totalMesasEscrutadasNacionalSen * 100 / totalMesasNacionalSen : 0, CantidadElectores = totalMesasNacionalSen * 350, CantidadVotantes = (int)((totalMesasNacionalSen * 350) * participacionNacionalSen), ParticipacionPorcentaje = participacionNacionalSen * 100 }); } else { logger.LogWarning("--> No se encontró el ámbito nacional (NivelId == 0) o las categorías electorales nacionales. No se agregaron estados nacionales."); } if (nuevosResultados.Any()) { await context.ResultadosVotos.AddRangeAsync(nuevosResultados); await context.EstadosRecuentosGenerales.AddRangeAsync(nuevosEstados); await context.SaveChangesAsync(); logger.LogInformation("--> Se generaron {Votos} registros de votos y {Estados} de estados de recuento.", nuevosResultados.Count, nuevosEstados.Count); } // PASO C: GENERAR BANCAS PREVIAS Y PROYECCIONES if (!await context.BancasPrevias.AnyAsync(b => b.EleccionId == eleccionNacionalId)) { var bancasPrevias = new List { new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = todosLosPartidos[0].Id, Cantidad = 40 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = todosLosPartidos[1].Id, Cantidad = 35 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = todosLosPartidos[2].Id, Cantidad = 30 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = todosLosPartidos[3].Id, Cantidad = 15 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = todosLosPartidos[4].Id, Cantidad = 10 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = todosLosPartidos[0].Id, Cantidad = 18 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = todosLosPartidos[1].Id, Cantidad = 15 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = todosLosPartidos[2].Id, Cantidad = 8 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = todosLosPartidos[3].Id, Cantidad = 4 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = todosLosPartidos[4].Id, Cantidad = 3 }, }; await context.BancasPrevias.AddRangeAsync(bancasPrevias); await context.SaveChangesAsync(); logger.LogInformation("--> Seeded Bancas Previas para la Elección Nacional."); } } } } // --- FIN DEL BLOQUE DE SEEDERS UNIFICADO --- // Configurar el pipeline de peticiones HTTP. // Añadimos el logging de peticiones de Serilog aquí. app.UseSerilogRequestLogging(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } // 1. Redirección a HTTPS (si se usa) //app.UseHttpsRedirection(); // 2. Middleware de Enrutamiento. ¡CLAVE! // Determina a qué endpoint irá la petición. app.UseRouting(); // 3. Middleware de CORS. // Ahora se ejecuta sabiendo a qué endpoint se dirige la petición. app.UseCors(MyAllowSpecificOrigins); // 4. Middleware de Autenticación. // Identifica quién es el usuario a partir del token. app.UseAuthentication(); // 5. Middleware de Autorización. // Verifica si el usuario identificado tiene permiso para acceder al endpoint. app.UseAuthorization(); // 6. Mapea los controladores a los endpoints que el enrutador descubrió. app.MapControllers(); // 5. Ejecutar la aplicación. app.Run();