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