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 3d345a0..4aecd07 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+69ddf2b2d24d4968c618c6fd9f38c1143625cdcd")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+30f1e751b770bf730fc48b1baefb00f560694f35")] [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 099cadb..7752afc 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":["mhE0FuBM0BOF9SNOE0rY9setCw2ye3UUh7cEPjhgDdY=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","jLJZbvuuUsGPkZf3QtwRFQTqJiXdNIIW3av7i2nQ\u002B30=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","BwAWLB3mTJ9blXh5ZTZOjcrdnzPEd9wZsoNfmceZfb8="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["mhE0FuBM0BOF9SNOE0rY9setCw2ye3UUh7cEPjhgDdY=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","7rMeSoKKF2\u002B9j5kLZ30FlE98meJ1tr4dywVzhYb49Qg="],"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 2cef5ef..9ec138e 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":["mhE0FuBM0BOF9SNOE0rY9setCw2ye3UUh7cEPjhgDdY=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","jLJZbvuuUsGPkZf3QtwRFQTqJiXdNIIW3av7i2nQ\u002B30=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","BwAWLB3mTJ9blXh5ZTZOjcrdnzPEd9wZsoNfmceZfb8="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["mhE0FuBM0BOF9SNOE0rY9setCw2ye3UUh7cEPjhgDdY=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","7rMeSoKKF2\u002B9j5kLZ30FlE98meJ1tr4dywVzhYb49Qg="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file 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 4532c1d..dbd4be4 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+69ddf2b2d24d4968c618c6fd9f38c1143625cdcd")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+30f1e751b770bf730fc48b1baefb00f560694f35")] [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/EleccionesDbContext.cs b/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs index 2978a02..3962dfb 100644 --- a/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs +++ b/Elecciones-Web/src/Elecciones.Database/EleccionesDbContext.cs @@ -18,26 +18,37 @@ public class EleccionesDbContext(DbContextOptions options) protected override void OnModelCreating(ModelBuilder modelBuilder) { - base.OnModelCreating(modelBuilder); // Es buena práctica llamar a la base + base.OnModelCreating(modelBuilder); - // Configuraciones adicionales del modelo (índices, etc.) pueden ir aquí. - // Por ejemplo, para optimizar las búsquedas. modelBuilder.Entity() .HasIndex(r => new { r.AmbitoGeograficoId, r.AgrupacionPoliticaId }) .IsUnique(); + // Precisión para los campos de porcentaje en EstadoRecuento modelBuilder.Entity(entity => { entity.Property(e => e.MesasTotalizadasPorcentaje).HasPrecision(5, 2); entity.Property(e => e.ParticipacionPorcentaje).HasPrecision(5, 2); + entity.Property(e => e.VotosNulosPorcentaje).HasPrecision(18, 4); + entity.Property(e => e.VotosEnBlancoPorcentaje).HasPrecision(18, 4); + entity.Property(e => e.VotosRecurridosPorcentaje).HasPrecision(18, 4); }); + + // Precisión para el campo de porcentaje en ResultadoVoto + modelBuilder.Entity() + .Property(e => e.PorcentajeVotos).HasPrecision(18, 4); + modelBuilder.Entity() - .Property(e => e.VotosPorcentaje).HasPrecision(5, 2); + .Property(e => e.VotosPorcentaje).HasPrecision(5, 2); modelBuilder.Entity(entity => - { - entity.Property(e => e.MesasTotalizadasPorcentaje).HasPrecision(5, 2); - entity.Property(e => e.ParticipacionPorcentaje).HasPrecision(5, 2); - }); + { + // Le decimos a EF que la combinación única es (AmbitoGeograficoId, CategoriaId) + entity.HasKey(e => new { e.AmbitoGeograficoId, e.CategoriaId }); + + // Mantener la configuración de precisión + entity.Property(e => e.MesasTotalizadasPorcentaje).HasPrecision(5, 2); + entity.Property(e => e.ParticipacionPorcentaje).HasPrecision(5, 2); + }); } } \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs b/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs index a4fbffb..3ecdf19 100644 --- a/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs +++ b/Elecciones-Web/src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs @@ -1,3 +1,4 @@ +// src/Elecciones.Database/Entities/EstadoRecuentoGeneral.cs using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -5,14 +6,16 @@ namespace Elecciones.Database.Entities; public class EstadoRecuentoGeneral { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.None)] // Le dice a EF que no genere este valor. public int AmbitoGeograficoId { get; set; } - + public int CategoriaId { get; set; } public int MesasEsperadas { get; set; } public int MesasTotalizadas { get; set; } public decimal MesasTotalizadasPorcentaje { get; set; } public int CantidadElectores { get; set; } public int CantidadVotantes { get; set; } public decimal ParticipacionPorcentaje { get; set; } + + // --- Propiedades de navegación (Opcional pero recomendado) --- + [ForeignKey("CategoriaId")] + public CategoriaElectoral CategoriaElectoral { get; set; } = null!; } \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/20250817230412_MakeEstadoGeneralKeyComposite.Designer.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250817230412_MakeEstadoGeneralKeyComposite.Designer.cs new file mode 100644 index 0000000..b9e63d0 --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250817230412_MakeEstadoGeneralKeyComposite.Designer.cs @@ -0,0 +1,364 @@ +// +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("20250817230412_MakeEstadoGeneralKeyComposite")] + partial class MakeEstadoGeneralKeyComposite + { + /// + 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("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"); + + 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("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("NroBancas") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AgrupacionPoliticaId"); + + b.HasIndex("AmbitoGeograficoId"); + + 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("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("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/20250817230412_MakeEstadoGeneralKeyComposite.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/20250817230412_MakeEstadoGeneralKeyComposite.cs new file mode 100644 index 0000000..356d7cf --- /dev/null +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/20250817230412_MakeEstadoGeneralKeyComposite.cs @@ -0,0 +1,148 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elecciones.Database.Migrations +{ + /// + public partial class MakeEstadoGeneralKeyComposite : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_EstadosRecuentosGenerales", + table: "EstadosRecuentosGenerales"); + + migrationBuilder.AlterColumn( + name: "PorcentajeVotos", + table: "ResultadosVotos", + type: "decimal(18,4)", + precision: 18, + scale: 4, + nullable: false, + oldClrType: typeof(decimal), + oldType: "decimal(18,2)"); + + migrationBuilder.AddColumn( + name: "CategoriaId", + table: "EstadosRecuentosGenerales", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AlterColumn( + name: "VotosRecurridosPorcentaje", + table: "EstadosRecuentos", + type: "decimal(18,4)", + precision: 18, + scale: 4, + nullable: false, + oldClrType: typeof(decimal), + oldType: "decimal(18,2)"); + + migrationBuilder.AlterColumn( + name: "VotosNulosPorcentaje", + table: "EstadosRecuentos", + type: "decimal(18,4)", + precision: 18, + scale: 4, + nullable: false, + oldClrType: typeof(decimal), + oldType: "decimal(18,2)"); + + migrationBuilder.AlterColumn( + name: "VotosEnBlancoPorcentaje", + table: "EstadosRecuentos", + type: "decimal(18,4)", + precision: 18, + scale: 4, + nullable: false, + oldClrType: typeof(decimal), + oldType: "decimal(18,2)"); + + migrationBuilder.AddPrimaryKey( + name: "PK_EstadosRecuentosGenerales", + table: "EstadosRecuentosGenerales", + columns: new[] { "AmbitoGeograficoId", "CategoriaId" }); + + migrationBuilder.CreateIndex( + name: "IX_EstadosRecuentosGenerales_CategoriaId", + table: "EstadosRecuentosGenerales", + column: "CategoriaId"); + + migrationBuilder.AddForeignKey( + name: "FK_EstadosRecuentosGenerales_CategoriasElectorales_CategoriaId", + table: "EstadosRecuentosGenerales", + column: "CategoriaId", + principalTable: "CategoriasElectorales", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_EstadosRecuentosGenerales_CategoriasElectorales_CategoriaId", + table: "EstadosRecuentosGenerales"); + + migrationBuilder.DropPrimaryKey( + name: "PK_EstadosRecuentosGenerales", + table: "EstadosRecuentosGenerales"); + + migrationBuilder.DropIndex( + name: "IX_EstadosRecuentosGenerales_CategoriaId", + table: "EstadosRecuentosGenerales"); + + migrationBuilder.DropColumn( + name: "CategoriaId", + table: "EstadosRecuentosGenerales"); + + migrationBuilder.AlterColumn( + name: "PorcentajeVotos", + table: "ResultadosVotos", + type: "decimal(18,2)", + nullable: false, + oldClrType: typeof(decimal), + oldType: "decimal(18,4)", + oldPrecision: 18, + oldScale: 4); + + migrationBuilder.AlterColumn( + name: "VotosRecurridosPorcentaje", + table: "EstadosRecuentos", + type: "decimal(18,2)", + nullable: false, + oldClrType: typeof(decimal), + oldType: "decimal(18,4)", + oldPrecision: 18, + oldScale: 4); + + migrationBuilder.AlterColumn( + name: "VotosNulosPorcentaje", + table: "EstadosRecuentos", + type: "decimal(18,2)", + nullable: false, + oldClrType: typeof(decimal), + oldType: "decimal(18,4)", + oldPrecision: 18, + oldScale: 4); + + migrationBuilder.AlterColumn( + name: "VotosEnBlancoPorcentaje", + table: "EstadosRecuentos", + type: "decimal(18,2)", + nullable: false, + oldClrType: typeof(decimal), + oldType: "decimal(18,4)", + oldPrecision: 18, + oldScale: 4); + + migrationBuilder.AddPrimaryKey( + name: "PK_EstadosRecuentosGenerales", + table: "EstadosRecuentosGenerales", + column: "AmbitoGeograficoId"); + } + } +} diff --git a/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs b/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs index 85d673d..74eb9a6 100644 --- a/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs +++ b/Elecciones-Web/src/Elecciones.Database/Migrations/EleccionesDbContextModelSnapshot.cs @@ -130,19 +130,22 @@ namespace Elecciones.Database.Migrations .HasColumnType("bigint"); b.Property("VotosEnBlancoPorcentaje") - .HasColumnType("decimal(18,2)"); + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); b.Property("VotosNulos") .HasColumnType("bigint"); b.Property("VotosNulosPorcentaje") - .HasColumnType("decimal(18,2)"); + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); b.Property("VotosRecurridos") .HasColumnType("bigint"); b.Property("VotosRecurridosPorcentaje") - .HasColumnType("decimal(18,2)"); + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); b.HasKey("AmbitoGeograficoId"); @@ -154,6 +157,9 @@ namespace Elecciones.Database.Migrations b.Property("AmbitoGeograficoId") .HasColumnType("int"); + b.Property("CategoriaId") + .HasColumnType("int"); + b.Property("CantidadElectores") .HasColumnType("int"); @@ -174,7 +180,9 @@ namespace Elecciones.Database.Migrations .HasPrecision(5, 2) .HasColumnType("decimal(5,2)"); - b.HasKey("AmbitoGeograficoId"); + b.HasKey("AmbitoGeograficoId", "CategoriaId"); + + b.HasIndex("CategoriaId"); b.ToTable("EstadosRecuentosGenerales"); }); @@ -225,7 +233,8 @@ namespace Elecciones.Database.Migrations .HasColumnType("bigint"); b.Property("PorcentajeVotos") - .HasColumnType("decimal(18,2)"); + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); b.HasKey("Id"); @@ -298,6 +307,17 @@ namespace Elecciones.Database.Migrations 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") 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 47ff514..c880685 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+69ddf2b2d24d4968c618c6fd9f38c1143625cdcd")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+30f1e751b770bf730fc48b1baefb00f560694f35")] [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/Services/ElectoralApiService.cs b/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs index eff8d22..6b171f4 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs +++ b/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs @@ -53,20 +53,30 @@ public class ElectoralApiService : IElectoralApiService return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync>() : null; } - public async Task GetResultadosAsync(string authToken, string distritoId, string seccionId, string municipioId) + public async Task GetResultadosAsync(string authToken, string distritoId, string seccionId, string? municipioId, int categoriaId) { var client = _httpClientFactory.CreateClient("ElectoralApiClient"); - var requestUri = $"/api/resultados/getResultados?distritoId={distritoId}&seccionId={seccionId}&municipioId={municipioId}&categoriaId=5"; // OJO: La categoría está fija + + // Construimos la URL base + var requestUri = $"/api/resultados/getResultados?distritoId={distritoId}&seccionId={seccionId}&categoriaId={categoriaId}"; + + // Añadimos el municipioId a la URL SÓLO si no es nulo o vacío + if (!string.IsNullOrEmpty(municipioId)) + { + requestUri += $"&municipioId={municipioId}"; + } + var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); var response = await client.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } - public async Task GetBancasAsync(string authToken, string distritoId, string seccionId) + public async Task GetBancasAsync(string authToken, string distritoId, string seccionId, int categoriaId) { var client = _httpClientFactory.CreateClient("ElectoralApiClient"); - var requestUri = $"/api/resultados/getBancas?distritoId={distritoId}&seccionId={seccionId}&categoriaId=5"; // OJO: La categoría está fija + // Usamos la categoriaId recibida en lugar de una fija + var requestUri = $"/api/resultados/getBancas?distritoId={distritoId}&seccionId={seccionId}&categoriaId={categoriaId}"; var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); var response = await client.SendAsync(request); @@ -103,10 +113,11 @@ public class ElectoralApiService : IElectoralApiService return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync() : null; } - public async Task GetEstadoRecuentoGeneralAsync(string authToken, string distritoId) + public async Task GetEstadoRecuentoGeneralAsync(string authToken, string distritoId, int categoriaId) { var client = _httpClientFactory.CreateClient("ElectoralApiClient"); - var requestUri = $"/api/estados/estadoRecuento?distritoId={distritoId}"; + // La URL ahora usa el parámetro 'categoriaId' que se recibe + var requestUri = $"/api/estados/estadoRecuento?distritoId={distritoId}&categoriaId={categoriaId}"; var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); var response = await client.SendAsync(request); diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs b/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs index 658bb39..eddc405 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs +++ b/Elecciones-Web/src/Elecciones.Infrastructure/Services/IElectoralApiService.cs @@ -12,11 +12,11 @@ public interface IElectoralApiService // Métodos para catálogos Task GetCatalogoAmbitosAsync(string authToken, int categoriaId); Task?> GetAgrupacionesAsync(string authToken, string distritoId, int categoriaId); - Task GetResultadosAsync(string authToken, string distritoId, string seccionId, string municipioId); - Task GetBancasAsync(string authToken, string distritoId, string seccionId); + Task GetResultadosAsync(string authToken, string distritoId, string seccionId, string? municipioId, int categoriaId); + Task GetBancasAsync(string authToken, string distritoId, string seccionId, int categoriaId); Task?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId); Task GetTelegramaFileAsync(string authToken, string mesaId); Task GetResumenAsync(string authToken, string distritoId); - Task GetEstadoRecuentoGeneralAsync(string authToken, string distritoId); + Task GetEstadoRecuentoGeneralAsync(string authToken, string distritoId, int categoriaId); Task?> GetCategoriasAsync(string authToken); } \ No newline at end of file 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 a18c32c..08fb351 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+69ddf2b2d24d4968c618c6fd9f38c1143625cdcd")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+30f1e751b770bf730fc48b1baefb00f560694f35")] [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/Worker.cs b/Elecciones-Web/src/Elecciones.Worker/Worker.cs index b33bf6a..caa6297 100644 --- a/Elecciones-Web/src/Elecciones.Worker/Worker.cs +++ b/Elecciones-Web/src/Elecciones.Worker/Worker.cs @@ -10,12 +10,15 @@ using System.Threading.Tasks; namespace Elecciones.Worker; +/// +/// Servicio de fondo (BackgroundService) responsable de sincronizar y sondear +/// periódicamente los datos de la API electoral. +/// public class Worker : BackgroundService { private readonly ILogger _logger; private readonly IElectoralApiService _apiService; private readonly IServiceProvider _serviceProvider; - private string? _authToken; // Almacenamos el token para reutilizarlo en el ciclo de vida public Worker(ILogger logger, IElectoralApiService apiService, IServiceProvider serviceProvider) { @@ -24,154 +27,75 @@ public class Worker : BackgroundService _serviceProvider = serviceProvider; } + /// + /// Método principal del worker que se ejecuta en segundo plano. + /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Elecciones Worker iniciado a las: {time}", DateTimeOffset.Now); - await SincronizarCatalogosAsync(stoppingToken); + // 1. SINCRONIZACIÓN INICIAL: Se ejecuta una vez al iniciar el worker para + // asegurar que la base de datos local tenga todos los catálogos maestros. + await SincronizarCatalogosMaestrosAsync(stoppingToken); _logger.LogInformation("-------------------------------------------------"); _logger.LogInformation("Iniciando sondeo periódico de resultados..."); _logger.LogInformation("-------------------------------------------------"); + // 2. BUCLE DE SONDEO: Se ejecuta continuamente hasta que se detenga la aplicación. while (!stoppingToken.IsCancellationRequested) { - // Ejecutamos todos los sondeos en paralelo para mayor eficiencia + var authToken = await _apiService.GetAuthTokenAsync(); + if (string.IsNullOrEmpty(authToken)) + { + _logger.LogError("CRÍTICO: No se pudo obtener el token de autenticación. Reintentando en 1 minuto..."); + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + continue; + } + await Task.WhenAll( - SondearResultadosAsync(stoppingToken), - SondearBancasAsync(stoppingToken), - SondearTelegramasAsync(stoppingToken), - SondearResumenProvincialAsync(stoppingToken), - SondearEstadoRecuentoGeneralAsync(stoppingToken) + SondearResultadosMunicipalesAsync(authToken, stoppingToken), + SondearProyeccionBancasAsync(authToken, stoppingToken), + SondearNuevosTelegramasAsync(authToken, stoppingToken), + SondearResumenProvincialAsync(authToken, stoppingToken), + SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken) ); try { - // Esperamos 1 minuto antes del siguiente ciclo completo de sondeos - _logger.LogInformation("Ciclo de sondeo completado. Esperando 5 minuto para el siguiente..."); + _logger.LogInformation("Ciclo de sondeo completado. Esperando 5 minutos para el siguiente..."); await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); } - catch (TaskCanceledException) { break; } + catch (TaskCanceledException) + { + break; + } } _logger.LogInformation("Elecciones Worker se está deteniendo."); } - - private async Task ObtenerTokenSiEsNecesario(CancellationToken stoppingToken) - { - // En un futuro, se podría añadir lógica para renovar el token solo cuando expire. - // Por ahora, para asegurar que siempre sea válido, lo obtenemos cada vez. - _authToken = await _apiService.GetAuthTokenAsync(); - if (string.IsNullOrEmpty(_authToken)) - { - _logger.LogError("No se pudo obtener el token, se cancelan las operaciones."); - } - } - - private async Task SondearResultadosAsync(CancellationToken stoppingToken) - { - try - { - await ObtenerTokenSiEsNecesario(stoppingToken); - if (string.IsNullOrEmpty(_authToken) || stoppingToken.IsCancellationRequested) return; - - // Usamos un 'scope' propio para esta operación - using var scope = _serviceProvider.CreateScope(); - var dbContext = scope.ServiceProvider.GetRequiredService(); - - // 1. OBTENEMOS LA LISTA DE TODOS LOS MUNICIPIOS DE NUESTRA BD - var municipiosASondear = await dbContext.AmbitosGeograficos - .AsNoTracking() - .Where(a => a.NivelId == 5 && a.MunicipioId != null && a.DistritoId != null && a.SeccionId != null) - .Select(a => new { a.Id, a.MunicipioId, a.SeccionId, a.DistritoId }) - .ToListAsync(stoppingToken); - - _logger.LogInformation("Iniciando sondeo para {count} municipios.", municipiosASondear.Count); - - // 2. RECORREMOS LA LISTA Y PROCESAMOS CADA MUNICIPIO - foreach (var municipio in municipiosASondear) - { - if (stoppingToken.IsCancellationRequested) break; - - _logger.LogInformation("Sondeando resultados para el municipio: {municipioId}", municipio.MunicipioId); - - var resultados = await _apiService.GetResultadosAsync(_authToken, municipio.DistritoId!, municipio.SeccionId!, municipio.MunicipioId!); - if (resultados is null) - { - _logger.LogWarning("No se recibieron resultados para el municipio {municipioId}", municipio.MunicipioId); - continue; // Saltamos al siguiente municipio - } - - // 3. GUARDAMOS LOS DATOS (Lógica de Upsert) - // La lógica de guardado que ya teníamos funciona perfectamente para cada municipio. - // La pasamos a un método separado para mayor claridad. - await GuardarResultadosDeAmbitoAsync(dbContext, municipio.Id, resultados, stoppingToken); - - _logger.LogInformation("Resultados para el municipio {municipioId} guardados/actualizados.", municipio.MunicipioId); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Ocurrió un error inesperado durante el sondeo de resultados."); - } - } - - private async Task GuardarResultadosDeAmbitoAsync(EleccionesDbContext dbContext, int ambitoId, Elecciones.Core.DTOs.ResultadosDto resultados, CancellationToken stoppingToken) - { - // --- ACTUALIZAR O INSERTAR ESTADO RECUENTO --- - var estadoRecuento = await dbContext.EstadosRecuentos.FindAsync(new object[] { ambitoId }, cancellationToken: stoppingToken); - if (estadoRecuento == null) - { - estadoRecuento = new EstadoRecuento { AmbitoGeograficoId = ambitoId }; - dbContext.EstadosRecuentos.Add(estadoRecuento); - } - estadoRecuento.FechaTotalizacion = DateTime.Parse(resultados.FechaTotalizacion).ToUniversalTime(); - estadoRecuento.MesasEsperadas = resultados.EstadoRecuento.MesasEsperadas; - estadoRecuento.MesasTotalizadas = resultados.EstadoRecuento.MesasTotalizadas; - estadoRecuento.CantidadElectores = resultados.EstadoRecuento.CantidadElectores; - estadoRecuento.ParticipacionPorcentaje = resultados.EstadoRecuento.ParticipacionPorcentaje; - if (resultados.ValoresTotalizadosOtros != null) - { - estadoRecuento.VotosEnBlanco = resultados.ValoresTotalizadosOtros.VotosEnBlanco; - estadoRecuento.VotosNulos = resultados.ValoresTotalizadosOtros.VotosNulos; - estadoRecuento.VotosRecurridos = resultados.ValoresTotalizadosOtros.VotosRecurridos; - } - - // --- ACTUALIZAR O INSERTAR VOTOS POSITIVOS --- - foreach (var votoPositivo in resultados.ValoresTotalizadosPositivos) - { - var resultadoVoto = await dbContext.ResultadosVotos.FirstOrDefaultAsync( - rv => rv.AmbitoGeograficoId == ambitoId && rv.AgrupacionPoliticaId == votoPositivo.IdAgrupacion, stoppingToken); - - if (resultadoVoto == null) - { - resultadoVoto = new ResultadoVoto - { - AmbitoGeograficoId = ambitoId, - AgrupacionPoliticaId = votoPositivo.IdAgrupacion - }; - dbContext.ResultadosVotos.Add(resultadoVoto); - } - resultadoVoto.CantidadVotos = votoPositivo.Votos; - } - - await dbContext.SaveChangesAsync(stoppingToken); - } - - private async Task SincronizarCatalogosAsync(CancellationToken stoppingToken) + /// + /// Descarga y sincroniza los catálogos base (Categorías, Ámbitos, Agrupaciones) + /// desde la API a la base de datos local. + /// + private async Task SincronizarCatalogosMaestrosAsync(CancellationToken stoppingToken) { try { _logger.LogInformation("Iniciando sincronización de catálogos maestros..."); - await ObtenerTokenSiEsNecesario(stoppingToken); - if (string.IsNullOrEmpty(_authToken) || stoppingToken.IsCancellationRequested) return; + var authToken = await _apiService.GetAuthTokenAsync(); + if (string.IsNullOrEmpty(authToken) || stoppingToken.IsCancellationRequested) + { + _logger.LogError("No se pudo obtener token para la sincronización de catálogos."); + return; + } using var scope = _serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); - // --- 1. SINCRONIZAR CATEGORÍAS --- - var categoriasApi = await _apiService.GetCategoriasAsync(_authToken); + // --- 1. SINCRONIZAR CATEGORÍAS ELECTORALES --- + var categoriasApi = await _apiService.GetCategoriasAsync(authToken); if (categoriasApi is null || !categoriasApi.Any()) { _logger.LogWarning("No se recibieron datos del catálogo de Categorías."); @@ -189,15 +113,17 @@ public class Worker : BackgroundService dbContext.CategoriasElectorales.Add(new CategoriaElectoral { Id = categoriaDto.CategoriaId, Nombre = categoriaDto.Nombre, Orden = categoriaDto.Orden }); } } - // Guardamos las categorías primero para asegurar que existan para los siguientes pasos await dbContext.SaveChangesAsync(stoppingToken); + // --- 2. SINCRONIZAR ÁMBITOS Y AGRUPACIONES POR CADA CATEGORÍA --- - // --- 2. SINCRONIZAR ÁMBITOS Y AGRUPACIONES --- - // Cargamos los catálogos existentes en memoria UNA SOLA VEZ para eficiencia. + // CORRECCIÓN: La siguiente línea faltaba por completo, causando el error CS0103. + // Carga todos los ámbitos existentes en un diccionario para una verificación rápida. var ambitosEnDb = await dbContext.AmbitosGeograficos.ToDictionaryAsync( a => (a.DistritoId, a.SeccionId, a.MunicipioId, a.CircuitoId, a.EstablecimientoId), - a => a, stoppingToken); + a => a, + stoppingToken + ); var agrupacionesEnDb = await dbContext.AgrupacionesPoliticas.ToDictionaryAsync(a => a.Id, a => a, stoppingToken); @@ -206,13 +132,12 @@ public class Worker : BackgroundService if (stoppingToken.IsCancellationRequested) break; _logger.LogInformation("--- Sincronizando datos para la categoría: {Nombre} (ID: {Id}) ---", categoria.Nombre, categoria.CategoriaId); - var catalogoDto = await _apiService.GetCatalogoAmbitosAsync(_authToken, categoria.CategoriaId); + var catalogoDto = await _apiService.GetCatalogoAmbitosAsync(authToken, categoria.CategoriaId); if (catalogoDto != null) { // SINCRONIZAR ÁMBITOS foreach (var ambitoDto in catalogoDto.Ambitos) { - // CLAVE ÚNICA CORREGIDA Y MÁS COMPLETA var claveUnica = ( ambitoDto.CodigoAmbitos.DistritoId, ambitoDto.CodigoAmbitos.SeccionId, @@ -221,6 +146,7 @@ public class Worker : BackgroundService ambitoDto.CodigoAmbitos.EstablecimientoId ); + // Aquí se usaba 'ambitosEnDb' sin haberlo declarado. Ahora funciona. if (!ambitosEnDb.ContainsKey(claveUnica)) { var nuevoAmbito = new AmbitoGeografico @@ -235,32 +161,40 @@ public class Worker : BackgroundService SeccionProvincialId = ambitoDto.CodigoAmbitos.SeccionProvincialId }; dbContext.AmbitosGeograficos.Add(nuevoAmbito); - ambitosEnDb.Add(claveUnica, nuevoAmbito); // Añadimos al diccionario en memoria para evitar duplicados en el mismo ciclo + // Se añade al diccionario en memoria para evitar duplicados en el mismo ciclo. + ambitosEnDb.Add(claveUnica, nuevoAmbito); } } - // SINCRONIZAR AGRUPACIONES + // Lógica para sincronizar AGRUPACIONES POLÍTICAS var provincia = catalogoDto.Ambitos.FirstOrDefault(a => a.NivelId == 10); if (provincia != null && !string.IsNullOrEmpty(provincia.CodigoAmbitos.DistritoId)) { - var agrupacionesApi = await _apiService.GetAgrupacionesAsync(_authToken, provincia.CodigoAmbitos.DistritoId, categoria.CategoriaId); - if (agrupacionesApi != null && agrupacionesApi.Any()) + try { - foreach (var agrupacionDto in agrupacionesApi) + var agrupacionesApi = await _apiService.GetAgrupacionesAsync(authToken, provincia.CodigoAmbitos.DistritoId, categoria.CategoriaId); + if (agrupacionesApi != null && agrupacionesApi.Any()) { - if (!agrupacionesEnDb.ContainsKey(agrupacionDto.IdAgrupacion)) + foreach (var agrupacionDto in agrupacionesApi) { - var nuevaAgrupacion = new AgrupacionPolitica + if (!agrupacionesEnDb.ContainsKey(agrupacionDto.IdAgrupacion)) { - Id = agrupacionDto.IdAgrupacion, - IdTelegrama = agrupacionDto.IdAgrupacionTelegrama, - Nombre = agrupacionDto.NombreAgrupacion - }; - dbContext.AgrupacionesPoliticas.Add(nuevaAgrupacion); - agrupacionesEnDb.Add(nuevaAgrupacion.Id, nuevaAgrupacion); + var nuevaAgrupacion = new AgrupacionPolitica + { + Id = agrupacionDto.IdAgrupacion, + IdTelegrama = agrupacionDto.IdAgrupacionTelegrama, + Nombre = agrupacionDto.NombreAgrupacion + }; + dbContext.AgrupacionesPoliticas.Add(nuevaAgrupacion); + agrupacionesEnDb.Add(nuevaAgrupacion.Id, nuevaAgrupacion); + } } } } + catch (Exception ex) + { + _logger.LogWarning(ex, "No se pudieron obtener agrupaciones para la categoría '{catNombre}' ({catId}). Esto puede ser normal si la categoría no aplica a nivel provincial.", categoria.Nombre, categoria.CategoriaId); + } } } } @@ -276,20 +210,130 @@ public class Worker : BackgroundService } } - private async Task SondearBancasAsync(CancellationToken stoppingToken) + // El resto de los métodos (SondearResultadosMunicipalesAsync, GuardarResultadosDeAmbitoAsync, etc.) + // se mantienen como en la versión anterior que te proporcioné. Los incluyo aquí para + // que tengas el archivo completo y sin errores. + private async Task SondearResultadosMunicipalesAsync(string authToken, CancellationToken stoppingToken) { try { - await ObtenerTokenSiEsNecesario(stoppingToken); - if (string.IsNullOrEmpty(_authToken) || stoppingToken.IsCancellationRequested) return; - using var scope = _serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); - // Obtenemos las secciones electorales (donde se reparten bancas de diputados) + // Buscamos los ámbitos de nivel Municipio/Partido + var municipiosASondear = await dbContext.AmbitosGeograficos + .AsNoTracking() + .Where(a => a.NivelId == 5 && a.MunicipioId != null && a.DistritoId != null && a.SeccionId != null) + .Select(a => new { a.Id, a.MunicipioId, a.SeccionId, a.DistritoId }) + .ToListAsync(stoppingToken); + + if (!municipiosASondear.Any()) return; + + _logger.LogInformation("Iniciando sondeo de resultados para {count} municipios (Partidos)...", municipiosASondear.Count); + + foreach (var municipio in municipiosASondear) + { + if (stoppingToken.IsCancellationRequested) break; + + var categoriaConcejales = await dbContext.CategoriasElectorales + .AsNoTracking() + .FirstOrDefaultAsync(c => c.Nombre.Contains("CONCEJALES"), stoppingToken); + + if (categoriaConcejales != null) + { + // Para obtener resultados del PARTIDO completo, pasamos 'municipioId' como null. + // Usamos el 'seccionId' del registro, que según la aclaración, es el ID del Partido. + var resultados = await _apiService.GetResultadosAsync( + authToken, + municipio.DistritoId!, + municipio.SeccionId!, + null, // <- AHORA ES NULL + categoriaConcejales.Id + ); + + if (resultados != null) + { + await GuardarResultadosDeAmbitoAsync(dbContext, municipio.Id, resultados, stoppingToken); + } + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Ocurrió un error inesperado durante el sondeo de resultados municipales."); + } + } + + private async Task GuardarResultadosDeAmbitoAsync(EleccionesDbContext dbContext, int ambitoId, Elecciones.Core.DTOs.ResultadosDto resultadosDto, CancellationToken stoppingToken) + { + var estadoRecuento = await dbContext.EstadosRecuentos.FindAsync(new object[] { ambitoId }, cancellationToken: stoppingToken); + if (estadoRecuento == null) + { + estadoRecuento = new EstadoRecuento { AmbitoGeograficoId = ambitoId }; + dbContext.EstadosRecuentos.Add(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; + } + + foreach (var votoPositivoDto in resultadosDto.ValoresTotalizadosPositivos) + { + var resultadoVoto = await dbContext.ResultadosVotos.FirstOrDefaultAsync( + rv => rv.AmbitoGeograficoId == ambitoId && rv.AgrupacionPoliticaId == votoPositivoDto.IdAgrupacion, + stoppingToken + ); + + if (resultadoVoto == null) + { + resultadoVoto = new ResultadoVoto + { + AmbitoGeograficoId = ambitoId, + AgrupacionPoliticaId = votoPositivoDto.IdAgrupacion + }; + dbContext.ResultadosVotos.Add(resultadoVoto); + } + resultadoVoto.CantidadVotos = votoPositivoDto.Votos; + resultadoVoto.PorcentajeVotos = votoPositivoDto.VotosPorcentaje; + } + await dbContext.SaveChangesAsync(stoppingToken); + } + + private async Task SondearProyeccionBancasAsync(string authToken, CancellationToken stoppingToken) + { + try + { + using var scope = _serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var categoriasDeBancas = await dbContext.CategoriasElectorales + .AsNoTracking() + .Where(c => c.Nombre.Contains("SENADORES") || c.Nombre.Contains("DIPUTADOS")) + .ToListAsync(stoppingToken); + + if (!categoriasDeBancas.Any()) + { + _logger.LogWarning("No se encontraron categorías para 'Senadores' o 'Diputados' en la BD. Omitiendo sondeo de bancas."); + return; + } + var secciones = await dbContext.AmbitosGeograficos .AsNoTracking() - .Where(a => a.NivelId == 4 && a.DistritoId != null && a.SeccionId != null) // Nivel 4 = Sección Electoral + .Where(a => a.NivelId == 4 && a.DistritoId != null && a.SeccionId != null) .ToListAsync(stoppingToken); if (!secciones.Any()) @@ -298,53 +342,43 @@ public class Worker : BackgroundService return; } - _logger.LogInformation("Iniciando sondeo de Bancas para {count} secciones...", secciones.Count); - - // Esta bandera nos asegura que solo borramos la tabla una vez y solo si hay datos nuevos. - bool hasReceivedNewData = false; + _logger.LogInformation("Iniciando sondeo de Bancas para {count} secciones y {catCount} categorías...", secciones.Count, categoriasDeBancas.Count); + bool hasReceivedAnyNewData = false; foreach (var seccion in secciones) { if (stoppingToken.IsCancellationRequested) break; - - var repartoBancas = await _apiService.GetBancasAsync(_authToken, seccion.DistritoId!, seccion.SeccionId!); - - // Verificamos que la respuesta no sea nula y que la lista de bancas contenga al menos un elemento. - if (repartoBancas?.RepartoBancas is { Count: > 0 }) + foreach (var categoria in categoriasDeBancas) { - // Si esta es la primera vez en este ciclo de sondeo que recibimos datos válidos, - // borramos todos los datos viejos de la tabla. - if (!hasReceivedNewData) + if (stoppingToken.IsCancellationRequested) break; + var repartoBancas = await _apiService.GetBancasAsync(authToken, seccion.DistritoId!, seccion.SeccionId!, categoria.Id); + if (repartoBancas?.RepartoBancas is { Count: > 0 }) { - _logger.LogInformation("Se recibieron nuevos datos de bancas. Limpiando tabla de proyecciones..."); - await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ProyeccionesBancas", stoppingToken); - hasReceivedNewData = true; // Marcamos que ya hemos limpiado la tabla. - } - - // Procedemos a añadir las nuevas proyecciones a la sesión de EF Core. - foreach (var banca in repartoBancas.RepartoBancas) - { - var nuevaProyeccion = new ProyeccionBanca + if (!hasReceivedAnyNewData) { - AmbitoGeograficoId = seccion.Id, - AgrupacionPoliticaId = banca.IdAgrupacion, - NroBancas = banca.NroBancas - }; - await dbContext.ProyeccionesBancas.AddAsync(nuevaProyeccion, stoppingToken); + _logger.LogInformation("Se recibieron nuevos datos de bancas. Limpiando la tabla de proyecciones para evitar duplicados..."); + await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ProyeccionesBancas", stoppingToken); + hasReceivedAnyNewData = true; + } + + foreach (var banca in repartoBancas.RepartoBancas) + { + var nuevaProyeccion = new ProyeccionBanca + { + AmbitoGeograficoId = seccion.Id, + AgrupacionPoliticaId = banca.IdAgrupacion, + NroBancas = banca.NroBancas + }; + await dbContext.ProyeccionesBancas.AddAsync(nuevaProyeccion, stoppingToken); + } } } - else - { - _logger.LogWarning("No se recibieron datos de bancas para la sección {seccionId}.", seccion.SeccionId); - } } - // Si hemos añadido alguna entidad nueva (es decir, hasReceivedNewData es true), - // guardamos todos los cambios en la base de datos. - if (hasReceivedNewData) + if (hasReceivedAnyNewData) { await dbContext.SaveChangesAsync(stoppingToken); - _logger.LogInformation("Sondeo de Bancas completado. La tabla de proyecciones ha sido actualizada."); + _logger.LogInformation("Sondeo de Bancas completado. La tabla de proyecciones ha sido actualizada con nuevos datos."); } else { @@ -353,18 +387,14 @@ public class Worker : BackgroundService } catch (Exception ex) { - _logger.LogError(ex, "Ocurrió un error en el sondeo de Bancas."); + _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Bancas."); } } - private async Task SondearTelegramasAsync(CancellationToken stoppingToken) + private async Task SondearNuevosTelegramasAsync(string authToken, CancellationToken stoppingToken) { try { - await ObtenerTokenSiEsNecesario(stoppingToken); - if (string.IsNullOrEmpty(_authToken) || stoppingToken.IsCancellationRequested) return; - - // --- CADA SONDEO USA SU PROPIO DBCONTEXT FRESCO --- using var scope = _serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); @@ -373,7 +403,11 @@ public class Worker : BackgroundService .Where(a => a.NivelId == 4 && a.DistritoId != null && a.SeccionId != null) .ToListAsync(stoppingToken); - if (!secciones.Any()) return; + if (!secciones.Any()) + { + _logger.LogWarning("No hay Secciones Electorales en la BD para sondear telegramas."); + return; + } _logger.LogInformation("Iniciando sondeo de Telegramas nuevos..."); @@ -381,78 +415,63 @@ public class Worker : BackgroundService { if (stoppingToken.IsCancellationRequested) break; - var listaTelegramasApi = await _apiService.GetTelegramasTotalizadosAsync(_authToken, seccion.DistritoId!, seccion.SeccionId!); + var listaTelegramasApi = await _apiService.GetTelegramasTotalizadosAsync(authToken, seccion.DistritoId!, seccion.SeccionId!); if (listaTelegramasApi is { Count: > 0 }) { var idsDeApi = listaTelegramasApi.Select(t => t[0]).Distinct().ToList(); - - // --- LÓGICA DE DUPLICADOS --- - // Consultamos a la base de datos por los IDs que la API nos acaba de dar var idsYaEnDb = await dbContext.Telegramas .Where(t => idsDeApi.Contains(t.Id)) .Select(t => t.Id) .ToListAsync(stoppingToken); - // Comparamos las dos listas para encontrar los que realmente son nuevos var nuevosTelegramasIds = idsDeApi.Except(idsYaEnDb).ToList(); - if (!nuevosTelegramasIds.Any()) { - _logger.LogInformation("No hay telegramas nuevos para la sección {seccionId}.", seccion.SeccionId); continue; } - _logger.LogInformation("Se encontraron {count} telegramas nuevos en la sección {seccionId}. Descargando...", nuevosTelegramasIds.Count, seccion.SeccionId); foreach (var mesaId in nuevosTelegramasIds) { if (stoppingToken.IsCancellationRequested) break; - - var telegramaFile = await _apiService.GetTelegramaFileAsync(_authToken, mesaId); + var telegramaFile = await _apiService.GetTelegramaFileAsync(authToken, mesaId); if (telegramaFile != null) { var nuevoTelegrama = new Telegrama { Id = telegramaFile.NombreArchivo, - AmbitoGeograficoId = seccion.Id, // Lo asociamos a la sección por simplicidad + AmbitoGeograficoId = seccion.Id, ContenidoBase64 = telegramaFile.Imagen, FechaEscaneo = DateTime.Parse(telegramaFile.FechaEscaneo).ToUniversalTime(), FechaTotalizacion = DateTime.Parse(telegramaFile.FechaTotalizacion).ToUniversalTime() }; - // Como estamos en un DbContext fresco, AddAsync no dará conflicto await dbContext.Telegramas.AddAsync(nuevoTelegrama, stoppingToken); } } - // Guardamos los cambios al final de cada sección procesada await dbContext.SaveChangesAsync(stoppingToken); } } - _logger.LogInformation("Sondeo de Telegramas completado."); } catch (Exception ex) { - _logger.LogError(ex, "Ocurrió un error en el sondeo de Telegramas."); + _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Telegramas."); } } - private async Task SondearResumenProvincialAsync(CancellationToken stoppingToken) + private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken) { try { - await ObtenerTokenSiEsNecesario(stoppingToken); - if (string.IsNullOrEmpty(_authToken)) return; - 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; - var resumen = await _apiService.GetResumenAsync(_authToken, provincia.DistritoId!); + var resumen = await _apiService.GetResumenAsync(authToken, provincia.DistritoId!); if (resumen?.ValoresTotalizadosPositivos is { Count: > 0 }) { - // Estrategia: Reemplazo completo await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ResumenesVotos", stoppingToken); foreach (var voto in resumen.ValoresTotalizadosPositivos) { @@ -474,44 +493,68 @@ public class Worker : BackgroundService } } - private async Task SondearEstadoRecuentoGeneralAsync(CancellationToken stoppingToken) + private async Task SondearEstadoRecuentoGeneralAsync(string authToken, CancellationToken stoppingToken) { try { - await ObtenerTokenSiEsNecesario(stoppingToken); - if (string.IsNullOrEmpty(_authToken)) return; - 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; - - var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(_authToken, provincia.DistritoId!); - if (estadoDto != null) + var provincia = await dbContext.AmbitosGeograficos + .AsNoTracking() + .FirstOrDefaultAsync(a => a.NivelId == 10, stoppingToken); + if (provincia == null) { - // Estrategia: Upsert - var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(provincia.Id); - if (registroDb == null) - { - registroDb = new EstadoRecuentoGeneral { AmbitoGeograficoId = provincia.Id }; - dbContext.EstadosRecuentosGenerales.Add(registroDb); - } - - registroDb.MesasEsperadas = estadoDto.MesasEsperadas; - registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas; - registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje; - registroDb.CantidadElectores = estadoDto.CantidadElectores; - registroDb.CantidadVotantes = estadoDto.CantidadVotantes; - registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje; - - await dbContext.SaveChangesAsync(stoppingToken); - _logger.LogInformation("Sondeo de Estado Recuento General completado."); + _logger.LogWarning("No se encontró el ámbito 'Provincia' (NivelId 10) en la BD. Omitiendo sondeo de estado general."); + return; } + + var categoriasParaSondear = await dbContext.CategoriasElectorales + .AsNoTracking() + .ToListAsync(stoppingToken); + if (!categoriasParaSondear.Any()) + { + _logger.LogWarning("No hay categorías en la BD para sondear el estado general del recuento."); + return; + } + _logger.LogInformation("Iniciando sondeo de Estado Recuento General para {count} categorías...", categoriasParaSondear.Count); + + foreach (var categoria in categoriasParaSondear) + { + if (stoppingToken.IsCancellationRequested) break; + + var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id); + if (estadoDto != null) + { + var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync( + new object[] { provincia.Id, categoria.Id }, + cancellationToken: stoppingToken + ); + + if (registroDb == null) + { + registroDb = new EstadoRecuentoGeneral + { + AmbitoGeograficoId = provincia.Id, + CategoriaId = categoria.Id + }; + dbContext.EstadosRecuentosGenerales.Add(registroDb); + } + + registroDb.MesasEsperadas = estadoDto.MesasEsperadas; + registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas; + registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje; + registroDb.CantidadElectores = estadoDto.CantidadElectores; + registroDb.CantidadVotantes = estadoDto.CantidadVotantes; + registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje; + } + } + await dbContext.SaveChangesAsync(stoppingToken); + _logger.LogInformation("Sondeo de Estado Recuento General completado para todas las categorías."); } catch (Exception ex) { - _logger.LogError(ex, "Ocurrió un error en el sondeo de Estado Recuento General."); + _logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General."); } } } \ No newline at end of file diff --git a/Elecciones-Web/src/Elecciones.Worker/obj/Debug/net9.0/Elecciones.Worker.AssemblyInfo.cs b/Elecciones-Web/src/Elecciones.Worker/obj/Debug/net9.0/Elecciones.Worker.AssemblyInfo.cs index 90b45be..d100b33 100644 --- a/Elecciones-Web/src/Elecciones.Worker/obj/Debug/net9.0/Elecciones.Worker.AssemblyInfo.cs +++ b/Elecciones-Web/src/Elecciones.Worker/obj/Debug/net9.0/Elecciones.Worker.AssemblyInfo.cs @@ -14,7 +14,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Worker")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+69ddf2b2d24d4968c618c6fd9f38c1143625cdcd")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+30f1e751b770bf730fc48b1baefb00f560694f35")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Worker")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Worker")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]