Este commit refactoriza por completo el sistema de recolección de datos para asegurar la compatibilidad con la nueva API nacional, pasando de un modelo de distrito único a uno multi-distrito. Cambios principales: - **Refactorización de `SondearResumenProvincialAsync`:** - Se elimina la dependencia del endpoint obsoleto `/getResumen`. - El método ahora itera sobre todas las provincias (`NivelId=10`) y categorías, utilizando `GetResultadosAsync` para obtener los datos agregados. - **Expansión de `SondearResultadosMunicipalesAsync`:** - Se renombra a `SondearResultadosPorAmbitosAsync` para reflejar su nueva responsabilidad. - La lógica ahora sondea múltiples niveles jerárquicos (`NivelId` 10, 20, 30), capturando resultados detallados para Provincias, Secciones Electorales y Municipios. - **Modificación del Modelo de Datos:** - Se añade la columna `CategoriaId` a la entidad y tabla `ResumenVoto`. - Se crea la migración de base de datos `AddCategoriaIdToResumenVoto` para aplicar el cambio. - **Ajustes de Nulabilidad en API Service:** - Se actualizan las firmas de `GetResultadosAsync` en `IElectoralApiService` y `ElectoralApiService` para permitir que `seccionId` y `municipioId` sean nulables (`string?`), resolviendo errores de compilación CS8625. - **Deshabilitación de Seeders de Ejemplo:** - Se introduce una bandera `generarDatosDeEjemplo` en `Program.cs` de la API, establecida en `false`, para prevenir la ejecución de código de simulación en entornos de producción o pruebas.
300 lines
13 KiB
C#
300 lines
13 KiB
C#
using Elecciones.Core.DTOs;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Collections.Generic;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using System.Threading.RateLimiting;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Elecciones.Infrastructure.Services;
|
|
|
|
public class ElectoralApiService : IElectoralApiService
|
|
{
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
|
private readonly IConfiguration _configuration;
|
|
private readonly ILogger<ElectoralApiService> _logger;
|
|
private readonly RateLimiter _rateLimiter;
|
|
|
|
public ElectoralApiService(
|
|
IHttpClientFactory httpClientFactory,
|
|
IConfiguration configuration,
|
|
ILogger<ElectoralApiService> logger,
|
|
RateLimiter rateLimiter)
|
|
{
|
|
_httpClientFactory = httpClientFactory;
|
|
_configuration = configuration;
|
|
_logger = logger;
|
|
_rateLimiter = rateLimiter;
|
|
}
|
|
|
|
public async Task<TokenResponse?> GetAuthTokenAsync()
|
|
{
|
|
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
|
|
if (lease.IsAcquired)
|
|
{
|
|
var client = _httpClientFactory.CreateClient("ElectoralApiClient");
|
|
var username = _configuration["ElectoralApi:Username"];
|
|
var password = _configuration["ElectoralApi:Password"];
|
|
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) return null;
|
|
|
|
var request = new HttpRequestMessage(HttpMethod.Get, "/api/createtoken");
|
|
request.Headers.Add("username", username);
|
|
request.Headers.Add("password", password);
|
|
|
|
var response = await client.SendAsync(request);
|
|
if (!response.IsSuccessStatusCode) return null;
|
|
|
|
return await response.Content.ReadFromJsonAsync<TokenResponse>();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public async Task<CatalogoDto?> GetCatalogoAmbitosAsync(string authToken, int categoriaId)
|
|
{
|
|
// "Pedir una ficha". Este método ahora devuelve un "lease" (permiso).
|
|
// Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo.
|
|
/*
|
|
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
|
|
|
|
// Si se nos concede el permiso para proceder...
|
|
if (lease.IsAcquired)
|
|
{*/
|
|
var client = _httpClientFactory.CreateClient("ElectoralApiClient");
|
|
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/catalogo/getCatalogo?categoriaId={categoriaId}");
|
|
request.Headers.Add("Authorization", $"Bearer {authToken}");
|
|
var response = await client.SendAsync(request);
|
|
return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync<CatalogoDto>() : null;
|
|
/* }
|
|
// Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null.
|
|
return null;*/
|
|
}
|
|
|
|
public async Task<List<AgrupacionDto>?> GetAgrupacionesAsync(string authToken, string distritoId, int categoriaId)
|
|
{
|
|
// "Pedir una ficha". Este método ahora devuelve un "lease" (permiso).
|
|
// Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo.
|
|
/*
|
|
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
|
|
|
|
// Si se nos concede el permiso para proceder...
|
|
if (lease.IsAcquired)
|
|
{*/
|
|
var client = _httpClientFactory.CreateClient("ElectoralApiClient");
|
|
var requestUri = $"/api/catalogo/getAgrupaciones?distritoId={distritoId}&categoriaId={categoriaId}";
|
|
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<List<AgrupacionDto>>() : null;
|
|
/* }
|
|
// Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null.
|
|
return null;*/
|
|
}
|
|
|
|
public async Task<ResultadosDto?> GetResultadosAsync(string authToken, string distritoId, string? seccionId, string? municipioId, int categoriaId)
|
|
{
|
|
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
|
|
if (!lease.IsAcquired) return null;
|
|
|
|
var client = _httpClientFactory.CreateClient("ElectoralApiClient");
|
|
var requestUri = $"/api/resultados/getResultados?distritoId={distritoId}&seccionId={seccionId}&categoriaId={categoriaId}";
|
|
|
|
if (!string.IsNullOrEmpty(municipioId))
|
|
{
|
|
requestUri += $"&municipioId={municipioId}";
|
|
}
|
|
|
|
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
|
request.Headers.Add("Authorization", $"Bearer {authToken}");
|
|
|
|
try
|
|
{
|
|
var response = await client.SendAsync(request);
|
|
|
|
// --- APLICAMOS LA MISMA LÓGICA DEFENSIVA ---
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
try
|
|
{
|
|
// Leemos el contenido como un string primero para poder loguearlo si falla.
|
|
var contentString = await response.Content.ReadAsStringAsync();
|
|
if (string.IsNullOrEmpty(contentString))
|
|
{
|
|
_logger.LogWarning("La API devolvió 200 OK pero con cuerpo vacío para getResultados. URI: {uri}", requestUri);
|
|
return null;
|
|
}
|
|
|
|
return JsonSerializer.Deserialize<ResultadosDto>(contentString);
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
_logger.LogWarning(ex, "La API devolvió una respuesta no-JSON para getResultados. URI: {uri}", requestUri);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
_logger.LogWarning("La API devolvió un código de error {statusCode} para getResultados. URI: {uri}", response.StatusCode, requestUri);
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "La petición HTTP a getResultados falló. URI: {uri}", requestUri);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<RepartoBancasDto?> GetBancasAsync(string authToken, string distritoId, string? seccionProvincialId, int categoriaId)
|
|
{
|
|
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
|
|
if (!lease.IsAcquired) return null;
|
|
|
|
var client = _httpClientFactory.CreateClient("ElectoralApiClient");
|
|
var requestUri = $"/api/resultados/getBancas?distritoId={distritoId}&categoriaId={categoriaId}";
|
|
if (!string.IsNullOrEmpty(seccionProvincialId))
|
|
{
|
|
requestUri += $"&seccionProvincialId={seccionProvincialId}";
|
|
}
|
|
|
|
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
|
request.Headers.Add("Authorization", $"Bearer {authToken}");
|
|
|
|
try
|
|
{
|
|
var response = await client.SendAsync(request);
|
|
|
|
// --- CORRECCIÓN FINAL ---
|
|
// Eliminamos la comprobación de ContentLength. Confiamos en el try-catch.
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
try
|
|
{
|
|
return await response.Content.ReadFromJsonAsync<RepartoBancasDto>();
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
// Esto se activará si el cuerpo está vacío o no es un JSON válido.
|
|
_logger.LogWarning(ex, "La API devolvió una respuesta no-JSON para getBancas. URI: {uri}", requestUri);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
_logger.LogWarning("La API devolvió un código de error {statusCode} para getBancas. URI: {uri}", response.StatusCode, requestUri);
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "La petición HTTP a getBancas falló. URI: {uri}", requestUri);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<List<string>?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId, int? categoriaId = null)
|
|
{
|
|
// "Pedir una ficha". Este método ahora devuelve un "lease" (permiso).
|
|
// Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo.
|
|
/*
|
|
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
|
|
|
|
// Si se nos concede el permiso para proceder...
|
|
if (lease.IsAcquired)
|
|
{*/
|
|
var client = _httpClientFactory.CreateClient("ElectoralApiClient");
|
|
var requestUri = $"/api/resultados/getTelegramasTotalizados?distritoId={distritoId}&seccionId={seccionId}";
|
|
|
|
if (categoriaId.HasValue)
|
|
{
|
|
requestUri += $"&categoriaId={categoriaId.Value}";
|
|
}
|
|
|
|
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
|
request.Headers.Add("Authorization", $"Bearer {authToken}");
|
|
var response = await client.SendAsync(request);
|
|
|
|
// Ahora deserializamos al tipo correcto: List<string>
|
|
return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync<List<string>>() : null;
|
|
/* }
|
|
// Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null.
|
|
return null;*/
|
|
}
|
|
|
|
public async Task<TelegramaFileDto?> GetTelegramaFileAsync(string authToken, string mesaId)
|
|
{
|
|
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
|
|
if (!lease.IsAcquired) return null;
|
|
|
|
var client = _httpClientFactory.CreateClient("ElectoralApiClient");
|
|
var requestUri = $"/api/resultados/getFile?mesaId={mesaId}";
|
|
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
|
request.Headers.Add("Authorization", $"Bearer {authToken}");
|
|
|
|
try
|
|
{
|
|
var response = await client.SendAsync(request);
|
|
|
|
// --- APLICAMOS LA MISMA CORRECCIÓN AQUÍ POR CONSISTENCIA ---
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
try
|
|
{
|
|
return await response.Content.ReadFromJsonAsync<TelegramaFileDto>();
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
_logger.LogWarning(ex, "La API devolvió una respuesta no-JSON para getFile para la mesa {mesaId}", mesaId);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
_logger.LogWarning("La API devolvió un código de error {statusCode} para la mesa {mesaId}", response.StatusCode, mesaId);
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "La petición HTTP a getFile falló para la mesa {mesaId}", mesaId);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<EstadoRecuentoGeneralDto?> GetEstadoRecuentoGeneralAsync(string authToken, string distritoId, int categoriaId)
|
|
{
|
|
// "Pedir una ficha". Este método ahora devuelve un "lease" (permiso).
|
|
// Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo.
|
|
/*
|
|
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
|
|
|
|
// Si se nos concede el permiso para proceder...
|
|
if (lease.IsAcquired)
|
|
{*/
|
|
var client = _httpClientFactory.CreateClient("ElectoralApiClient");
|
|
// 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);
|
|
return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync<EstadoRecuentoGeneralDto>() : null;
|
|
/* }
|
|
// Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null.
|
|
return null;*/
|
|
}
|
|
|
|
public async Task<List<CategoriaDto>?> GetCategoriasAsync(string authToken)
|
|
{
|
|
// "Pedir una ficha". Este método ahora devuelve un "lease" (permiso).
|
|
// Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo.
|
|
/*
|
|
using RateLimitLease lease = await _rateLimiter.AcquireAsync(1);
|
|
|
|
// Si se nos concede el permiso para proceder...
|
|
if (lease.IsAcquired)
|
|
{*/
|
|
var client = _httpClientFactory.CreateClient("ElectoralApiClient");
|
|
var request = new HttpRequestMessage(HttpMethod.Get, "/api/catalogo/getCategorias");
|
|
request.Headers.Add("Authorization", $"Bearer {authToken}");
|
|
var response = await client.SendAsync(request);
|
|
return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync<List<CategoriaDto>>() : null;
|
|
/* }
|
|
// Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null.
|
|
return null;*/
|
|
}
|
|
} |