Feat Prototipos Widgets y Fix Worker Telegramas
This commit is contained in:
@@ -21,13 +21,12 @@ public class CatalogosController : ControllerBase
|
||||
[HttpGet("municipios")]
|
||||
public async Task<IActionResult> GetMunicipios()
|
||||
{
|
||||
// CORRECCIÓN: Los partidos/municipios corresponden al NivelId 30 (Sección)
|
||||
var municipios = await _dbContext.AmbitosGeograficos
|
||||
.AsNoTracking()
|
||||
.Where(a => a.NivelId == 30 && a.SeccionId != null) // <-- NivelId 30
|
||||
.Where(a => a.NivelId == 30 && a.SeccionId != null)
|
||||
.Select(a => new MunicipioSimpleDto
|
||||
{
|
||||
Id = a.SeccionId!, // <-- Usamos SeccionId como el ID
|
||||
Id = a.SeccionId!,
|
||||
Nombre = a.Nombre
|
||||
})
|
||||
.OrderBy(m => m.Nombre)
|
||||
@@ -41,8 +40,82 @@ public class CatalogosController : ControllerBase
|
||||
{
|
||||
var agrupaciones = await _dbContext.AgrupacionesPoliticas
|
||||
.AsNoTracking()
|
||||
.Select(a => new { a.Id, a.Nombre }) // Devuelve solo lo necesario
|
||||
.Select(a => new { a.Id, a.Nombre })
|
||||
.ToListAsync();
|
||||
return Ok(agrupaciones);
|
||||
}
|
||||
|
||||
[HttpGet("secciones-electorales")]
|
||||
public async Task<IActionResult> GetSeccionesElectorales()
|
||||
{
|
||||
var secciones = await _dbContext.AmbitosGeograficos
|
||||
.AsNoTracking()
|
||||
// Buscamos los ámbitos que son Secciones Electorales (Nivel 20)
|
||||
.Where(a => a.NivelId == 20 && a.SeccionProvincialId != null)
|
||||
.Select(a => new MunicipioSimpleDto // Reutilizamos el DTO porque tiene la misma estructura {Id, Nombre}
|
||||
{
|
||||
Id = a.SeccionProvincialId!, // El ID que usaremos es el SeccionProvincialId
|
||||
Nombre = a.Nombre
|
||||
})
|
||||
.OrderBy(s => s.Nombre)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(secciones);
|
||||
}
|
||||
|
||||
// Nivel 20: Sección Electoral
|
||||
[HttpGet("secciones")]
|
||||
public async Task<IActionResult> GetSecciones()
|
||||
{
|
||||
var secciones = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
||||
.Where(a => a.NivelId == 20 && !string.IsNullOrEmpty(a.SeccionProvincialId))
|
||||
.Select(a => new { Id = a.SeccionProvincialId, a.Nombre })
|
||||
.Distinct().OrderBy(s => s.Nombre).ToListAsync();
|
||||
return Ok(secciones);
|
||||
}
|
||||
|
||||
// Nivel 30: Municipio (llamado "Sección" por la API)
|
||||
[HttpGet("municipios/{seccionProvincialId}")]
|
||||
public async Task<IActionResult> GetMunicipiosPorSeccion(string seccionProvincialId)
|
||||
{
|
||||
var municipios = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
||||
.Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionProvincialId && !string.IsNullOrEmpty(a.SeccionId))
|
||||
.Select(a => new { Id = a.SeccionId, a.Nombre }) // El ID del Municipio es SeccionId
|
||||
.Distinct().OrderBy(m => m.Nombre).ToListAsync();
|
||||
return Ok(municipios);
|
||||
}
|
||||
|
||||
// Nivel 50: Circuito (llamado "Municipio" por la API)
|
||||
[HttpGet("circuitos/{municipioId}")]
|
||||
public async Task<IActionResult> GetCircuitosPorMunicipio(string municipioId)
|
||||
{
|
||||
var circuitos = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
||||
// Un Circuito (Nivel 50) pertenece a un Municipio (Nivel 30) a través del campo 'SeccionId'
|
||||
.Where(a => a.NivelId == 50 && a.SeccionId == municipioId && !string.IsNullOrEmpty(a.CircuitoId))
|
||||
.Select(a => new { Id = a.CircuitoId, a.Nombre })
|
||||
.Distinct().OrderBy(c => c.Nombre).ToListAsync();
|
||||
return Ok(circuitos);
|
||||
}
|
||||
|
||||
// Nivel 60: Establecimiento (llamado "Colegio" por la API)
|
||||
[HttpGet("establecimientos/{circuitoId}")]
|
||||
public async Task<IActionResult> GetEstablecimientosPorCircuito(string circuitoId)
|
||||
{
|
||||
var establecimientos = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
||||
.Where(a => a.NivelId == 60 && a.CircuitoId == circuitoId && !string.IsNullOrEmpty(a.EstablecimientoId))
|
||||
.Select(a => new { Id = a.EstablecimientoId, a.Nombre })
|
||||
.Distinct().OrderBy(e => e.Nombre).ToListAsync();
|
||||
return Ok(establecimientos);
|
||||
}
|
||||
|
||||
// Nivel 70: Mesa
|
||||
[HttpGet("mesas/{establecimientoId}")]
|
||||
public async Task<IActionResult> GetMesasPorEstablecimiento(string establecimientoId)
|
||||
{
|
||||
var mesas = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
||||
.Where(a => a.NivelId == 70 && a.EstablecimientoId == establecimientoId && !string.IsNullOrEmpty(a.MesaId))
|
||||
.Select(a => new { Id = a.MesaId, a.Nombre })
|
||||
.Distinct().OrderBy(m => m.Nombre).ToListAsync();
|
||||
return Ok(mesas);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ using Elecciones.Core.DTOs.ApiResponses;
|
||||
using Elecciones.Database;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -91,59 +93,85 @@ public class ResultadosController : ControllerBase
|
||||
[HttpGet("provincia/{distritoId}")]
|
||||
public async Task<IActionResult> GetResultadosProvinciales(string distritoId)
|
||||
{
|
||||
// TODO: Esta lógica debe ser reemplazada para leer datos reales de la BD
|
||||
// cuando el worker comience a ingestar los totales a nivel provincial.
|
||||
// Por ahora, devolvemos datos simulados para permitir el desarrollo del frontend.
|
||||
_logger.LogInformation("Solicitud de resultados para la provincia con distritoId: {DistritoId}", distritoId);
|
||||
|
||||
var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
||||
.FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10);
|
||||
// PASO 1: Encontrar el ámbito geográfico de la provincia.
|
||||
var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking()
|
||||
.FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10);
|
||||
|
||||
if (ambito == null)
|
||||
if (provincia == null)
|
||||
{
|
||||
return NotFound(new { message = "No se encontró la provincia" });
|
||||
_logger.LogWarning("No se encontró la provincia con distritoId: {DistritoId}", distritoId);
|
||||
return NotFound(new { message = $"No se encontró la provincia con distritoId {distritoId}" });
|
||||
}
|
||||
|
||||
// Simulación
|
||||
var random = new Random();
|
||||
var respuestaSimulada = new ResumenProvincialDto
|
||||
// PASO 2: Obtener el estado general del recuento para la provincia.
|
||||
// Como las estadísticas generales (mesas, participación) son las mismas para todas las categorías,
|
||||
// simplemente tomamos la primera que encontremos para este ámbito.
|
||||
var estadoGeneral = await _dbContext.EstadosRecuentosGenerales.AsNoTracking()
|
||||
.FirstOrDefaultAsync(e => e.AmbitoGeograficoId == provincia.Id);
|
||||
|
||||
// PASO 3: Obtener el resumen de votos por agrupación para la provincia.
|
||||
// Hacemos un JOIN manual entre ResumenesVotos y AgrupacionesPoliticas para obtener los nombres.
|
||||
var resultados = await _dbContext.ResumenesVotos
|
||||
.AsNoTracking()
|
||||
.Where(r => r.AmbitoGeograficoId == provincia.Id)
|
||||
.Join(
|
||||
_dbContext.AgrupacionesPoliticas.AsNoTracking(),
|
||||
resumen => resumen.AgrupacionPoliticaId,
|
||||
agrupacion => agrupacion.Id,
|
||||
(resumen, agrupacion) => new AgrupacionResultadoDto
|
||||
{
|
||||
Nombre = agrupacion.Nombre,
|
||||
Votos = resumen.Votos,
|
||||
Porcentaje = resumen.VotosPorcentaje
|
||||
})
|
||||
.OrderByDescending(r => r.Votos)
|
||||
.ToListAsync();
|
||||
|
||||
// PASO 4: Construir el objeto de respuesta (DTO).
|
||||
// Si no hay datos de recuento aún, usamos valores por defecto para evitar errores en el frontend.
|
||||
var respuestaDto = new ResumenProvincialDto
|
||||
{
|
||||
ProvinciaNombre = ambito.Nombre,
|
||||
UltimaActualizacion = DateTime.UtcNow,
|
||||
PorcentajeEscrutado = 78.45m,
|
||||
PorcentajeParticipacion = 65.12m,
|
||||
Resultados =
|
||||
[
|
||||
new() { Nombre = "ALIANZA POR EL FUTURO", Votos = 2500000 + random.Next(1, 1000), Porcentaje = 45.12m },
|
||||
new() { Nombre = "FRENTE DE AVANZADA", Votos = 2100000 + random.Next(1, 1000), Porcentaje = 38.78m },
|
||||
new() { Nombre = "UNION POPULAR", Votos = 800000 + random.Next(1, 1000), Porcentaje = 14.10m },
|
||||
],
|
||||
VotosAdicionales = new VotosAdicionalesDto { EnBlanco = 150000, Nulos = 80000, Recurridos = 1200 }
|
||||
ProvinciaNombre = provincia.Nombre,
|
||||
UltimaActualizacion = estadoGeneral?.FechaTotalizacion ?? DateTime.UtcNow,
|
||||
PorcentajeEscrutado = estadoGeneral?.MesasTotalizadasPorcentaje ?? 0,
|
||||
PorcentajeParticipacion = estadoGeneral?.ParticipacionPorcentaje ?? 0,
|
||||
Resultados = resultados,
|
||||
// NOTA: Los votos adicionales (nulos, en blanco) no están en la tabla de resumen provincial.
|
||||
// Esto es una mejora pendiente en el Worker. Por ahora, devolvemos 0.
|
||||
VotosAdicionales = new VotosAdicionalesDto { EnBlanco = 0, Nulos = 0, Recurridos = 0 }
|
||||
};
|
||||
|
||||
return Ok(await Task.FromResult(respuestaSimulada));
|
||||
_logger.LogInformation("Devolviendo {NumResultados} resultados de agrupaciones para la provincia.", respuestaDto.Resultados.Count);
|
||||
|
||||
return Ok(respuestaDto);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("bancas/{seccionId}")]
|
||||
public async Task<IActionResult> GetBancasPorSeccion(string seccionId)
|
||||
{
|
||||
// 1. Buscamos el ámbito de la sección electoral
|
||||
// 1. Buscamos el ámbito usando 'SeccionProvincialId'.
|
||||
// La API oficial usa este campo para las secciones electorales.
|
||||
// Además, el worker guarda estas secciones con NivelId = 20, por lo que lo usamos aquí para consistencia.
|
||||
var seccion = await _dbContext.AmbitosGeograficos
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(a => a.SeccionId == seccionId && a.NivelId == 4); // Nivel 4 = Sección Electoral
|
||||
.FirstOrDefaultAsync(a => a.SeccionProvincialId == seccionId && a.NivelId == 20); // Nivel 20 = Sección Electoral Provincial
|
||||
|
||||
if (seccion == null)
|
||||
{
|
||||
_logger.LogWarning("No se encontró la sección electoral con SeccionProvincialId: {SeccionId}", seccionId);
|
||||
return NotFound(new { message = $"No se encontró la sección electoral con ID {seccionId}" });
|
||||
}
|
||||
|
||||
// 2. Buscamos todas las proyecciones para ese ámbito, incluyendo el nombre de la agrupación
|
||||
// 2. Buscamos todas las proyecciones para ese ámbito (usando su clave primaria 'Id')
|
||||
var proyecciones = await _dbContext.ProyeccionesBancas
|
||||
.AsNoTracking()
|
||||
.Include(p => p.AgrupacionPolitica) // Incluimos el nombre del partido
|
||||
.Include(p => p.AgrupacionPolitica)
|
||||
.Where(p => p.AmbitoGeograficoId == seccion.Id)
|
||||
.Select(p => new
|
||||
{
|
||||
// Creamos un objeto anónimo para la respuesta, más limpio que un DTO para este caso simple
|
||||
AgrupacionNombre = p.AgrupacionPolitica.Nombre,
|
||||
Bancas = p.NroBancas
|
||||
})
|
||||
@@ -152,10 +180,12 @@ public class ResultadosController : ControllerBase
|
||||
|
||||
if (!proyecciones.Any())
|
||||
{
|
||||
// Este caso es posible si aún no hay proyecciones calculadas para esta sección.
|
||||
_logger.LogWarning("No se encontraron proyecciones de bancas para la sección: {SeccionNombre}", seccion.Nombre);
|
||||
return NotFound(new { message = $"No se han encontrado proyecciones de bancas para la sección {seccion.Nombre}" });
|
||||
}
|
||||
|
||||
// 3. Devolvemos un objeto que contiene el nombre de la sección y la lista de resultados
|
||||
// 3. Devolvemos la respuesta
|
||||
return Ok(new
|
||||
{
|
||||
SeccionNombre = seccion.Nombre,
|
||||
@@ -263,4 +293,40 @@ public class ResultadosController : ControllerBase
|
||||
|
||||
return Ok(respuestaDto);
|
||||
}
|
||||
|
||||
[HttpGet("composicion-congreso")]
|
||||
public IActionResult GetComposicionCongreso()
|
||||
{
|
||||
var diputados = new
|
||||
{
|
||||
CamaraNombre = "Cámara de Diputados",
|
||||
TotalBancas = 92,
|
||||
BancasEnJuego = 46,
|
||||
Partidos = new[]
|
||||
{
|
||||
// --- DATOS ACTUALIZADOS CON 'BANCASENJUEGO' ---
|
||||
new { Id = "501", Nombre = "CANDIDATURA 501", BancasTotales = 2, BancasEnJuego = 1, Color = "#d62728" },
|
||||
new { Id = "513", Nombre = "CANDIDATURA 513", BancasTotales = 37, BancasEnJuego = 19, Color = "#1f77b4" },
|
||||
new { Id = "516", Nombre = "CANDIDATURA 516", BancasTotales = 18, BancasEnJuego = 8, Color = "#2ca02c" },
|
||||
new { Id = "511", Nombre = "CANDIDATURA 511", BancasTotales = 22, BancasEnJuego = 12, Color = "#ff7f0e" },
|
||||
new { Id = "507", Nombre = "CANDIDATURA 507", BancasTotales = 13, BancasEnJuego = 6, Color = "#9467bd" }
|
||||
}
|
||||
};
|
||||
|
||||
var senadores = new
|
||||
{
|
||||
CamaraNombre = "Cámara de Senadores",
|
||||
TotalBancas = 46,
|
||||
BancasEnJuego = 23,
|
||||
Partidos = new[]
|
||||
{
|
||||
new { Id = "513_S", Nombre = "CANDIDATURA 513", BancasTotales = 21, BancasEnJuego = 10, Color = "#1f77b4" },
|
||||
new { Id = "516_S", Nombre = "CANDIDATURA 516", BancasTotales = 9, BancasEnJuego = 5, Color = "#2ca02c" },
|
||||
new { Id = "511_S", Nombre = "CANDIDATURA 511", BancasTotales = 11, BancasEnJuego = 6, Color = "#ff7f0e" },
|
||||
new { Id = "507_S", Nombre = "CANDIDATURA 507", BancasTotales = 5, BancasEnJuego = 2, Color = "#9467bd" }
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(new { Diputados = diputados, Senadores = senadores });
|
||||
}
|
||||
}
|
||||
@@ -40,16 +40,31 @@ public class TelegramasController : ControllerBase
|
||||
[HttpGet("{mesaId}")]
|
||||
public async Task<IActionResult> GetTelegramaPorId(string mesaId)
|
||||
{
|
||||
// PASO 1: Buscar el ámbito geográfico que corresponde a esta MesaId.
|
||||
// Esto nos dará el ID interno (clave primaria) del ámbito.
|
||||
var ambitoMesa = await _dbContext.AmbitosGeograficos
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(a => a.MesaId == mesaId && a.NivelId == 70);
|
||||
|
||||
if (ambitoMesa == null)
|
||||
{
|
||||
// Si no encontramos el ámbito, significa que el MesaId no es válido.
|
||||
return NotFound(new { message = $"No se encontró un ámbito geográfico para la mesa con ID {mesaId}" });
|
||||
}
|
||||
|
||||
// PASO 2: Usar el ID interno del ámbito para buscar el telegrama.
|
||||
// Esta es la relación correcta entre las tablas.
|
||||
var telegrama = await _dbContext.Telegramas
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t => t.Id == mesaId);
|
||||
.FirstOrDefaultAsync(t => t.AmbitoGeograficoId == ambitoMesa.Id);
|
||||
|
||||
if (telegrama == null)
|
||||
{
|
||||
return NotFound(new { message = $"No se encontró el telegrama con ID {mesaId}" });
|
||||
// Si encontramos el ámbito pero no el telegrama, significa que aún no ha sido descargado.
|
||||
return NotFound(new { message = $"Se encontró la mesa, pero su telegrama aún no ha sido procesado." });
|
||||
}
|
||||
|
||||
// Devolvemos todos los datos del telegrama
|
||||
// Devolvemos los datos del telegrama encontrado.
|
||||
return Ok(new
|
||||
{
|
||||
telegrama.Id,
|
||||
|
||||
@@ -14,7 +14,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+13c6accd156385dfc057e9dd765349535f494139")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8192185bc567f6103d3457235798b1cefca61239")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["Dji\u002Bta/0e7zUKw3oe\u002BriV3kbWxZ93FP2z2QIYsHXTl4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/dERIyc1JOhwFtrVKNy7mb/2h9NWmiwO1FwPtFm4Im0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","yTq/Ml6GIPmYajoAWVY9apLuQ9\u002B9//ZN/AKmDBmwvBg="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["Dji\u002Bta/0e7zUKw3oe\u002BriV3kbWxZ93FP2z2QIYsHXTl4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","pJWMHkD90O0wJmytQzhndfB28\u002Bh41dgWfu2x4/0cWSQ=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","b188IfzQfT2K5xJ83wmDTA9\u002B6jiF27JwsgOAO2dXlcY="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["Dji\u002Bta/0e7zUKw3oe\u002BriV3kbWxZ93FP2z2QIYsHXTl4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/dERIyc1JOhwFtrVKNy7mb/2h9NWmiwO1FwPtFm4Im0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","yTq/Ml6GIPmYajoAWVY9apLuQ9\u002B9//ZN/AKmDBmwvBg="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["Dji\u002Bta/0e7zUKw3oe\u002BriV3kbWxZ93FP2z2QIYsHXTl4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","pJWMHkD90O0wJmytQzhndfB28\u002Bh41dgWfu2x4/0cWSQ=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","b188IfzQfT2K5xJ83wmDTA9\u002B6jiF27JwsgOAO2dXlcY="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+13c6accd156385dfc057e9dd765349535f494139")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8192185bc567f6103d3457235798b1cefca61239")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+13c6accd156385dfc057e9dd765349535f494139")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8192185bc567f6103d3457235798b1cefca61239")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+13c6accd156385dfc057e9dd765349535f494139")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8192185bc567f6103d3457235798b1cefca61239")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -237,8 +237,6 @@ public class LowPriorityDataWorker : BackgroundService
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sondea la proyección de bancas a nivel Provincial y por Sección Electoral.
|
||||
/// Esta versión es completamente robusta: maneja respuestas de API vacías o con fechas mal formadas,
|
||||
@@ -410,21 +408,16 @@ public class LowPriorityDataWorker : BackgroundService
|
||||
|
||||
if (!partidos.Any() || !categorias.Any()) return;
|
||||
|
||||
// --- LÓGICA DE GOTEO LENTO ---
|
||||
// Procesamos una combinación (partido/categoría) a la vez.
|
||||
foreach (var partido in partidos)
|
||||
{
|
||||
foreach (var categoria in categorias)
|
||||
{
|
||||
// Si la aplicación se apaga, salimos inmediatamente.
|
||||
if (stoppingToken.IsCancellationRequested) return;
|
||||
|
||||
// Obtenemos la lista de IDs.
|
||||
var listaTelegramasApi = await _apiService.GetTelegramasTotalizadosAsync(authToken, partido.DistritoId!, partido.SeccionId!, categoria.Id);
|
||||
|
||||
if (listaTelegramasApi is { Count: > 0 })
|
||||
{
|
||||
// Usamos un DbContext propio para este bloque para asegurar que los cambios se guarden.
|
||||
using var innerScope = _serviceProvider.CreateScope();
|
||||
var innerDbContext = innerScope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
||||
|
||||
@@ -439,7 +432,6 @@ public class LowPriorityDataWorker : BackgroundService
|
||||
{
|
||||
_logger.LogInformation("Se encontraron {count} telegramas nuevos en '{partido}' para '{cat}'. Descargando...", nuevosTelegramasIds.Count, partido.Nombre, categoria.Nombre);
|
||||
|
||||
// Descargamos los archivos de uno en uno, con una pausa entre cada uno.
|
||||
foreach (var mesaId in nuevosTelegramasIds)
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested) return;
|
||||
@@ -447,24 +439,37 @@ public class LowPriorityDataWorker : BackgroundService
|
||||
var telegramaFile = await _apiService.GetTelegramaFileAsync(authToken, mesaId);
|
||||
if (telegramaFile != null)
|
||||
{
|
||||
var nuevoTelegrama = new Telegrama
|
||||
// --- INICIO DE LA CORRECCIÓN ---
|
||||
// 1. Buscamos el AmbitoGeografico específico de la MESA que estamos procesando.
|
||||
var ambitoMesa = await innerDbContext.AmbitosGeograficos
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(a => a.MesaId == mesaId, stoppingToken);
|
||||
|
||||
// 2. Solo guardamos el telegrama si encontramos su ámbito de mesa correspondiente.
|
||||
if (ambitoMesa != null)
|
||||
{
|
||||
Id = telegramaFile.NombreArchivo,
|
||||
AmbitoGeograficoId = partido.Id,
|
||||
ContenidoBase64 = telegramaFile.Imagen,
|
||||
FechaEscaneo = DateTime.Parse(telegramaFile.FechaEscaneo).ToUniversalTime(),
|
||||
FechaTotalizacion = DateTime.Parse(telegramaFile.FechaTotalizacion).ToUniversalTime()
|
||||
};
|
||||
await innerDbContext.Telegramas.AddAsync(nuevoTelegrama, stoppingToken);
|
||||
var nuevoTelegrama = new Telegrama
|
||||
{
|
||||
Id = telegramaFile.NombreArchivo,
|
||||
// 3. Usamos el ID del ÁMBITO DE LA MESA, no el del municipio.
|
||||
AmbitoGeograficoId = ambitoMesa.Id,
|
||||
ContenidoBase64 = telegramaFile.Imagen,
|
||||
FechaEscaneo = DateTime.Parse(telegramaFile.FechaEscaneo).ToUniversalTime(),
|
||||
FechaTotalizacion = DateTime.Parse(telegramaFile.FechaTotalizacion).ToUniversalTime()
|
||||
};
|
||||
await innerDbContext.Telegramas.AddAsync(nuevoTelegrama, stoppingToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("No se encontró un ámbito geográfico para la mesa con MesaId {MesaId}. El telegrama no será guardado.", mesaId);
|
||||
}
|
||||
// --- FIN DE LA CORRECCIÓN ---
|
||||
}
|
||||
// PAUSA DELIBERADA: Esperamos un poco para no parecer un bot.
|
||||
await Task.Delay(250, stoppingToken); // 250ms de espera = 4 peticiones/segundo máximo.
|
||||
await Task.Delay(250, stoppingToken);
|
||||
}
|
||||
await innerDbContext.SaveChangesAsync(stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
// PAUSA DELIBERADA: Esperamos un poco entre cada consulta de lista de telegramas.
|
||||
await Task.Delay(100, stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user