2025-09-06 21:44:52 -03:00
//Elecciones.Api/Program.cs
2025-08-14 13:12:16 -03:00
using Elecciones.Database ;
using Microsoft.EntityFrameworkCore ;
2025-08-15 17:31:51 -03:00
using Serilog ;
2025-08-29 09:54:22 -03:00
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 ;
2025-09-03 17:54:49 -03:00
using Microsoft.AspNetCore.HttpOverrides ;
2025-08-14 13:12:16 -03:00
2025-08-15 17:31:51 -03:00
// Esta es la estructura estándar y recomendada.
2025-08-14 12:37:57 -03:00
var builder = WebApplication . CreateBuilder ( args ) ;
2025-09-06 21:44:52 -03:00
// 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 < LoggingSwitchService > ( ) ;
builder . Host . UseSerilog ( ( context , services , configuration ) = >
{
// 2. Obtenemos la instancia del interruptor que acabamos de registrar.
var loggingSwitch = services . GetRequiredService < LoggingSwitchService > ( ) ;
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"
} ) ;
2025-08-14 13:12:16 -03:00
2025-08-15 17:31:51 -03:00
// 2. Añadir servicios al contenedor.
2025-08-14 13:12:16 -03:00
var connectionString = builder . Configuration . GetConnectionString ( "DefaultConnection" ) ;
builder . Services . AddDbContext < EleccionesDbContext > ( options = >
options . UseSqlServer ( connectionString ) ) ;
2025-08-29 09:54:22 -03:00
builder . Services . AddScoped < IPasswordHasher , PasswordHasher > ( ) ;
2025-08-14 13:12:16 -03:00
2025-08-29 09:54:22 -03:00
builder . Services . AddControllers ( ) . AddJsonOptions ( options = >
{
// Esto le dice al serializador que maneje las referencias circulares
options . JsonSerializerOptions . ReferenceHandler = ReferenceHandler . IgnoreCycles ;
} ) ;
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins" ;
2025-08-14 13:12:16 -03:00
builder . Services . AddCors ( options = >
{
2025-08-29 09:54:22 -03:00
options . AddPolicy ( name : MyAllowSpecificOrigins ,
policy = >
{
policy . WithOrigins (
2025-09-03 18:56:01 -03:00
"http://localhost:5173" ,
2025-09-03 17:06:39 -03:00
"http://localhost:5174" ,
2025-09-03 18:56:01 -03:00
"http://192.168.5.128:8700" ,
2025-09-03 17:06:39 -03:00
"https://www.eldia.com" ,
2025-09-05 13:40:33 -03:00
"https://extras.eldia.com" ,
"https://eldia.mustang.cloud"
2025-08-30 11:31:45 -03:00
)
2025-08-29 09:54:22 -03:00
. AllowAnyHeader ( )
. AllowAnyMethod ( ) ;
} ) ;
} ) ;
builder . Services . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme )
. AddJwtBearer ( options = >
2025-08-14 13:12:16 -03:00
{
2025-08-29 09:54:22 -03:00
options . TokenValidationParameters = new TokenValidationParameters
2025-08-14 15:51:19 -03:00
{
2025-08-29 09:54:22 -03:00
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" ] ! ) )
} ;
2025-08-14 13:12:16 -03:00
} ) ;
builder . Services . AddEndpointsApiExplorer ( ) ;
builder . Services . AddSwaggerGen ( ) ;
2025-08-14 12:37:57 -03:00
2025-09-03 17:54:49 -03:00
builder . Services . Configure < ForwardedHeadersOptions > ( options = >
{
options . ForwardedHeaders =
ForwardedHeaders . XForwardedFor | ForwardedHeaders . XForwardedProto ;
options . KnownNetworks . Clear ( ) ;
options . KnownProxies . Clear ( ) ;
} ) ;
2025-08-15 17:31:51 -03:00
// 3. Construir la aplicación.
2025-08-14 12:37:57 -03:00
var app = builder . Build ( ) ;
2025-09-06 21:44:52 -03:00
// --- 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 < EleccionesDbContext > ( ) ;
var loggingSwitchService = services . GetRequiredService < LoggingSwitchService > ( ) ;
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}" ) ;
}
}
2025-09-03 17:54:49 -03:00
app . UseForwardedHeaders ( ) ;
2025-08-29 09:54:22 -03:00
// Seeder para el usuario admin
using ( var scope = app . Services . CreateScope ( ) )
{
var services = scope . ServiceProvider ;
try
{
var context = services . GetRequiredService < EleccionesDbContext > ( ) ;
var hasher = services . GetRequiredService < IPasswordHasher > ( ) ;
if ( ! context . AdminUsers . Any ( ) )
{
var ( hash , salt ) = hasher . HashPassword ( "PTP847elec" ) ;
context . AdminUsers . Add ( new Elecciones . Database . Entities . AdminUser
{
Username = "admin" ,
PasswordHash = hash ,
PasswordSalt = salt
} ) ;
context . SaveChanges ( ) ;
Console . WriteLine ( "--> Admin user seeded." ) ;
}
}
catch ( Exception ex )
{
var logger = services . GetRequiredService < ILogger < Program > > ( ) ;
logger . LogError ( ex , "An error occurred while seeding the database." ) ;
}
}
// Seeder para las bancas vacías
using ( var scope = app . Services . CreateScope ( ) )
{
var services = scope . ServiceProvider ;
var context = services . GetRequiredService < EleccionesDbContext > ( ) ;
if ( ! context . Bancadas . Any ( ) )
{
var bancas = new List < Bancada > ( ) ;
// 92 bancas de diputados
2025-08-30 11:31:45 -03:00
for ( int i = 1 ; i < = 92 ; i + + ) // Bucle de 1 a 92
2025-08-29 09:54:22 -03:00
{
2025-08-30 11:31:45 -03:00
bancas . Add ( new Bancada
{
Camara = Elecciones . Core . Enums . TipoCamara . Diputados ,
NumeroBanca = i // Asignamos el número de banca
} ) ;
2025-08-29 09:54:22 -03:00
}
// 46 bancas de senadores
2025-08-30 11:31:45 -03:00
for ( int i = 1 ; i < = 46 ; i + + ) // Bucle de 1 a 46
2025-08-29 09:54:22 -03:00
{
2025-08-30 11:31:45 -03:00
bancas . Add ( new Bancada
{
Camara = Elecciones . Core . Enums . TipoCamara . Senadores ,
NumeroBanca = i // Asignamos el número de banca
} ) ;
2025-08-29 09:54:22 -03:00
}
context . Bancadas . AddRange ( bancas ) ;
context . SaveChanges ( ) ;
2025-08-30 11:31:45 -03:00
Console . WriteLine ( "--> Seeded 138 bancas físicas." ) ;
2025-08-29 09:54:22 -03:00
}
}
// Seeder para las configuraciones por defecto
using ( var scope = app . Services . CreateScope ( ) )
{
var services = scope . ServiceProvider ;
var context = services . GetRequiredService < EleccionesDbContext > ( ) ;
2025-09-06 21:44:52 -03:00
// Lista de configuraciones por defecto a asegurar
var defaultConfiguraciones = new Dictionary < string , string >
2025-08-29 09:54:22 -03:00
{
2025-09-06 21:44:52 -03:00
{ "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 )
2025-09-01 14:04:40 -03:00
{
2025-09-06 21:44:52 -03:00
if ( ! context . Configuraciones . Any ( c = > c . Clave = = config . Key ) )
{
context . Configuraciones . Add ( new Configuracion { Clave = config . Key , Valor = config . Value } ) ;
}
2025-09-01 14:04:40 -03:00
}
2025-09-06 21:44:52 -03:00
context . SaveChanges ( ) ;
Console . WriteLine ( "--> Seeded default configurations." ) ;
2025-08-29 09:54:22 -03:00
}
2025-09-17 11:31:17 -03:00
// --- SEEDER FINAL Y AUTOSUFICIENTE: Resultados Nacionales de Simulación para todo el país ---
using ( var scope = app . Services . CreateScope ( ) )
{
var services = scope . ServiceProvider ;
var context = services . GetRequiredService < EleccionesDbContext > ( ) ;
var logger = services . GetRequiredService < ILogger < Program > > ( ) ;
const int eleccionNacionalId = 2 ;
if ( ! context . ResultadosVotos . Any ( r = > r . EleccionId = = eleccionNacionalId ) )
{
logger . LogInformation ( "--> No se encontraron datos para la elección nacional ID {EleccionId}. Generando datos de simulación para TODO EL PAÍS..." , eleccionNacionalId ) ;
var eleccionNacional = await context . Elecciones . FindAsync ( eleccionNacionalId ) ? ? new Eleccion { Id = eleccionNacionalId , Nombre = "Elecciones Nacionales 2025" , Nivel = "Nacional" , DistritoId = "00" , Fecha = new DateOnly ( 2025 , 10 , 26 ) } ;
if ( ! context . Elecciones . Local . Any ( e = > e . Id = = eleccionNacionalId ) ) context . Elecciones . Add ( eleccionNacional ) ;
var categoriaDiputadosNac = await context . CategoriasElectorales . FindAsync ( 2 ) ? ? new CategoriaElectoral { Id = 2 , Nombre = "DIPUTADOS NACIONALES" , Orden = 3 } ;
if ( ! context . CategoriasElectorales . Local . Any ( c = > c . Id = = 2 ) ) context . CategoriasElectorales . Add ( categoriaDiputadosNac ) ;
await context . SaveChangesAsync ( ) ;
var provinciasMaestras = new Dictionary < string , string >
{
{ "01" , "CABA" } , { "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 ( ) ;
logger . LogInformation ( "--> Verificados/creados los 24 ámbitos geográficos de Nivel 10." ) ;
// --- INICIO DE LA LÓGICA DE CREACIÓN DE MUNICIPIOS DE EJEMPLO ---
logger . LogInformation ( "--> Verificando existencia de municipios (Nivel 30) para cada provincia..." ) ;
var provinciasEnDb = await context . AmbitosGeograficos . AsNoTracking ( ) . Where ( a = > a . NivelId = = 10 ) . ToListAsync ( ) ;
foreach ( var provincia in provinciasEnDb )
{
bool existenMunicipios = await context . AmbitosGeograficos . AnyAsync ( a = > a . NivelId = = 30 & & a . DistritoId = = provincia . DistritoId ) ;
if ( ! existenMunicipios )
{
logger . LogWarning ( "--> No se encontraron municipios para {Provincia}. Creando 5 municipios de ejemplo..." , provincia . Nombre ) ;
for ( int i = 1 ; i < = 5 ; i + + )
{
context . AmbitosGeograficos . Add ( new AmbitoGeografico
{
Nombre = $"{provincia.Nombre} - Municipio de Ejemplo {i}" ,
NivelId = 30 ,
DistritoId = provincia . DistritoId
} ) ;
}
}
}
await context . SaveChangesAsync ( ) ;
// --- FIN DE LA LÓGICA DE CREACIÓN DE MUNICIPIOS DE EJEMPLO ---
var todosLosPartidos = await context . AgrupacionesPoliticas . Take ( 5 ) . ToListAsync ( ) ;
if ( ! todosLosPartidos . Any ( ) ) {
logger . LogWarning ( "--> No hay agrupaciones políticas en la BD. No se pueden generar votos de simulación." ) ;
return ;
}
var nuevosResultados = new List < ResultadoVoto > ( ) ;
var rand = new Random ( ) ;
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 ;
logger . LogInformation ( "--> Generando votos para {Count} municipios en {Provincia}..." , municipiosDeProvincia . Count , provincia . Nombre ) ;
int partidoIndex = rand . Next ( todosLosPartidos . Count ) ;
foreach ( var municipio in municipiosDeProvincia )
{
var partidoGanador = todosLosPartidos [ partidoIndex % todosLosPartidos . Count ] ;
partidoIndex + + ;
nuevosResultados . Add ( new ResultadoVoto {
EleccionId = eleccionNacional . Id , AmbitoGeograficoId = municipio . Id , CategoriaId = categoriaDiputadosNac . Id ,
AgrupacionPoliticaId = partidoGanador . Id , CantidadVotos = rand . Next ( 25000 , 70000 )
} ) ;
var otrosPartidos = todosLosPartidos . Where ( p = > p . Id ! = partidoGanador . Id ) . OrderBy ( p = > rand . Next ( ) ) . Take ( rand . Next ( 3 , 6 ) ) ;
foreach ( var competidor in otrosPartidos ) {
nuevosResultados . Add ( new ResultadoVoto {
EleccionId = eleccionNacional . Id , AmbitoGeograficoId = municipio . Id , CategoriaId = categoriaDiputadosNac . Id ,
AgrupacionPoliticaId = competidor . Id , CantidadVotos = rand . Next ( 1000 , 24000 )
} ) ;
}
}
}
if ( nuevosResultados . Any ( ) ) {
await context . ResultadosVotos . AddRangeAsync ( nuevosResultados ) ;
await context . SaveChangesAsync ( ) ;
logger . LogInformation ( "--> Se generaron {Count} registros de votos de simulación para todo el país." , nuevosResultados . Count ) ;
} else {
logger . LogWarning ( "--> No se encontraron municipios en la BD para generar votos de simulación." ) ;
}
}
}
2025-08-29 09:54:22 -03:00
// Configurar el pipeline de peticiones HTTP.
2025-08-15 17:31:51 -03:00
// Añadimos el logging de peticiones de Serilog aquí.
app . UseSerilogRequestLogging ( ) ;
2025-08-14 13:12:16 -03:00
2025-08-14 12:37:57 -03:00
if ( app . Environment . IsDevelopment ( ) )
{
2025-08-14 13:12:16 -03:00
app . UseSwagger ( ) ;
app . UseSwaggerUI ( ) ;
2025-08-14 12:37:57 -03:00
}
2025-08-29 09:54:22 -03:00
// 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.
2025-08-14 13:12:16 -03:00
app . UseAuthorization ( ) ;
2025-08-29 09:54:22 -03:00
// 6. Mapea los controladores a los endpoints que el enrutador descubrió.
2025-08-14 13:12:16 -03:00
app . MapControllers ( ) ;
2025-08-15 17:31:51 -03:00
// 5. Ejecutar la aplicación.
2025-08-14 13:12:16 -03:00
app . Run ( ) ;