Fix fecha totalizados nivel provincia
This commit is contained in:
		| @@ -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")] | ||||
|   | ||||
| @@ -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":{}} | ||||
| {"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":{}} | ||||
| @@ -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":{}} | ||||
| {"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":{}} | ||||
| @@ -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<ResumenPositivoDto> ValoresTotalizadosPositivos { get; set; } = []; | ||||
|     public List<ResumenVotosPositivosDto> 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; } | ||||
|  | ||||
|   | ||||
| @@ -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")] | ||||
|   | ||||
| @@ -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; } | ||||
|   | ||||
| @@ -0,0 +1,380 @@ | ||||
| // <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("20250823160853_AddFechaTotalizacionToEstadoGeneral")] | ||||
|     partial class AddFechaTotalizacionToEstadoGeneral | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         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<string>("Id") | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<string>("IdTelegrama") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     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.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.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<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<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.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<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<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(max)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<long>("Votos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     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<DateTime>("FechaEscaneo") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<DateTime>("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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| using System; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace Elecciones.Database.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class AddFechaTotalizacionToEstadoGeneral : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AddColumn<DateTime>( | ||||
|                 name: "FechaTotalizacion", | ||||
|                 table: "EstadosRecuentosGenerales", | ||||
|                 type: "datetime2", | ||||
|                 nullable: false, | ||||
|                 defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropColumn( | ||||
|                 name: "FechaTotalizacion", | ||||
|                 table: "EstadosRecuentosGenerales"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -169,6 +169,9 @@ namespace Elecciones.Database.Migrations | ||||
|                     b.Property<int>("CantidadVotantes") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<int>("MesasEsperadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|   | ||||
| @@ -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")] | ||||
|   | ||||
| @@ -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")] | ||||
|   | ||||
| @@ -184,59 +184,119 @@ public class CriticalDataWorker : BackgroundService | ||||
|   } | ||||
|  | ||||
|   /// <summary> | ||||
|   /// 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. | ||||
|   /// </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) | ||||
|   { | ||||
|     try | ||||
|     { | ||||
|       // Creamos un scope de DbContext para esta operación. | ||||
|       using var scope = _serviceProvider.CreateScope(); | ||||
|       var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>(); | ||||
|  | ||||
|       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."); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user