feat(Worker): Adaptación integral para la API de Elecciones Nacionales

Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito.

Cambios principales:

- **Refactorización de `SondearResumenProvincialAsync`:**
  - Se elimina la dependencia del endpoint obsoleto `/getResumen`.
  - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados.

- **Expansión de `SondearResultadosMunicipalesAsync`:**
  - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad.
  - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios.

- **Modificación del Modelo de Datos:**
  - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`.
  - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio.

- **Ajustes de Nulabilidad en API Service:**
  - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625.

- **Deshabilitación de Seeders de Ejemplo:**
  - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
This commit is contained in:
2025-10-14 16:00:55 -03:00
parent 84f7643907
commit 316f49f25b
18 changed files with 1053 additions and 343 deletions

View File

@@ -167,6 +167,9 @@ using (var scope = app.Services.CreateScope())
var logger = services.GetRequiredService<ILogger<Program>>(); var logger = services.GetRequiredService<ILogger<Program>>();
var hasher = services.GetRequiredService<IPasswordHasher>(); var hasher = services.GetRequiredService<IPasswordHasher>();
// --- 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) --- // --- 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. // Estos son los datos maestros que NUNCA cambian.
@@ -218,169 +221,171 @@ using (var scope = app.Services.CreateScope())
await context.SaveChangesAsync(); await context.SaveChangesAsync();
logger.LogInformation("--> Default configurations verified/seeded."); logger.LogInformation("--> Default configurations verified/seeded.");
// --- PASO 2: Envolver todo el bloque del Seeder 2 en esta condición ---
// --- SEEDER 2: DATOS DE EJEMPLO PARA ELECCIÓN NACIONAL (se ejecuta solo si faltan sus votos) --- if (generarDatosDeEjemplo)
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); // --- 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) // PASO A: VERIFICAR/CREAR DEPENDENCIAS (Ámbitos, Categorías)
if (!await context.CategoriasElectorales.AnyAsync(c => c.Id == 1)) if (!await context.CategoriasElectorales.AnyAsync(c => c.Id == 1))
context.CategoriasElectorales.Add(new CategoriaElectoral { Id = 1, Nombre = "SENADORES NACIONALES", Orden = 2 }); context.CategoriasElectorales.Add(new CategoriaElectoral { Id = 1, Nombre = "SENADORES NACIONALES", Orden = 2 });
if (!await context.CategoriasElectorales.AnyAsync(c => c.Id == 2)) if (!await context.CategoriasElectorales.AnyAsync(c => c.Id == 2))
context.CategoriasElectorales.Add(new CategoriaElectoral { Id = 2, Nombre = "DIPUTADOS NACIONALES", Orden = 3 }); context.CategoriasElectorales.Add(new CategoriaElectoral { Id = 2, Nombre = "DIPUTADOS NACIONALES", Orden = 3 });
var provinciasMaestras = new Dictionary<string, string> { var provinciasMaestras = new Dictionary<string, string> {
{ "01", "CIUDAD AUTONOMA DE BUENOS AIRES" }, { "02", "BUENOS AIRES" }, { "03", "CATAMARCA" }, { "04", "CORDOBA" }, { "05", "CORRIENTES" }, { "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" }, { "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" }, { "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" }, { "18", "SAN JUAN" }, { "19", "SAN LUIS" }, { "20", "SANTA CRUZ" }, { "21", "SANTA FE" }, { "22", "SANTIAGO DEL ESTERO" },
{ "23", "TIERRA DEL FUEGO" }, { "24", "TUCUMAN" } { "23", "TIERRA DEL FUEGO" }, { "24", "TUCUMAN" }
}; };
foreach (var p in provinciasMaestras) 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++) if (!await context.AmbitosGeograficos.AnyAsync(a => a.NivelId == 10 && a.DistritoId == p.Key))
context.AmbitosGeograficos.Add(new AmbitoGeografico { Nombre = $"{provincia.Nombre} - Depto. {i}", NivelId = 30, DistritoId = provincia.DistritoId }); context.AmbitosGeograficos.Add(new AmbitoGeografico { Nombre = p.Value, NivelId = 10, DistritoId = p.Key });
} }
} await context.SaveChangesAsync();
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 provinciasEnDb = await context.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 10).ToListAsync();
var todosLosPartidos = await context.AgrupacionesPoliticas.Take(5).ToListAsync(); foreach (var provincia in provinciasEnDb)
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<ResultadoVoto>();
var nuevosEstados = new List<EstadoRecuentoGeneral>();
var rand = new Random();
var provinciasQueRenuevanSenadores = new HashSet<string> { "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<CategoriaElectoral> { categoriaDiputadosNac! };
if (provinciasQueRenuevanSenadores.Contains(provincia.DistritoId!))
categoriasParaProcesar.Add(categoriaSenadoresNac!);
foreach (var categoria in categoriasParaProcesar)
{ {
long totalVotosProvinciaCategoria = 0; if (!await context.AmbitosGeograficos.AnyAsync(a => a.NivelId == 30 && a.DistritoId == provincia.DistritoId))
int partidoIndex = rand.Next(todosLosPartidos.Count);
foreach (var municipio in municipiosDeProvincia)
{ {
var partidoGanador = todosLosPartidos[partidoIndex++ % todosLosPartidos.Count]; for (int i = 1; i <= 5; i++)
var votosGanador = rand.Next(25000, 70000); context.AmbitosGeograficos.Add(new AmbitoGeografico { Nombre = $"{provincia.Nombre} - Depto. {i}", NivelId = 30, DistritoId = provincia.DistritoId });
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)); await context.SaveChangesAsync();
foreach (var competidor in otrosPartidos) 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<ResultadoVoto>();
var nuevosEstados = new List<EstadoRecuentoGeneral>();
var rand = new Random();
var provinciasQueRenuevanSenadores = new HashSet<string> { "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<CategoriaElectoral> { 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 votosCompetidor = rand.Next(1000, 24000); var partidoGanador = todosLosPartidos[partidoIndex++ % todosLosPartidos.Count];
nuevosResultados.Add(new ResultadoVoto { EleccionId = eleccionNacionalId, AmbitoGeograficoId = municipio.Id, CategoriaId = categoria.Id, AgrupacionPoliticaId = competidor.Id, CantidadVotos = votosCompetidor }); var votosGanador = rand.Next(25000, 70000);
totalVotosProvinciaCategoria += votosCompetidor; 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 mesasEsperadasProvincia = municipiosDeProvincia.Count * rand.Next(15, 30); }
var mesasTotalizadasProvincia = (int)(mesasEsperadasProvincia * (rand.Next(75, 99) / 100.0)); var ambitoNacional = await context.AmbitosGeograficos.AsNoTracking().FirstOrDefaultAsync(a => a.NivelId == 0);
var cantidadElectoresProvincia = mesasEsperadasProvincia * 350; if (ambitoNacional != null && categoriaDiputadosNac != null && categoriaSenadoresNac != null)
var participacionProvincia = (decimal)(rand.Next(65, 85) / 100.0); {
var participacionNacionalDip = (decimal)(rand.Next(70, 88) / 100.0);
nuevosEstados.Add(new EstadoRecuentoGeneral nuevosEstados.Add(new EstadoRecuentoGeneral
{ {
EleccionId = eleccionNacionalId, EleccionId = eleccionNacionalId,
AmbitoGeograficoId = provincia.Id, AmbitoGeograficoId = ambitoNacional.Id,
CategoriaId = categoria.Id, CategoriaId = categoriaDiputadosNac.Id,
FechaTotalizacion = DateTime.UtcNow, FechaTotalizacion = DateTime.UtcNow,
MesasEsperadas = mesasEsperadasProvincia, MesasEsperadas = totalMesasNacionalDip,
MesasTotalizadas = mesasTotalizadasProvincia, MesasTotalizadas = totalMesasEscrutadasNacionalDip,
MesasTotalizadasPorcentaje = mesasEsperadasProvincia > 0 ? (decimal)mesasTotalizadasProvincia * 100 / mesasEsperadasProvincia : 0, MesasTotalizadasPorcentaje = totalMesasNacionalDip > 0 ? (decimal)totalMesasEscrutadasNacionalDip * 100 / totalMesasNacionalDip : 0,
CantidadElectores = cantidadElectoresProvincia, CantidadElectores = totalMesasNacionalDip * 350,
CantidadVotantes = (int)(cantidadElectoresProvincia * participacionProvincia), CantidadVotantes = (int)((totalMesasNacionalDip * 350) * participacionNacionalDip),
ParticipacionPorcentaje = participacionProvincia * 100 ParticipacionPorcentaje = participacionNacionalDip * 100
}); });
if (categoriaDiputadosNac != null && categoria.Id == categoriaDiputadosNac.Id) var participacionNacionalSen = (decimal)(rand.Next(70, 88) / 100.0);
nuevosEstados.Add(new EstadoRecuentoGeneral
{ {
totalVotosNacionalDip += totalVotosProvinciaCategoria; totalMesasNacionalDip += mesasEsperadasProvincia; totalMesasEscrutadasNacionalDip += mesasTotalizadasProvincia; EleccionId = eleccionNacionalId,
} AmbitoGeograficoId = ambitoNacional.Id,
else CategoriaId = categoriaSenadoresNac.Id,
{ FechaTotalizacion = DateTime.UtcNow,
totalVotosNacionalSen += totalVotosProvinciaCategoria; totalMesasNacionalSen += mesasEsperadasProvincia; totalMesasEscrutadasNacionalSen += mesasTotalizadasProvincia; MesasEsperadas = totalMesasNacionalSen,
} MesasTotalizadas = totalMesasEscrutadasNacionalSen,
MesasTotalizadasPorcentaje = totalMesasNacionalSen > 0 ? (decimal)totalMesasEscrutadasNacionalSen * 100 / totalMesasNacionalSen : 0,
CantidadElectores = totalMesasNacionalSen * 350,
CantidadVotantes = (int)((totalMesasNacionalSen * 350) * participacionNacionalSen),
ParticipacionPorcentaje = participacionNacionalSen * 100
});
} }
} else
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, logger.LogWarning("--> No se encontró el ámbito nacional (NivelId == 0) o las categorías electorales nacionales. No se agregaron estados nacionales.");
AmbitoGeograficoId = ambitoNacional.Id, }
CategoriaId = categoriaDiputadosNac.Id,
FechaTotalizacion = DateTime.UtcNow, if (nuevosResultados.Any())
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, await context.ResultadosVotos.AddRangeAsync(nuevosResultados);
AmbitoGeograficoId = ambitoNacional.Id, await context.EstadosRecuentosGenerales.AddRangeAsync(nuevosEstados);
CategoriaId = categoriaSenadoresNac.Id, await context.SaveChangesAsync();
FechaTotalizacion = DateTime.UtcNow, logger.LogInformation("--> Se generaron {Votos} registros de votos y {Estados} de estados de recuento.", nuevosResultados.Count, nuevosEstados.Count);
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()) // PASO C: GENERAR BANCAS PREVIAS Y PROYECCIONES
{ if (!await context.BancasPrevias.AnyAsync(b => b.EleccionId == eleccionNacionalId))
await context.ResultadosVotos.AddRangeAsync(nuevosResultados); {
await context.EstadosRecuentosGenerales.AddRangeAsync(nuevosEstados); var bancasPrevias = new List<BancaPrevia> {
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<BancaPrevia> {
new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = todosLosPartidos[0].Id, Cantidad = 40 }, 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[1].Id, Cantidad = 35 },
new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = todosLosPartidos[2].Id, Cantidad = 30 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Diputados, AgrupacionPoliticaId = todosLosPartidos[2].Id, Cantidad = 30 },
@@ -392,9 +397,10 @@ using (var scope = app.Services.CreateScope())
new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = todosLosPartidos[3].Id, Cantidad = 4 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = todosLosPartidos[3].Id, Cantidad = 4 },
new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = todosLosPartidos[4].Id, Cantidad = 3 }, new() { EleccionId = eleccionNacionalId, Camara = TipoCamara.Senadores, AgrupacionPoliticaId = todosLosPartidos[4].Id, Cantidad = 3 },
}; };
await context.BancasPrevias.AddRangeAsync(bancasPrevias); await context.BancasPrevias.AddRangeAsync(bancasPrevias);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
logger.LogInformation("--> Seeded Bancas Previas para la Elección Nacional."); logger.LogInformation("--> Seeded Bancas Previas para la Elección Nacional.");
}
} }
} }
} }

View File

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

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","M5oop6AzAA5LmqkUXPrT16q3xF0Jty6n7nfGWkjXG94="],"CachedAssets":{},"CachedCopyCandidates":{}} {"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","UIntj4QoiyGr7bnJN8KK5PGrhQd89m\u002BLfh4T8VKPxAk=","Zf7uND\u002BRw1vwmlJX6vYl9l2j52U48b1N59mSoDoOeFU=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","ViGA16LZjzGiizSwwZApo6OQjv5hBNvl3QW/ISbKUWM="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","M5oop6AzAA5LmqkUXPrT16q3xF0Jty6n7nfGWkjXG94="],"CachedAssets":{},"CachedCopyCandidates":{}} {"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","TEsXImnzxFKTIq2f5fiDu7i6Ar/cbecW5MZ3z8Wb/a4=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","UIntj4QoiyGr7bnJN8KK5PGrhQd89m\u002BLfh4T8VKPxAk=","Zf7uND\u002BRw1vwmlJX6vYl9l2j52U48b1N59mSoDoOeFU=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","ViGA16LZjzGiizSwwZApo6OQjv5hBNvl3QW/ISbKUWM="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

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

View File

@@ -56,8 +56,14 @@ public class EleccionesDbContext(DbContextOptions<EleccionesDbContext> options)
modelBuilder.Entity<ResultadoVoto>() modelBuilder.Entity<ResultadoVoto>()
.Property(e => e.PorcentajeVotos).HasPrecision(18, 4); .Property(e => e.PorcentajeVotos).HasPrecision(18, 4);
modelBuilder.Entity<ResumenVoto>() modelBuilder.Entity<ResumenVoto>(entity => // Busca o crea este bloque
.Property(e => e.VotosPorcentaje).HasPrecision(5, 2); {
entity.Property(e => e.VotosPorcentaje).HasPrecision(5, 2);
// Esto asegura que no se pueda tener dos entradas para el mismo partido,
// en la misma categoría y en el mismo ámbito.
entity.HasIndex(r => new { r.AmbitoGeograficoId, r.CategoriaId, r.AgrupacionPoliticaId })
.IsUnique();
});
modelBuilder.Entity<EstadoRecuentoGeneral>(entity => modelBuilder.Entity<EstadoRecuentoGeneral>(entity =>
{ {

View File

@@ -13,6 +13,9 @@ public class ResumenVoto
[Required] [Required]
public int AmbitoGeograficoId { get; set; } public int AmbitoGeograficoId { get; set; }
[Required]
public int CategoriaId { get; set; }
[Required] [Required]
public string AgrupacionPoliticaId { get; set; } = null!; public string AgrupacionPoliticaId { get; set; } = null!;

View File

@@ -0,0 +1,732 @@
// <auto-generated />
using System;
using Elecciones.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Elecciones.Database.Migrations
{
[DbContext(typeof(EleccionesDbContext))]
[Migration("20251014172711_AddCategoriaIdToResumenVoto")]
partial class AddCategoriaIdToResumenVoto
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseCollation("Modern_Spanish_CI_AS")
.HasAnnotation("ProductVersion", "9.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Eleccion", b =>
{
b.Property<int>("Id")
.HasColumnType("int");
b.Property<string>("DistritoId")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateOnly>("Fecha")
.HasColumnType("date");
b.Property<string>("Nivel")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Nombre")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Elecciones");
});
modelBuilder.Entity("Elecciones.Database.Entities.AdminUser", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PasswordSalt")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.ToTable("AdminUsers");
});
modelBuilder.Entity("Elecciones.Database.Entities.AgrupacionPolitica", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("Color")
.HasColumnType("nvarchar(max)");
b.Property<string>("IdTelegrama")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Nombre")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("NombreCorto")
.HasColumnType("nvarchar(max)");
b.Property<int?>("OrdenDiputados")
.HasColumnType("int");
b.Property<int?>("OrdenDiputadosNacionales")
.HasColumnType("int");
b.Property<int?>("OrdenSenadores")
.HasColumnType("int");
b.Property<int?>("OrdenSenadoresNacionales")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("AgrupacionesPoliticas");
});
modelBuilder.Entity("Elecciones.Database.Entities.AmbitoGeografico", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("CircuitoId")
.HasColumnType("nvarchar(max)");
b.Property<string>("DistritoId")
.HasColumnType("nvarchar(max)");
b.Property<string>("EstablecimientoId")
.HasColumnType("nvarchar(max)");
b.Property<string>("MesaId")
.HasColumnType("nvarchar(max)");
b.Property<string>("MunicipioId")
.HasColumnType("nvarchar(max)");
b.Property<int>("NivelId")
.HasColumnType("int");
b.Property<string>("Nombre")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("SeccionId")
.HasColumnType("nvarchar(max)");
b.Property<string>("SeccionProvincialId")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("AmbitosGeograficos");
});
modelBuilder.Entity("Elecciones.Database.Entities.BancaPrevia", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("AgrupacionPoliticaId")
.IsRequired()
.HasColumnType("nvarchar(450)")
.UseCollation("Modern_Spanish_CI_AS");
b.Property<int>("Camara")
.HasColumnType("int");
b.Property<int>("Cantidad")
.HasColumnType("int");
b.Property<int>("EleccionId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("AgrupacionPoliticaId");
b.ToTable("BancasPrevias");
});
modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("AgrupacionPoliticaId")
.HasColumnType("nvarchar(450)");
b.Property<int>("Camara")
.HasColumnType("int");
b.Property<int>("EleccionId")
.HasColumnType("int");
b.Property<int>("NumeroBanca")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("AgrupacionPoliticaId");
b.ToTable("Bancadas");
});
modelBuilder.Entity("Elecciones.Database.Entities.CandidatoOverride", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("AgrupacionPoliticaId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<int?>("AmbitoGeograficoId")
.HasColumnType("int");
b.Property<int>("CategoriaId")
.HasColumnType("int");
b.Property<int>("EleccionId")
.HasColumnType("int");
b.Property<string>("NombreCandidato")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.HasKey("Id");
b.HasIndex("AmbitoGeograficoId");
b.HasIndex("CategoriaId");
b.HasIndex("AgrupacionPoliticaId", "CategoriaId", "AmbitoGeograficoId")
.IsUnique()
.HasFilter("[AmbitoGeograficoId] IS NOT NULL");
b.ToTable("CandidatosOverrides");
});
modelBuilder.Entity("Elecciones.Database.Entities.CategoriaElectoral", b =>
{
b.Property<int>("Id")
.HasColumnType("int");
b.Property<string>("Nombre")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Orden")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("CategoriasElectorales");
});
modelBuilder.Entity("Elecciones.Database.Entities.Configuracion", b =>
{
b.Property<string>("Clave")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Valor")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Clave");
b.ToTable("Configuraciones");
});
modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b =>
{
b.Property<int>("AmbitoGeograficoId")
.HasColumnType("int");
b.Property<int>("CategoriaId")
.HasColumnType("int");
b.Property<int>("CantidadElectores")
.HasColumnType("int");
b.Property<int>("CantidadVotantes")
.HasColumnType("int");
b.Property<int>("EleccionId")
.HasColumnType("int");
b.Property<DateTime>("FechaTotalizacion")
.HasColumnType("datetime2");
b.Property<int>("MesasEsperadas")
.HasColumnType("int");
b.Property<int>("MesasTotalizadas")
.HasColumnType("int");
b.Property<decimal>("MesasTotalizadasPorcentaje")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)");
b.Property<decimal>("ParticipacionPorcentaje")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)");
b.Property<long>("VotosEnBlanco")
.HasColumnType("bigint");
b.Property<decimal>("VotosEnBlancoPorcentaje")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<long>("VotosNulos")
.HasColumnType("bigint");
b.Property<decimal>("VotosNulosPorcentaje")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<long>("VotosRecurridos")
.HasColumnType("bigint");
b.Property<decimal>("VotosRecurridosPorcentaje")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.HasKey("AmbitoGeograficoId", "CategoriaId");
b.ToTable("EstadosRecuentos");
});
modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b =>
{
b.Property<int>("AmbitoGeograficoId")
.HasColumnType("int");
b.Property<int>("CategoriaId")
.HasColumnType("int");
b.Property<int>("CantidadElectores")
.HasColumnType("int");
b.Property<int>("CantidadVotantes")
.HasColumnType("int");
b.Property<int>("EleccionId")
.HasColumnType("int");
b.Property<DateTime>("FechaTotalizacion")
.HasColumnType("datetime2");
b.Property<int>("MesasEsperadas")
.HasColumnType("int");
b.Property<int>("MesasTotalizadas")
.HasColumnType("int");
b.Property<decimal>("MesasTotalizadasPorcentaje")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)");
b.Property<decimal>("ParticipacionPorcentaje")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)");
b.HasKey("AmbitoGeograficoId", "CategoriaId");
b.HasIndex("CategoriaId");
b.ToTable("EstadosRecuentosGenerales");
});
modelBuilder.Entity("Elecciones.Database.Entities.LogoAgrupacionCategoria", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("AgrupacionPoliticaId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<int?>("AmbitoGeograficoId")
.HasColumnType("int");
b.Property<int>("CategoriaId")
.HasColumnType("int");
b.Property<int?>("EleccionId")
.HasColumnType("int");
b.Property<string>("LogoUrl")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("AgrupacionPoliticaId", "CategoriaId", "AmbitoGeograficoId")
.IsUnique()
.HasFilter("[AmbitoGeograficoId] IS NOT NULL");
b.ToTable("LogosAgrupacionesCategorias");
});
modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BancadaId")
.HasColumnType("int");
b.Property<int>("EleccionId")
.HasColumnType("int");
b.Property<string>("FotoUrl")
.HasColumnType("nvarchar(max)");
b.Property<string>("NombreOcupante")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Periodo")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("BancadaId")
.IsUnique();
b.ToTable("OcupantesBancas");
});
modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("AgrupacionPoliticaId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<int>("AmbitoGeograficoId")
.HasColumnType("int");
b.Property<int>("CategoriaId")
.HasColumnType("int");
b.Property<int>("EleccionId")
.HasColumnType("int");
b.Property<DateTime>("FechaTotalizacion")
.HasColumnType("datetime2");
b.Property<int>("NroBancas")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("AgrupacionPoliticaId");
b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId")
.IsUnique();
b.ToTable("ProyeccionesBancas");
});
modelBuilder.Entity("Elecciones.Database.Entities.ResultadoVoto", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<string>("AgrupacionPoliticaId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<int>("AmbitoGeograficoId")
.HasColumnType("int");
b.Property<long>("CantidadVotos")
.HasColumnType("bigint");
b.Property<int>("CategoriaId")
.HasColumnType("int");
b.Property<int>("EleccionId")
.HasColumnType("int");
b.Property<decimal>("PorcentajeVotos")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.HasKey("Id");
b.HasIndex("AgrupacionPoliticaId");
b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId")
.IsUnique();
b.ToTable("ResultadosVotos");
});
modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("AgrupacionPoliticaId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<int>("AmbitoGeograficoId")
.HasColumnType("int");
b.Property<int>("CategoriaId")
.HasColumnType("int");
b.Property<int>("EleccionId")
.HasColumnType("int");
b.Property<long>("Votos")
.HasColumnType("bigint");
b.Property<decimal>("VotosPorcentaje")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)");
b.HasKey("Id");
b.HasIndex("AgrupacionPoliticaId");
b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId")
.IsUnique();
b.ToTable("ResumenesVotos");
});
modelBuilder.Entity("Elecciones.Database.Entities.Telegrama", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AmbitoGeograficoId")
.HasColumnType("int");
b.Property<string>("ContenidoBase64")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("EleccionId")
.HasColumnType("int");
b.Property<DateTime>("FechaEscaneo")
.HasColumnType("datetime2");
b.Property<DateTime>("FechaTotalizacion")
.HasColumnType("datetime2");
b.HasKey("Id");
b.ToTable("Telegramas");
});
modelBuilder.Entity("Elecciones.Database.Entities.BancaPrevia", b =>
{
b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica")
.WithMany()
.HasForeignKey("AgrupacionPoliticaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AgrupacionPolitica");
});
modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b =>
{
b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica")
.WithMany()
.HasForeignKey("AgrupacionPoliticaId");
b.Navigation("AgrupacionPolitica");
});
modelBuilder.Entity("Elecciones.Database.Entities.CandidatoOverride", b =>
{
b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica")
.WithMany()
.HasForeignKey("AgrupacionPoliticaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico")
.WithMany()
.HasForeignKey("AmbitoGeograficoId");
b.HasOne("Elecciones.Database.Entities.CategoriaElectoral", "CategoriaElectoral")
.WithMany()
.HasForeignKey("CategoriaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AgrupacionPolitica");
b.Navigation("AmbitoGeografico");
b.Navigation("CategoriaElectoral");
});
modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b =>
{
b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico")
.WithMany()
.HasForeignKey("AmbitoGeograficoId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AmbitoGeografico");
});
modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b =>
{
b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico")
.WithMany()
.HasForeignKey("AmbitoGeograficoId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Elecciones.Database.Entities.CategoriaElectoral", "CategoriaElectoral")
.WithMany()
.HasForeignKey("CategoriaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AmbitoGeografico");
b.Navigation("CategoriaElectoral");
});
modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b =>
{
b.HasOne("Elecciones.Database.Entities.Bancada", "Bancada")
.WithOne("Ocupante")
.HasForeignKey("Elecciones.Database.Entities.OcupanteBanca", "BancadaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Bancada");
});
modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b =>
{
b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica")
.WithMany()
.HasForeignKey("AgrupacionPoliticaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico")
.WithMany()
.HasForeignKey("AmbitoGeograficoId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AgrupacionPolitica");
b.Navigation("AmbitoGeografico");
});
modelBuilder.Entity("Elecciones.Database.Entities.ResultadoVoto", b =>
{
b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica")
.WithMany()
.HasForeignKey("AgrupacionPoliticaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico")
.WithMany()
.HasForeignKey("AmbitoGeograficoId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AgrupacionPolitica");
b.Navigation("AmbitoGeografico");
});
modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b =>
{
b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica")
.WithMany()
.HasForeignKey("AgrupacionPoliticaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AgrupacionPolitica");
});
modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b =>
{
b.Navigation("Ocupante");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elecciones.Database.Migrations
{
/// <inheritdoc />
public partial class AddCategoriaIdToResumenVoto : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "CategoriaId",
table: "ResumenesVotos",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateIndex(
name: "IX_ResumenesVotos_AmbitoGeograficoId_CategoriaId_AgrupacionPoliticaId",
table: "ResumenesVotos",
columns: new[] { "AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_ResumenesVotos_AmbitoGeograficoId_CategoriaId_AgrupacionPoliticaId",
table: "ResumenesVotos");
migrationBuilder.DropColumn(
name: "CategoriaId",
table: "ResumenesVotos");
}
}
}

View File

@@ -535,6 +535,9 @@ namespace Elecciones.Database.Migrations
b.Property<int>("AmbitoGeograficoId") b.Property<int>("AmbitoGeograficoId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("CategoriaId")
.HasColumnType("int");
b.Property<int>("EleccionId") b.Property<int>("EleccionId")
.HasColumnType("int"); .HasColumnType("int");
@@ -549,6 +552,9 @@ namespace Elecciones.Database.Migrations
b.HasIndex("AgrupacionPoliticaId"); b.HasIndex("AgrupacionPoliticaId");
b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId")
.IsUnique();
b.ToTable("ResumenesVotos"); b.ToTable("ResumenesVotos");
}); });

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+a316e5dd0876a581437b08d9980b658cc714da90")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+84f764390766e1d9716a38464ee478f3d0b75f96")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -92,7 +92,7 @@ public class ElectoralApiService : IElectoralApiService
return null;*/ return null;*/
} }
public async Task<ResultadosDto?> GetResultadosAsync(string authToken, string distritoId, string seccionId, string? municipioId, int categoriaId) public async Task<ResultadosDto?> GetResultadosAsync(string authToken, string distritoId, string? seccionId, string? municipioId, int categoriaId)
{ {
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
if (!lease.IsAcquired) return null; if (!lease.IsAcquired) return null;
@@ -256,27 +256,6 @@ public class ElectoralApiService : IElectoralApiService
} }
} }
public async Task<ResumenDto?> GetResumenAsync(string authToken, string distritoId)
{
// "Pedir una ficha". Este método ahora devuelve un "lease" (permiso).
// Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo.
/*
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
// Si se nos concede el permiso para proceder...
if (lease.IsAcquired)
{*/
var client = _httpClientFactory.CreateClient("ElectoralApiClient");
var requestUri = $"/api/resultados/getResumen?distritoId={distritoId}";
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
request.Headers.Add("Authorization", $"Bearer {authToken}");
var response = await client.SendAsync(request);
return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync<ResumenDto>() : null;
/* }
// Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null.
return null;*/
}
public async Task<EstadoRecuentoGeneralDto?> GetEstadoRecuentoGeneralAsync(string authToken, string distritoId, int categoriaId) public async Task<EstadoRecuentoGeneralDto?> GetEstadoRecuentoGeneralAsync(string authToken, string distritoId, int categoriaId)
{ {
// "Pedir una ficha". Este método ahora devuelve un "lease" (permiso). // "Pedir una ficha". Este método ahora devuelve un "lease" (permiso).

View File

@@ -11,11 +11,10 @@ public interface IElectoralApiService
// Métodos para catálogos // Métodos para catálogos
Task<CatalogoDto?> GetCatalogoAmbitosAsync(string authToken, int categoriaId); Task<CatalogoDto?> GetCatalogoAmbitosAsync(string authToken, int categoriaId);
Task<List<AgrupacionDto>?> GetAgrupacionesAsync(string authToken, string distritoId, int categoriaId); Task<List<AgrupacionDto>?> GetAgrupacionesAsync(string authToken, string distritoId, int categoriaId);
Task<ResultadosDto?> GetResultadosAsync(string authToken, string distritoId, string seccionId, string? municipioId, int categoriaId); Task<ResultadosDto?> GetResultadosAsync(string authToken, string distritoId, string? seccionId, string? municipioId, int categoriaId);
Task<RepartoBancasDto?> GetBancasAsync(string authToken, string distritoId, string? seccionProvincialId, int categoriaId); Task<RepartoBancasDto?> GetBancasAsync(string authToken, string distritoId, string? seccionProvincialId, int categoriaId);
Task<List<string>?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId, int? categoriaId = null); Task<List<string>?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId, int? categoriaId = null);
Task<TelegramaFileDto?> GetTelegramaFileAsync(string authToken, string mesaId); Task<TelegramaFileDto?> GetTelegramaFileAsync(string authToken, string mesaId);
Task<ResumenDto?> GetResumenAsync(string authToken, string distritoId);
Task<EstadoRecuentoGeneralDto?> GetEstadoRecuentoGeneralAsync(string authToken, string distritoId, int categoriaId); Task<EstadoRecuentoGeneralDto?> GetEstadoRecuentoGeneralAsync(string authToken, string distritoId, int categoriaId);
Task<List<CategoriaDto>?> GetCategoriasAsync(string authToken); Task<List<CategoriaDto>?> GetCategoriasAsync(string authToken);
} }

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+a316e5dd0876a581437b08d9980b658cc714da90")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+84f764390766e1d9716a38464ee478f3d0b75f96")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -473,119 +473,89 @@ public class CriticalDataWorker : BackgroundService
} }
/// <summary> /// <summary>
/// Obtiene y actualiza el resumen de votos y el estado del recuento a nivel provincial. /// Obtiene y actualiza el resumen de votos y el estado del recuento a nivel provincial para CADA categoría.
/// Esta versión actualizada guarda tanto los votos por agrupación (en ResumenesVotos) /// Este método itera sobre todas las provincias y categorías, obteniendo sus resultados consolidados
/// como el estado general del recuento, incluyendo la fecha de totalización (en EstadosRecuentosGenerales), /// y guardándolos en las tablas 'ResumenesVotos' y 'EstadosRecuentosGenerales'.
/// asegurando que toda la operación sea atómica mediante una transacción de base de datos.
/// </summary> /// </summary>
/// <param name="authToken">El token de autenticación válido para la sesión.</param>
/// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken) private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken)
{ {
try try
{ {
// Creamos un scope de DbContext para esta operación. _logger.LogInformation("Iniciando sondeo de Resúmenes Provinciales...");
using var scope = _serviceProvider.CreateScope(); using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>(); var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
// Obtenemos el registro de la Provincia (NivelId 10). var provinciasASondear = await dbContext.AmbitosGeograficos
var provincia = await dbContext.AmbitosGeograficos
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken); .Where(a => a.NivelId == 10 && a.DistritoId != null)
.ToListAsync(stoppingToken);
// Si no encontramos el ámbito de la provincia, no podemos continuar. var todasLasCategorias = await dbContext.CategoriasElectorales
if (provincia == null) .AsNoTracking()
.ToListAsync(stoppingToken);
if (!provinciasASondear.Any() || !todasLasCategorias.Any())
{ {
_logger.LogWarning("No se encontró el ámbito 'Provincia' (NivelId 10) para el sondeo de resumen."); _logger.LogWarning("No se encontraron Provincias o Categorías para sondear resúmenes.");
return; return;
} }
// Llamamos a la API para obtener el resumen de datos provincial. foreach (var provincia in provinciasASondear)
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 if (stoppingToken.IsCancellationRequested) break;
// 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) foreach (var categoria in todasLasCategorias)
{ {
// Buscamos el registro existente usando la clave primaria compuesta. if (stoppingToken.IsCancellationRequested) break;
var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { provincia.Id, categoria.Id }, stoppingToken);
// Si no existe, lo creamos. // Usamos GetResultados sin seccionId/municipioId para obtener el resumen del distrito.
if (registroDb == null) var resultadosDto = await _apiService.GetResultadosAsync(authToken, provincia.DistritoId!, null, null, categoria.Id);
if (resultadosDto?.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos)
{ {
registroDb = new EstadoRecuentoGeneral { AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id }; // Usamos una transacción para asegurar que el borrado y la inserción sean atómicos.
dbContext.EstadosRecuentosGenerales.Add(registroDb); await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
// A. Borrar los resúmenes viejos SOLO para esta provincia y categoría.
await dbContext.ResumenesVotos
.Where(rv => rv.AmbitoGeograficoId == provincia.Id && rv.CategoriaId == categoria.Id)
.ExecuteDeleteAsync(stoppingToken);
// B. Añadir los nuevos resúmenes.
foreach (var voto in nuevosVotos)
{
dbContext.ResumenesVotos.Add(new ResumenVoto
{
AmbitoGeograficoId = provincia.Id,
CategoriaId = categoria.Id,
AgrupacionPoliticaId = voto.IdAgrupacion,
Votos = voto.Votos,
VotosPorcentaje = voto.VotosPorcentaje
});
}
// C. Guardar los cambios en la tabla ResumenesVotos.
await dbContext.SaveChangesAsync(stoppingToken);
// No es necesario actualizar EstadosRecuentosGenerales aquí,
// ya que el método SondearEstadoRecuentoGeneralAsync se encarga de eso
// de forma más específica y eficiente.
await transaction.CommitAsync(stoppingToken);
} }
} // Fin bucle categorías
} // Fin bucle provincias
// Parseamos la fecha de forma segura para evitar errores con cadenas vacías o nulas. _logger.LogInformation("Sondeo de Resúmenes Provinciales completado.");
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) catch (OperationCanceledException)
{ {
_logger.LogInformation("Sondeo de resumen provincial cancelado."); _logger.LogInformation("Sondeo de resúmenes provinciales cancelado.");
} }
catch (Exception ex) 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 Resúmenes Provinciales.");
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Resumen Provincial.");
} }
} }

View File

@@ -195,119 +195,89 @@ public class LowPriorityDataWorker : BackgroundService
} }
/// <summary> /// <summary>
/// Obtiene y actualiza el resumen de votos y el estado del recuento a nivel provincial. /// Obtiene y actualiza el resumen de votos y el estado del recuento a nivel provincial para CADA categoría.
/// Esta versión actualizada guarda tanto los votos por agrupación (en ResumenesVotos) /// Este método itera sobre todas las provincias y categorías, obteniendo sus resultados consolidados
/// como el estado general del recuento, incluyendo la fecha de totalización (en EstadosRecuentosGenerales), /// y guardándolos en las tablas 'ResumenesVotos' y 'EstadosRecuentosGenerales'.
/// asegurando que toda la operación sea atómica mediante una transacción de base de datos.
/// </summary> /// </summary>
/// <param name="authToken">El token de autenticación válido para la sesión.</param>
/// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken) private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken)
{ {
try try
{ {
// Creamos un scope de DbContext para esta operación. _logger.LogInformation("Iniciando sondeo de Resúmenes Provinciales...");
using var scope = _serviceProvider.CreateScope(); using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>(); var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
// Obtenemos el registro de la Provincia (NivelId 10). var provinciasASondear = await dbContext.AmbitosGeograficos
var provincia = await dbContext.AmbitosGeograficos
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken); .Where(a => a.NivelId == 10 && a.DistritoId != null)
.ToListAsync(stoppingToken);
// Si no encontramos el ámbito de la provincia, no podemos continuar. var todasLasCategorias = await dbContext.CategoriasElectorales
if (provincia == null) .AsNoTracking()
.ToListAsync(stoppingToken);
if (!provinciasASondear.Any() || !todasLasCategorias.Any())
{ {
_logger.LogWarning("No se encontró el ámbito 'Provincia' (NivelId 10) para el sondeo de resumen."); _logger.LogWarning("No se encontraron Provincias o Categorías para sondear resúmenes.");
return; return;
} }
// Llamamos a la API para obtener el resumen de datos provincial. foreach (var provincia in provinciasASondear)
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 if (stoppingToken.IsCancellationRequested) break;
// 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) foreach (var categoria in todasLasCategorias)
{ {
// Buscamos el registro existente usando la clave primaria compuesta. if (stoppingToken.IsCancellationRequested) break;
var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { provincia.Id, categoria.Id }, stoppingToken);
// Si no existe, lo creamos. // Usamos GetResultados sin seccionId/municipioId para obtener el resumen del distrito.
if (registroDb == null) var resultadosDto = await _apiService.GetResultadosAsync(authToken, provincia.DistritoId!, null, null, categoria.Id);
if (resultadosDto?.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos)
{ {
registroDb = new EstadoRecuentoGeneral { AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id }; // Usamos una transacción para asegurar que el borrado y la inserción sean atómicos.
dbContext.EstadosRecuentosGenerales.Add(registroDb); await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
// A. Borrar los resúmenes viejos SOLO para esta provincia y categoría.
await dbContext.ResumenesVotos
.Where(rv => rv.AmbitoGeograficoId == provincia.Id && rv.CategoriaId == categoria.Id)
.ExecuteDeleteAsync(stoppingToken);
// B. Añadir los nuevos resúmenes.
foreach (var voto in nuevosVotos)
{
dbContext.ResumenesVotos.Add(new ResumenVoto
{
AmbitoGeograficoId = provincia.Id,
CategoriaId = categoria.Id,
AgrupacionPoliticaId = voto.IdAgrupacion,
Votos = voto.Votos,
VotosPorcentaje = voto.VotosPorcentaje
});
}
// C. Guardar los cambios en la tabla ResumenesVotos.
await dbContext.SaveChangesAsync(stoppingToken);
// No es necesario actualizar EstadosRecuentosGenerales aquí,
// ya que el método SondearEstadoRecuentoGeneralAsync se encarga de eso
// de forma más específica y eficiente.
await transaction.CommitAsync(stoppingToken);
} }
} // Fin bucle categorías
} // Fin bucle provincias
// Parseamos la fecha de forma segura para evitar errores con cadenas vacías o nulas. _logger.LogInformation("Sondeo de Resúmenes Provinciales completado.");
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) catch (OperationCanceledException)
{ {
_logger.LogInformation("Sondeo de resumen provincial cancelado."); _logger.LogInformation("Sondeo de resúmenes provinciales cancelado.");
} }
catch (Exception ex) 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 Resúmenes Provinciales.");
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Resumen Provincial.");
} }
} }

View File

@@ -6,7 +6,7 @@
} }
}, },
"ElectoralApi": { "ElectoralApi": {
"BaseUrl": "https://api.eleccionesbonaerenses.gba.gob.ar", "BaseUrl": "https://api.resultados.gob.ar",
"Username": "30500094156@elecciones2025.onmicrosoft.com", "Username": "30500094156@elecciones2025.onmicrosoft.com",
"Password": "PTP847elec" "Password": "PTP847elec"
}, },

View File

@@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Worker")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Worker")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+ce4fc52d4ab234dc5c25703d8945a3f79dccebc4")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+84f764390766e1d9716a38464ee478f3d0b75f96")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Worker")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Worker")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Worker")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Worker")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]