Fix Worker
This commit is contained in:
		
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -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+18e6e8d3c0a378a172ad8e8afd31109673460717")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5de9d6729c40c2a0d191760aa879b57282ff37e1")] | ||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")] | ||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")] | ||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||
|   | ||||
| @@ -173,7 +173,6 @@ E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Api\bin\Debug\net9.0\Microsoft. | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Api\bin\Debug\net9.0\Microsoft.Extensions.Diagnostics.Abstractions.dll | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Api\bin\Debug\net9.0\Microsoft.Extensions.Http.dll | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Api\bin\Debug\net9.0\Microsoft.Extensions.Options.ConfigurationExtensions.dll | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Api\bin\Debug\net9.0\buenos-aires-municipios.geojson | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Api\bin\Debug\net9.0\Serilog.dll | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Api\bin\Debug\net9.0\Serilog.AspNetCore.dll | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Api\bin\Debug\net9.0\Serilog.Extensions.Hosting.dll | ||||
|   | ||||
| @@ -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=","42z\u002Bw0pajpMLFLNS29VoU/hUn9IzvZ/pVLNadS0rApY=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","ifGdmI/zx2hsc8PYkk8IWTP8aZ9RYmaQbfk383bAiYQ="],"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=","GjNU75/P064gZSGHRGFQCzuU1j91Y6ISVIG9G9dpcs0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","Oxph25v5ZpGJ3gq0\u002BdtskQ7kRY5YG8YA8z83gWvNxyw="],"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=","42z\u002Bw0pajpMLFLNS29VoU/hUn9IzvZ/pVLNadS0rApY=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","ifGdmI/zx2hsc8PYkk8IWTP8aZ9RYmaQbfk383bAiYQ="],"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=","GjNU75/P064gZSGHRGFQCzuU1j91Y6ISVIG9G9dpcs0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","Oxph25v5ZpGJ3gq0\u002BdtskQ7kRY5YG8YA8z83gWvNxyw="],"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+18e6e8d3c0a378a172ad8e8afd31109673460717")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5de9d6729c40c2a0d191760aa879b57282ff37e1")] | ||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")] | ||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")] | ||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||
|   | ||||
| @@ -27,6 +27,7 @@ public class EleccionesDbContext(DbContextOptions<EleccionesDbContext> options) | ||||
|         // Precisión para los campos de porcentaje en EstadoRecuento | ||||
|         modelBuilder.Entity<EstadoRecuento>(entity => | ||||
|         { | ||||
|             entity.HasKey(e => new { e.AmbitoGeograficoId, e.CategoriaId }); | ||||
|             entity.Property(e => e.MesasTotalizadasPorcentaje).HasPrecision(5, 2); | ||||
|             entity.Property(e => e.ParticipacionPorcentaje).HasPrecision(5, 2); | ||||
|             entity.Property(e => e.VotosNulosPorcentaje).HasPrecision(18, 4); | ||||
|   | ||||
| @@ -8,9 +8,9 @@ public class EstadoRecuento | ||||
| { | ||||
|     [Key] | ||||
|     public int AmbitoGeograficoId { get; set; } | ||||
|     public int CategoriaId { get; set; } | ||||
|     [ForeignKey("AmbitoGeograficoId")] | ||||
|     public AmbitoGeografico AmbitoGeografico { get; set; } = null!; | ||||
|  | ||||
|     public AmbitoGeografico AmbitoGeografico { get; set; } = null!;     | ||||
|     public DateTime FechaTotalizacion { get; set; } | ||||
|     public int MesasEsperadas { get; set; } | ||||
|     public int MesasTotalizadas { get; set; } | ||||
|   | ||||
| @@ -11,6 +11,8 @@ public class ResultadoVoto | ||||
|     public long Id { get; set; } | ||||
|  | ||||
|     public int AmbitoGeograficoId { get; set; } | ||||
|     public int CategoriaId { get; set; } | ||||
|      | ||||
|     [ForeignKey("AmbitoGeograficoId")] | ||||
|     public AmbitoGeografico AmbitoGeografico { get; set; } = null!; | ||||
|  | ||||
|   | ||||
							
								
								
									
										370
									
								
								Elecciones-Web/src/Elecciones.Database/Migrations/20250823135433_AddCategoriaIdToResultados.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										370
									
								
								Elecciones-Web/src/Elecciones.Database/Migrations/20250823135433_AddCategoriaIdToResultados.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,370 @@ | ||||
| // <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("20250823135433_AddCategoriaIdToResultados")] | ||||
|     partial class AddCategoriaIdToResultados | ||||
|     { | ||||
|         /// <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<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>("NroBancas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.HasIndex("AmbitoGeograficoId"); | ||||
|  | ||||
|                     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", "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,58 @@ | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace Elecciones.Database.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class AddCategoriaIdToResultados : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropPrimaryKey( | ||||
|                 name: "PK_EstadosRecuentos", | ||||
|                 table: "EstadosRecuentos"); | ||||
|  | ||||
|             migrationBuilder.AddColumn<int>( | ||||
|                 name: "CategoriaId", | ||||
|                 table: "ResultadosVotos", | ||||
|                 type: "int", | ||||
|                 nullable: false, | ||||
|                 defaultValue: 0); | ||||
|  | ||||
|             migrationBuilder.AddColumn<int>( | ||||
|                 name: "CategoriaId", | ||||
|                 table: "EstadosRecuentos", | ||||
|                 type: "int", | ||||
|                 nullable: false, | ||||
|                 defaultValue: 0); | ||||
|  | ||||
|             migrationBuilder.AddPrimaryKey( | ||||
|                 name: "PK_EstadosRecuentos", | ||||
|                 table: "EstadosRecuentos", | ||||
|                 columns: new[] { "AmbitoGeograficoId", "CategoriaId" }); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropPrimaryKey( | ||||
|                 name: "PK_EstadosRecuentos", | ||||
|                 table: "EstadosRecuentos"); | ||||
|  | ||||
|             migrationBuilder.DropColumn( | ||||
|                 name: "CategoriaId", | ||||
|                 table: "ResultadosVotos"); | ||||
|  | ||||
|             migrationBuilder.DropColumn( | ||||
|                 name: "CategoriaId", | ||||
|                 table: "EstadosRecuentos"); | ||||
|  | ||||
|             migrationBuilder.AddPrimaryKey( | ||||
|                 name: "PK_EstadosRecuentos", | ||||
|                 table: "EstadosRecuentos", | ||||
|                 column: "AmbitoGeograficoId"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -103,6 +103,9 @@ namespace Elecciones.Database.Migrations | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadElectores") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
| @@ -147,7 +150,7 @@ namespace Elecciones.Database.Migrations | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.HasKey("AmbitoGeograficoId"); | ||||
|                     b.HasKey("AmbitoGeograficoId", "CategoriaId"); | ||||
|  | ||||
|                     b.ToTable("EstadosRecuentos"); | ||||
|                 }); | ||||
| @@ -232,6 +235,9 @@ namespace Elecciones.Database.Migrations | ||||
|                     b.Property<long>("CantidadVotos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<decimal>("PorcentajeVotos") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|   | ||||
| @@ -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+18e6e8d3c0a378a172ad8e8afd31109673460717")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5de9d6729c40c2a0d191760aa879b57282ff37e1")] | ||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")] | ||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")] | ||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -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+18e6e8d3c0a378a172ad8e8afd31109673460717")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5de9d6729c40c2a0d191760aa879b57282ff37e1")] | ||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] | ||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] | ||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||
|   | ||||
| @@ -13,4 +13,3 @@ E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\obj\Debug\net9.0 | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\obj\Debug\net9.0\refint\Elecciones.Infrastructure.dll | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\obj\Debug\net9.0\Elecciones.Infrastructure.pdb | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\obj\Debug\net9.0\ref\Elecciones.Infrastructure.dll | ||||
| E:\Elecciones-2025\Elecciones-Web\src\Elecciones.Infrastructure\bin\Debug\net9.0\buenos-aires-municipios.geojson | ||||
|   | ||||
| @@ -11,38 +11,35 @@ public class CriticalDataWorker : BackgroundService | ||||
|   private readonly ILogger<CriticalDataWorker> _logger; | ||||
|   private readonly SharedTokenService _tokenService; | ||||
|   private readonly IServiceProvider _serviceProvider; | ||||
|   private readonly IElectoralApiService _apiService; // <-- DEPENDENCIA AÑADIDA | ||||
|   private readonly IElectoralApiService _apiService; | ||||
|  | ||||
|   // Inyectamos IElectoralApiService en el constructor | ||||
|   public CriticalDataWorker( | ||||
|       ILogger<CriticalDataWorker> logger, | ||||
|       SharedTokenService tokenService, | ||||
|       IServiceProvider serviceProvider, | ||||
|       IElectoralApiService apiService) // <-- PARÁMETRO AÑADIDO | ||||
|       IElectoralApiService apiService) | ||||
|   { | ||||
|     _logger = logger; | ||||
|     _tokenService = tokenService; | ||||
|     _serviceProvider = serviceProvider; | ||||
|     _apiService = apiService; // <-- ASIGNACIÓN AÑADIDA | ||||
|     _apiService = apiService; | ||||
|   } | ||||
|  | ||||
|   protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|   { | ||||
|     _logger.LogInformation("Worker de Datos Críticos iniciado."); | ||||
|  | ||||
|     // Damos tiempo a la sincronización inicial del otro worker para que se complete. | ||||
|     try | ||||
|     { | ||||
|       await Task.Delay(TimeSpan.FromMinutes(2), stoppingToken); | ||||
|     } | ||||
|     catch (TaskCanceledException) { return; } // Salir si la app se apaga durante la espera inicial | ||||
|     catch (TaskCanceledException) { return; } | ||||
|  | ||||
|     int cicloContador = 0; | ||||
|     while (!stoppingToken.IsCancellationRequested) | ||||
|     { | ||||
|       var cicloInicio = DateTime.UtcNow; | ||||
|       cicloContador++; | ||||
|  | ||||
|       _logger.LogInformation("--- Iniciando Ciclo de Datos Críticos #{ciclo} ---", cicloContador); | ||||
|  | ||||
|       var authToken = await _tokenService.GetValidAuthTokenAsync(stoppingToken); | ||||
| @@ -68,200 +65,122 @@ public class CriticalDataWorker : BackgroundService | ||||
|       { | ||||
|         await Task.Delay(tiempoDeEspera, stoppingToken); | ||||
|       } | ||||
|       catch (TaskCanceledException) | ||||
|       { | ||||
|         break; | ||||
|       } | ||||
|       catch (TaskCanceledException) { break; } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /// <summary> | ||||
|   /// Sondea los resultados electorales para todos los municipios/partidos de forma optimizada. | ||||
|   /// Utiliza paralelismo controlado para ejecutar múltiples peticiones a la API simultáneamente | ||||
|   /// sin sobrecargar la red, y luego guarda todos los resultados en la base de datos de forma masiva. | ||||
|   /// </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 SondearResultadosMunicipalesAsync(string authToken, CancellationToken stoppingToken) | ||||
|   { | ||||
|     try | ||||
|     { | ||||
|       // PASO 1: Preparar el DbContext y los datos necesarios. | ||||
|       using var scope = _serviceProvider.CreateScope(); | ||||
|       var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>(); | ||||
|  | ||||
|       // Obtenemos de nuestra BD local la lista de todos los partidos (NivelId=30) que necesitamos consultar. | ||||
|       var municipiosASondear = await dbContext.AmbitosGeograficos | ||||
|           .AsNoTracking() | ||||
|           .Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null) | ||||
|           .Select(a => new { a.Id, a.Nombre, a.MunicipioId, a.SeccionId, a.DistritoId }) | ||||
|           .ToListAsync(stoppingToken); | ||||
|  | ||||
|       if (!municipiosASondear.Any()) | ||||
|       { | ||||
|         _logger.LogWarning("No se encontraron Partidos (NivelId 30) en la BD para sondear resultados."); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       // Obtenemos la categoría "CONCEJALES", ya que los resultados municipales aplican a esta. | ||||
|       var categoriaConcejales = await dbContext.CategoriasElectorales | ||||
|       var todasLasCategorias = await dbContext.CategoriasElectorales | ||||
|           .AsNoTracking() | ||||
|           .FirstOrDefaultAsync(c => c.Nombre.Contains("CONCEJALES"), stoppingToken); | ||||
|           .ToListAsync(stoppingToken); | ||||
|  | ||||
|       if (categoriaConcejales == null) | ||||
|       if (!municipiosASondear.Any() || !todasLasCategorias.Any()) | ||||
|       { | ||||
|         _logger.LogWarning("No se encontró la categoría 'CONCEJALES'. Omitiendo sondeo de resultados municipales."); | ||||
|         _logger.LogWarning("No se encontraron Partidos (NivelId 30) o Categorías para sondear resultados."); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       // PASO 2: Ejecutar las consultas a la API con paralelismo controlado. | ||||
|       _logger.LogInformation("Iniciando sondeo de resultados para {m} municipios y {c} categorías...", municipiosASondear.Count, todasLasCategorias.Count); | ||||
|  | ||||
|       // Definimos cuántas peticiones queremos que se ejecuten simultáneamente. | ||||
|       // Un valor entre 8 y 16 es generalmente seguro y ofrece una gran mejora de velocidad. | ||||
|       const int GRADO_DE_PARALELISMO = 3; | ||||
|       // Creamos un semáforo que actuará como un "control de acceso" con 10 pases libres. | ||||
|       var semaforo = new SemaphoreSlim(GRADO_DE_PARALELISMO); | ||||
|  | ||||
|       // Usamos un ConcurrentDictionary para almacenar los resultados. A diferencia de un Dictionary normal, | ||||
|       // este permite que múltiples tareas escriban en él al mismo tiempo sin conflictos. | ||||
|       var resultadosPorId = new ConcurrentDictionary<int, Elecciones.Core.DTOs.ResultadosDto>(); | ||||
|  | ||||
|       _logger.LogInformation("Iniciando sondeo de resultados para {count} municipios con un paralelismo de {degree}...", municipiosASondear.Count, GRADO_DE_PARALELISMO); | ||||
|  | ||||
|       // Creamos una lista de tareas (Tasks), una por cada municipio a consultar. | ||||
|       // El método .Select() no ejecuta las tareas todavía, solo las prepara. | ||||
|       var tareas = municipiosASondear.Select(async municipio => | ||||
|       foreach (var municipio in municipiosASondear) | ||||
|       { | ||||
|         // Cada tarea debe "pedir permiso" al semáforo antes de ejecutarse. | ||||
|         // Si ya hay 10 tareas en ejecución, esta línea esperará hasta que una termine. | ||||
|         await semaforo.WaitAsync(stoppingToken); | ||||
|         try | ||||
|         { | ||||
|           // Una vez que obtiene el permiso, ejecuta la petición a la API. | ||||
|           var resultados = await _apiService.GetResultadosAsync( | ||||
|                     authToken, municipio.DistritoId!, municipio.SeccionId!, null, categoriaConcejales.Id | ||||
|                 ); | ||||
|         if (stoppingToken.IsCancellationRequested) break; | ||||
|  | ||||
|         var tareasCategoria = todasLasCategorias.Select(async categoria => | ||||
|         { | ||||
|           var resultados = await _apiService.GetResultadosAsync(authToken, municipio.DistritoId!, municipio.SeccionId!, null, categoria.Id); | ||||
|  | ||||
|           // Si la API devuelve datos válidos... | ||||
|           if (resultados != null) | ||||
|           { | ||||
|             // ...los guardamos en el diccionario concurrente. | ||||
|             resultadosPorId[municipio.Id] = resultados; | ||||
|             using var innerScope = _serviceProvider.CreateScope(); | ||||
|             var innerDbContext = innerScope.ServiceProvider.GetRequiredService<EleccionesDbContext>(); | ||||
|  | ||||
|             // --- LLAMADA CORRECTA --- | ||||
|             await GuardarResultadosDeAmbitoAsync(innerDbContext, municipio.Id, categoria.Id, resultados, stoppingToken); | ||||
|           } | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|           // ¡CRUCIAL! Liberamos el pase del semáforo, permitiendo que la siguiente | ||||
|           // tarea en espera pueda comenzar su ejecución. | ||||
|           semaforo.Release(); | ||||
|           // Añadir un pequeño retraso aleatorio para no parecer un robot | ||||
|           await Task.Delay(TimeSpan.FromMilliseconds(new Random().Next(50, 251)), stoppingToken); | ||||
|         } | ||||
|       }); | ||||
|         }); | ||||
|  | ||||
|       // Ahora sí, ejecutamos todas las tareas preparadas en paralelo y esperamos a que todas terminen. | ||||
|       await Task.WhenAll(tareas); | ||||
|  | ||||
|       // PASO 3: Guardar los resultados en la base de datos. | ||||
|       // Solo procedemos si recolectamos al menos un resultado válido. | ||||
|       if (resultadosPorId.Any()) | ||||
|       { | ||||
|         // Llamamos a nuestro método de guardado masivo y optimizado, pasándole todos los resultados | ||||
|         // recolectados para que los inserte en una única y eficiente transacción. | ||||
|         await GuardarResultadosDeMunicipiosAsync(dbContext, resultadosPorId.ToDictionary(kv => kv.Key, kv => kv.Value), stoppingToken); | ||||
|         await Task.WhenAll(tareasCategoria); | ||||
|       } | ||||
|     } | ||||
|     catch (Exception ex) | ||||
|     { | ||||
|       // Capturamos cualquier error inesperado en el proceso para que el worker no se detenga. | ||||
|       _logger.LogError(ex, "Ocurrió un error inesperado durante el sondeo de resultados municipales."); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /// Realiza una operación "Upsert" (Update o Insert) de forma masiva y optimizada. | ||||
|   /// Este método es llamado por SondearResultadosMunicipalesAsync. | ||||
|   /// </summary> | ||||
|   private async Task GuardarResultadosDeMunicipiosAsync( | ||||
|       EleccionesDbContext dbContext, | ||||
|       Dictionary<int, Elecciones.Core.DTOs.ResultadosDto> todosLosResultados, | ||||
|       CancellationToken stoppingToken) // <-- PARÁMETRO AÑADIDO | ||||
|   private async Task GuardarResultadosDeAmbitoAsync( | ||||
|       EleccionesDbContext dbContext, int ambitoId, int categoriaId, | ||||
|       Elecciones.Core.DTOs.ResultadosDto resultadosDto, CancellationToken stoppingToken) | ||||
|   { | ||||
|     // Obtenemos los IDs de todos los ámbitos que vamos a actualizar. | ||||
|     var ambitoIds = todosLosResultados.Keys; | ||||
|     var estadoRecuento = await dbContext.EstadosRecuentos.FindAsync(new object[] { ambitoId, categoriaId }, stoppingToken); | ||||
|  | ||||
|     // --- OPTIMIZACIÓN 1: Cargar todos los datos existentes en memoria UNA SOLA VEZ --- | ||||
|     var estadosRecuentoExistentes = await dbContext.EstadosRecuentos | ||||
|         .Where(e => ambitoIds.Contains(e.AmbitoGeograficoId)) | ||||
|         .ToDictionaryAsync(e => e.AmbitoGeograficoId, stoppingToken); | ||||
|  | ||||
|     var resultadosVotosExistentes = await dbContext.ResultadosVotos | ||||
|         .Where(rv => ambitoIds.Contains(rv.AmbitoGeograficoId)) | ||||
|         .GroupBy(rv => rv.AmbitoGeograficoId) | ||||
|         .ToDictionaryAsync(g => g.Key, g => g.ToDictionary(item => item.AgrupacionPoliticaId), stoppingToken); | ||||
|  | ||||
|     _logger.LogInformation("Procesando en memoria los resultados de {count} municipios.", todosLosResultados.Count); | ||||
|  | ||||
|     // --- OPTIMIZACIÓN 2: Procesar todo en memoria --- | ||||
|     foreach (var kvp in todosLosResultados) | ||||
|     if (estadoRecuento == null) | ||||
|     { | ||||
|       var ambitoId = kvp.Key; | ||||
|       var resultadosDto = kvp.Value; | ||||
|  | ||||
|       // Lógica Upsert para EstadoRecuento | ||||
|       if (!estadosRecuentoExistentes.TryGetValue(ambitoId, out var estadoRecuento)) | ||||
|       { | ||||
|         estadoRecuento = new EstadoRecuento { AmbitoGeograficoId = ambitoId }; | ||||
|         dbContext.EstadosRecuentos.Add(estadoRecuento); | ||||
|       } | ||||
|  | ||||
|       // Mapeo completo de propiedades para EstadoRecuento | ||||
|       estadoRecuento.FechaTotalizacion = DateTime.Parse(resultadosDto.FechaTotalizacion).ToUniversalTime(); | ||||
|       estadoRecuento.MesasEsperadas = resultadosDto.EstadoRecuento.MesasEsperadas; | ||||
|       estadoRecuento.MesasTotalizadas = resultadosDto.EstadoRecuento.MesasTotalizadas; | ||||
|       estadoRecuento.MesasTotalizadasPorcentaje = resultadosDto.EstadoRecuento.MesasTotalizadasPorcentaje; | ||||
|       estadoRecuento.CantidadElectores = resultadosDto.EstadoRecuento.CantidadElectores; | ||||
|       estadoRecuento.CantidadVotantes = resultadosDto.EstadoRecuento.CantidadVotantes; | ||||
|       estadoRecuento.ParticipacionPorcentaje = resultadosDto.EstadoRecuento.ParticipacionPorcentaje; | ||||
|  | ||||
|       if (resultadosDto.ValoresTotalizadosOtros != null) | ||||
|       { | ||||
|         estadoRecuento.VotosEnBlanco = resultadosDto.ValoresTotalizadosOtros.VotosEnBlanco; | ||||
|         estadoRecuento.VotosEnBlancoPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosEnBlancoPorcentaje; | ||||
|         estadoRecuento.VotosNulos = resultadosDto.ValoresTotalizadosOtros.VotosNulos; | ||||
|         estadoRecuento.VotosNulosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosNulosPorcentaje; | ||||
|         estadoRecuento.VotosRecurridos = resultadosDto.ValoresTotalizadosOtros.VotosRecurridos; | ||||
|         estadoRecuento.VotosRecurridosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosRecurridosPorcentaje; | ||||
|       } | ||||
|  | ||||
|       // Lógica Upsert para ResultadosVotos | ||||
|       var votosDeAmbitoExistentes = resultadosVotosExistentes.GetValueOrDefault(ambitoId); | ||||
|       foreach (var votoPositivoDto in resultadosDto.ValoresTotalizadosPositivos) | ||||
|       { | ||||
|         ResultadoVoto? resultadoVoto = null; | ||||
|         if (votosDeAmbitoExistentes != null) | ||||
|         { | ||||
|           votosDeAmbitoExistentes.TryGetValue(votoPositivoDto.IdAgrupacion, out resultadoVoto); | ||||
|         } | ||||
|  | ||||
|         if (resultadoVoto == null) | ||||
|         { | ||||
|           resultadoVoto = new ResultadoVoto | ||||
|           { | ||||
|             AmbitoGeograficoId = ambitoId, | ||||
|             AgrupacionPoliticaId = votoPositivoDto.IdAgrupacion | ||||
|           }; | ||||
|           dbContext.ResultadosVotos.Add(resultadoVoto); | ||||
|         } | ||||
|         resultadoVoto.CantidadVotos = votoPositivoDto.Votos; | ||||
|         resultadoVoto.PorcentajeVotos = votoPositivoDto.VotosPorcentaje; | ||||
|       } | ||||
|       estadoRecuento = new EstadoRecuento { AmbitoGeograficoId = ambitoId, CategoriaId = categoriaId }; | ||||
|       dbContext.EstadosRecuentos.Add(estadoRecuento); | ||||
|     } | ||||
|  | ||||
|     // --- OPTIMIZACIÓN 3: Guardar todos los cambios en UNA SOLA TRANSACCIÓN --- | ||||
|     _logger.LogInformation("Guardando todos los cambios de resultados municipales en la base de datos..."); | ||||
|     // Ahora 'stoppingToken' es reconocido aquí | ||||
|     await dbContext.SaveChangesAsync(stoppingToken); | ||||
|     _logger.LogInformation("Guardado completado."); | ||||
|     estadoRecuento.FechaTotalizacion = DateTime.Parse(resultadosDto.FechaTotalizacion).ToUniversalTime(); | ||||
|     estadoRecuento.MesasEsperadas = resultadosDto.EstadoRecuento.MesasEsperadas; | ||||
|     estadoRecuento.MesasTotalizadas = resultadosDto.EstadoRecuento.MesasTotalizadas; | ||||
|     estadoRecuento.MesasTotalizadasPorcentaje = resultadosDto.EstadoRecuento.MesasTotalizadasPorcentaje; | ||||
|     estadoRecuento.CantidadElectores = resultadosDto.EstadoRecuento.CantidadElectores; | ||||
|     estadoRecuento.CantidadVotantes = resultadosDto.EstadoRecuento.CantidadVotantes; | ||||
|     estadoRecuento.ParticipacionPorcentaje = resultadosDto.EstadoRecuento.ParticipacionPorcentaje; | ||||
|  | ||||
|     if (resultadosDto.ValoresTotalizadosOtros != null) | ||||
|     { | ||||
|       estadoRecuento.VotosEnBlanco = resultadosDto.ValoresTotalizadosOtros.VotosEnBlanco; | ||||
|       estadoRecuento.VotosEnBlancoPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosEnBlancoPorcentaje; | ||||
|       estadoRecuento.VotosNulos = resultadosDto.ValoresTotalizadosOtros.VotosNulos; | ||||
|       estadoRecuento.VotosNulosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosNulosPorcentaje; | ||||
|       estadoRecuento.VotosRecurridos = resultadosDto.ValoresTotalizadosOtros.VotosRecurridos; | ||||
|       estadoRecuento.VotosRecurridosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosRecurridosPorcentaje; | ||||
|     } | ||||
|  | ||||
|     foreach (var votoPositivoDto in resultadosDto.ValoresTotalizadosPositivos) | ||||
|     { | ||||
|       var resultadoVoto = await dbContext.ResultadosVotos.FirstOrDefaultAsync( | ||||
|           rv => rv.AmbitoGeograficoId == ambitoId && | ||||
|                 rv.CategoriaId == categoriaId && | ||||
|                 rv.AgrupacionPoliticaId == votoPositivoDto.IdAgrupacion, | ||||
|           stoppingToken | ||||
|       ); | ||||
|  | ||||
|       if (resultadoVoto == null) | ||||
|       { | ||||
|         resultadoVoto = new ResultadoVoto | ||||
|         { | ||||
|           AmbitoGeograficoId = ambitoId, | ||||
|           CategoriaId = categoriaId, | ||||
|           AgrupacionPoliticaId = votoPositivoDto.IdAgrupacion | ||||
|         }; | ||||
|         dbContext.ResultadosVotos.Add(resultadoVoto); | ||||
|       } | ||||
|       resultadoVoto.CantidadVotos = votoPositivoDto.Votos; | ||||
|       resultadoVoto.PorcentajeVotos = votoPositivoDto.VotosPorcentaje; | ||||
|     } | ||||
|  | ||||
|     try | ||||
|     { | ||||
|       await dbContext.SaveChangesAsync(stoppingToken); | ||||
|     } | ||||
|     catch (DbUpdateException ex) | ||||
|     { | ||||
|       _logger.LogError(ex, "DbUpdateException al guardar resultados para AmbitoId {ambitoId} y CategoriaId {categoriaId}", ambitoId, categoriaId); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /// <summary> | ||||
| @@ -420,11 +339,4 @@ public class CriticalDataWorker : BackgroundService | ||||
|       _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General."); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Pega aquí los métodos: | ||||
|   // - SondearResultadosMunicipalesAsync | ||||
|   // - GuardarResultadosDeMunicipiosAsync | ||||
|   // - SondearResumenProvincialAsync | ||||
|   // - SondearEstadoRecuentoGeneralAsync | ||||
|   // (Estos métodos necesitan IServiceProvider y SharedTokenService, que ya están inyectados) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user