From 8192185bc567f6103d3457235798b1cefca61239 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Sat, 23 Aug 2025 13:19:35 -0300 Subject: [PATCH] Fix fecha totalizados nivel provincia --- .../net9.0/Elecciones.Api.AssemblyInfo.cs | 2 +- .../Debug/net9.0/rjsmcshtml.dswa.cache.json | 2 +- .../Debug/net9.0/rjsmrazor.dswa.cache.json | 2 +- .../src/Elecciones.Core/DTOs/ResumenDto.cs | 16 +- .../net9.0/Elecciones.Core.AssemblyInfo.cs | 2 +- .../Entities/EstadoRecuentoGeneral.cs | 1 + ...chaTotalizacionToEstadoGeneral.Designer.cs | 380 ++++++++++++++++++ ...853_AddFechaTotalizacionToEstadoGeneral.cs | 30 ++ .../EleccionesDbContextModelSnapshot.cs | 3 + .../Elecciones.Database.AssemblyInfo.cs | 2 +- .../Elecciones.Infrastructure.AssemblyInfo.cs | 2 +- .../Elecciones.Worker/CriticalDataWorker.cs | 112 ++++-- 12 files changed, 518 insertions(+), 36 deletions(-) create mode 100644 Elecciones-Web/src/Elecciones.Database/Migrations/20250823160853_AddFechaTotalizacionToEstadoGeneral.Designer.cs create mode 100644 Elecciones-Web/src/Elecciones.Database/Migrations/20250823160853_AddFechaTotalizacionToEstadoGeneral.cs diff --git a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs index 551752b..aa332f2 100644 --- a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/Elecciones.Api.AssemblyInfo.cs @@ -14,7 +14,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+303a469c57fa05bb9274569591bd4dc9aeb9f240")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+13c6accd156385dfc057e9dd765349535f494139")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json index a20a2b0..2a8c961 100644 --- a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json +++ b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["Dji\u002Bta/0e7zUKw3oe\u002BriV3kbWxZ93FP2z2QIYsHXTl4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","\u002B1WVFC1fp/70404bSmvblp/iQiRDfBqAK2zILDLIK04=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","NjY7JdiCu55MOnreQavF53Y5DhSnDcSEpiAcbi6UO60="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"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":{}} \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json index 58b0034..48d62e5 100644 --- a/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json +++ b/Elecciones-Web/src/Elecciones.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["Dji\u002Bta/0e7zUKw3oe\u002BriV3kbWxZ93FP2z2QIYsHXTl4=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","\u002B1WVFC1fp/70404bSmvblp/iQiRDfBqAK2zILDLIK04=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","NjY7JdiCu55MOnreQavF53Y5DhSnDcSEpiAcbi6UO60="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"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":{}} \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Core/DTOs/ResumenDto.cs b/Elecciones-Web/src/Elecciones.Core/DTOs/ResumenDto.cs index 95688af..ceebad3 100644 --- a/Elecciones-Web/src/Elecciones.Core/DTOs/ResumenDto.cs +++ b/Elecciones-Web/src/Elecciones.Core/DTOs/ResumenDto.cs @@ -3,17 +3,25 @@ using System.Collections.Generic; namespace Elecciones.Core.DTOs; +// DTO para la respuesta del endpoint /resultados/getResumen public class ResumenDto { + [JsonPropertyName("fechaTotalizacion")] + public string FechaTotalizacion { get; set; } = string.Empty; + + [JsonPropertyName("estadoRecuento")] + public EstadoRecuentoDto EstadoRecuento { get; set; } = new(); + [JsonPropertyName("valoresTotalizadosPositivos")] - public List ValoresTotalizadosPositivos { get; set; } = []; + public List ValoresTotalizadosPositivos { get; set; } = []; } -public class ResumenPositivoDto +// DTO específico para los objetos dentro de la lista "valoresTotalizadosPositivos" de /getResumen +public class ResumenVotosPositivosDto { [JsonPropertyName("idAgrupacion")] - public string IdAgrupacion { get; set; } = null!; - + public string IdAgrupacion { get; set; } = string.Empty; + [JsonPropertyName("votos")] public long Votos { get; set; } diff --git a/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs index 28a3576..dc3acc5 100644 --- a/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Core/obj/Debug/net9.0/Elecciones.Core.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+303a469c57fa05bb9274569591bd4dc9aeb9f240")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+13c6accd156385dfc057e9dd765349535f494139")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs b/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs index 3ecdf19..9d92d34 100644 --- a/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs +++ b/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs @@ -8,6 +8,7 @@ public class EstadoRecuentoGeneral { public int AmbitoGeograficoId { get; set; } public int CategoriaId { get; set; } + public DateTime FechaTotalizacion { get; set; } public int MesasEsperadas { get; set; } public int MesasTotalizadas { get; set; } public decimal MesasTotalizadasPorcentaje { get; set; } diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250823160853_AddFechaTotalizacionToEstadoGeneral.Designer.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250823160853_AddFechaTotalizacionToEstadoGeneral.Designer.cs new file mode 100644 index 0000000..1184123 --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250823160853_AddFechaTotalizacionToEstadoGeneral.Designer.cs @@ -0,0 +1,380 @@ +// +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("20250823160853_AddFechaTotalizacionToEstadoGeneral")] + partial class AddFechaTotalizacionToEstadoGeneral + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Elecciones.Database.Entities.AgrupacionPolitica", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("IdTelegrama") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AgrupacionesPoliticas"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.AmbitoGeografico", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CircuitoId") + .HasColumnType("nvarchar(max)"); + + b.Property("DistritoId") + .HasColumnType("nvarchar(max)"); + + b.Property("EstablecimientoId") + .HasColumnType("nvarchar(max)"); + + b.Property("MesaId") + .HasColumnType("nvarchar(max)"); + + b.Property("MunicipioId") + .HasColumnType("nvarchar(max)"); + + b.Property("NivelId") + .HasColumnType("int"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SeccionId") + .HasColumnType("nvarchar(max)"); + + b.Property("SeccionProvincialId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AmbitosGeograficos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.CategoriaElectoral", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Nombre") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Orden") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("CategoriasElectorales"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b => + { + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("CantidadElectores") + .HasColumnType("int"); + + b.Property("CantidadVotantes") + .HasColumnType("int"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.Property("MesasEsperadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadasPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("ParticipacionPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("VotosEnBlanco") + .HasColumnType("bigint"); + + b.Property("VotosEnBlancoPorcentaje") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VotosNulos") + .HasColumnType("bigint"); + + b.Property("VotosNulosPorcentaje") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VotosRecurridos") + .HasColumnType("bigint"); + + b.Property("VotosRecurridosPorcentaje") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("AmbitoGeograficoId", "CategoriaId"); + + b.ToTable("EstadosRecuentos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b => + { + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("CantidadElectores") + .HasColumnType("int"); + + b.Property("CantidadVotantes") + .HasColumnType("int"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.Property("MesasEsperadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadas") + .HasColumnType("int"); + + b.Property("MesasTotalizadasPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("ParticipacionPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("AmbitoGeograficoId", "CategoriaId"); + + b.HasIndex("CategoriaId"); + + b.ToTable("EstadosRecuentosGenerales"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("CantidadVotos") + .HasColumnType("bigint"); + + b.Property("CategoriaId") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AgrupacionPoliticaId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("Votos") + .HasColumnType("bigint"); + + b.Property("VotosPorcentaje") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.ToTable("ResumenesVotos"); + }); + + modelBuilder.Entity("Elecciones.Database.Entities.Telegrama", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AmbitoGeograficoId") + .HasColumnType("int"); + + b.Property("ContenidoBase64") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FechaEscaneo") + .HasColumnType("datetime2"); + + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Telegramas"); + }); + + 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.CategoriaElectoral", "CategoriaElectoral") + .WithMany() + .HasForeignKey("CategoriaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CategoriaElectoral"); + }); + + 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250823160853_AddFechaTotalizacionToEstadoGeneral.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250823160853_AddFechaTotalizacionToEstadoGeneral.cs new file mode 100644 index 0000000..d6948f6 --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250823160853_AddFechaTotalizacionToEstadoGeneral.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elecciones.Database.Migrations +{ + /// + public partial class AddFechaTotalizacionToEstadoGeneral : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FechaTotalizacion", + table: "EstadosRecuentosGenerales", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FechaTotalizacion", + table: "EstadosRecuentosGenerales"); + } + } +} diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs index 1aedc84..df18e27 100644 --- a/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs @@ -169,6 +169,9 @@ namespace Elecciones.Database.Migrations b.Property("CantidadVotantes") .HasColumnType("int"); + b.Property("FechaTotalizacion") + .HasColumnType("datetime2"); + b.Property("MesasEsperadas") .HasColumnType("int"); diff --git a/Elecciones-Web/src/Elecciones.Database/obj/Debug/net9.0/Elecciones.Database.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Database/obj/Debug/net9.0/Elecciones.Database.AssemblyInfo.cs index 28ce639..da6a25b 100644 --- a/Elecciones-Web/src/Elecciones.Database/obj/Debug/net9.0/Elecciones.Database.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Database/obj/Debug/net9.0/Elecciones.Database.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+303a469c57fa05bb9274569591bd4dc9aeb9f240")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+13c6accd156385dfc057e9dd765349535f494139")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs index 120a79a..72df14c 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Infrastructure/obj/Debug/net9.0/Elecciones.Infrastructure.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+303a469c57fa05bb9274569591bd4dc9aeb9f240")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+13c6accd156385dfc057e9dd765349535f494139")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Elecciones-Web/src/Elecciones.Worker/CriticalDataWorker.cs b/Elecciones-Web/src/Elecciones.Worker/CriticalDataWorker.cs index fbad2bc..a0ce962 100644 --- a/Elecciones-Web/src/Elecciones.Worker/CriticalDataWorker.cs +++ b/Elecciones-Web/src/Elecciones.Worker/CriticalDataWorker.cs @@ -184,59 +184,119 @@ public class CriticalDataWorker : BackgroundService } /// - /// Obtiene y actualiza el resumen de votos a nivel provincial. - /// Esta versión mejorada utiliza una transacción para garantizar la consistencia de los datos. + /// Obtiene y actualiza el resumen de votos y el estado del recuento a nivel provincial. + /// Esta versión actualizada guarda tanto los votos por agrupación (en ResumenesVotos) + /// como el estado general del recuento, incluyendo la fecha de totalización (en EstadosRecuentosGenerales), + /// asegurando que toda la operación sea atómica mediante una transacción de base de datos. /// + /// El token de autenticación válido para la sesión. + /// El token de cancelación para detener la operación. private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken) { try { + // Creamos un scope de DbContext para esta operación. using var scope = _serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); - var provincia = await dbContext.AmbitosGeograficos.AsNoTracking().FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken); - if (provincia == null) return; + // Obtenemos el registro de la Provincia (NivelId 10). + var provincia = await dbContext.AmbitosGeograficos + .AsNoTracking() + .FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken); - var resumen = await _apiService.GetResumenAsync(authToken, provincia.DistritoId!); - - // --- CAMBIO CLAVE: Lógica de actualización robusta --- - // Solo procedemos si la respuesta de la API es válida Y contiene datos de votos positivos. - if (resumen?.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos) + // Si no encontramos el ámbito de la provincia, no podemos continuar. + if (provincia == null) { - // Usamos una transacción explícita para asegurar que la operación sea atómica: - // O se completa todo (borrado e inserción), o no se hace nada. + _logger.LogWarning("No se encontró el ámbito 'Provincia' (NivelId 10) para el sondeo de resumen."); + return; + } + + // Llamamos a la API para obtener el resumen de datos provincial. + var resumenDto = await _apiService.GetResumenAsync(authToken, provincia.DistritoId!); + + // Solo procedemos si la API devolvió una respuesta válida y no nula. + if (resumenDto != null) + { + // Iniciamos una transacción explícita. Esto garantiza que todas las operaciones de base de datos + // dentro de este bloque (el DELETE, los INSERTs y los UPDATEs) se completen con éxito, + // o si algo falla, se reviertan todas, manteniendo la consistencia de los datos. await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken); - // 1. Borramos los datos viejos. - await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ResumenesVotos", stoppingToken); + // --- 1. ACTUALIZAR LA TABLA 'ResumenesVotos' --- - // 2. Insertamos los nuevos datos. - foreach (var voto in nuevosVotos) + // Verificamos si la respuesta contiene una lista de votos positivos. + if (resumenDto.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos) { - dbContext.ResumenesVotos.Add(new ResumenVoto + // 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) { - AmbitoGeograficoId = provincia.Id, - AgrupacionPoliticaId = voto.IdAgrupacion, - Votos = voto.Votos, - VotosPorcentaje = voto.VotosPorcentaje - }); + dbContext.ResumenesVotos.Add(new ResumenVoto + { + AmbitoGeograficoId = provincia.Id, + AgrupacionPoliticaId = voto.IdAgrupacion, + Votos = voto.Votos, + VotosPorcentaje = voto.VotosPorcentaje + }); + } } - // 3. Guardamos los cambios y confirmamos la transacción. + // --- 2. ACTUALIZAR LA TABLA 'EstadosRecuentosGenerales' --- + + // El endpoint de Resumen no especifica una categoría, por lo que aplicamos sus datos de estado de recuento + // a todas las categorías que tenemos en nuestra base de datos. + var todasLasCategorias = await dbContext.CategoriasElectorales.AsNoTracking().ToListAsync(stoppingToken); + foreach (var categoria in todasLasCategorias) + { + // Buscamos el registro existente usando la clave primaria compuesta. + var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { provincia.Id, categoria.Id }, stoppingToken); + + // Si no existe, lo creamos. + if (registroDb == null) + { + registroDb = new EstadoRecuentoGeneral { AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id }; + dbContext.EstadosRecuentosGenerales.Add(registroDb); + } + + // Parseamos la fecha de forma segura para evitar errores con cadenas vacías o nulas. + if (DateTime.TryParse(resumenDto.FechaTotalizacion, out var parsedDate)) + { + registroDb.FechaTotalizacion = parsedDate.ToUniversalTime(); + } + + // Mapeamos el resto de los datos del estado del recuento. + registroDb.MesasEsperadas = resumenDto.EstadoRecuento.MesasEsperadas; + registroDb.MesasTotalizadas = resumenDto.EstadoRecuento.MesasTotalizadas; + registroDb.MesasTotalizadasPorcentaje = resumenDto.EstadoRecuento.MesasTotalizadasPorcentaje; + registroDb.CantidadElectores = resumenDto.EstadoRecuento.CantidadElectores; + registroDb.CantidadVotantes = resumenDto.EstadoRecuento.CantidadVotantes; + registroDb.ParticipacionPorcentaje = resumenDto.EstadoRecuento.ParticipacionPorcentaje; + } + + // 3. CONFIRMAR Y GUARDAR + // Guardamos todos los cambios preparados (DELETEs, INSERTs, UPDATEs) en la base de datos. await dbContext.SaveChangesAsync(stoppingToken); + // Confirmamos la transacción para hacer los cambios permanentes. await transaction.CommitAsync(stoppingToken); - _logger.LogInformation("Sondeo de Resumen Provincial completado. La tabla ha sido actualizada."); + _logger.LogInformation("Sondeo de Resumen Provincial completado. Las tablas han sido actualizadas."); } else { - // Si la API no devuelve datos de votos, no hacemos NADA en la base de datos. - _logger.LogInformation("Sondeo de Resumen Provincial completado. No se recibieron datos nuevos, la tabla no fue modificada."); + // Si la API no devolvió datos (ej. devuelve null), no hacemos nada en la BD. + _logger.LogInformation("Sondeo de Resumen Provincial completado. No se recibieron datos nuevos."); } } + catch (OperationCanceledException) + { + _logger.LogInformation("Sondeo de resumen provincial cancelado."); + } catch (Exception ex) { - _logger.LogError(ex, "Ocurrió un error en el sondeo de Resumen Provincial."); + // Capturamos cualquier otro error inesperado para que el worker no se detenga. + _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Resumen Provincial."); } }