fix: Resolve encoding issue for AgroFetcher and authorization for BcrFetcher

This commit is contained in:
2025-07-01 12:27:28 -03:00
parent 10f19af9f8
commit 8595dd16a8
3 changed files with 30 additions and 16 deletions

View File

@@ -11,19 +11,23 @@ namespace Mercados.Infrastructure.DataFetchers
public class BcrDataFetcher : IDataFetcher public class BcrDataFetcher : IDataFetcher
{ {
#region Clases DTO para la respuesta de la API de BCR #region Clases DTO para la respuesta de la API de BCR
private class BcrTokenResponse { private class BcrTokenResponse
{
[JsonPropertyName("data")] [JsonPropertyName("data")]
public TokenData? Data { get; set; } public TokenData? Data { get; set; }
} }
private class TokenData { private class TokenData
{
[JsonPropertyName("token")] [JsonPropertyName("token")]
public string? Token { get; set; } public string? Token { get; set; }
} }
private class BcrPreciosResponse { private class BcrPreciosResponse
{
[JsonPropertyName("data")] [JsonPropertyName("data")]
public List<BcrPrecioItem>? Data { get; set; } public List<BcrPrecioItem>? Data { get; set; }
} }
private class BcrPrecioItem { private class BcrPrecioItem
{
[JsonPropertyName("precio_Cotizacion")] [JsonPropertyName("precio_Cotizacion")]
public decimal PrecioCotizacion { get; set; } public decimal PrecioCotizacion { get; set; }
[JsonPropertyName("variacion_Precio_Cotizacion")] [JsonPropertyName("variacion_Precio_Cotizacion")]
@@ -47,10 +51,10 @@ namespace Mercados.Infrastructure.DataFetchers
private readonly ILogger<BcrDataFetcher> _logger; private readonly ILogger<BcrDataFetcher> _logger;
public BcrDataFetcher( public BcrDataFetcher(
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
ICotizacionGranoRepository cotizacionRepository, ICotizacionGranoRepository cotizacionRepository,
IFuenteDatoRepository fuenteDatoRepository, IFuenteDatoRepository fuenteDatoRepository,
IConfiguration configuration, IConfiguration configuration,
ILogger<BcrDataFetcher> logger) ILogger<BcrDataFetcher> logger)
{ {
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
@@ -65,19 +69,26 @@ namespace Mercados.Infrastructure.DataFetchers
_logger.LogInformation("Iniciando fetch para {SourceName}.", SourceName); _logger.LogInformation("Iniciando fetch para {SourceName}.", SourceName);
try try
{ {
var client = _httpClientFactory.CreateClient(); var client = _httpClientFactory.CreateClient(); // Creamos el cliente una vez
var token = await GetAuthTokenAsync(client); var token = await GetAuthTokenAsync(client);
if (string.IsNullOrEmpty(token)) if (string.IsNullOrEmpty(token))
{ {
return (false, "No se pudo obtener el token de autenticación de BCR."); return (false, "No se pudo obtener el token de autenticación de BCR.");
} }
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); // Añadimos el token a las cabeceras por defecto de este cliente para todas las peticiones futuras.
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); // Aseguramos que no haya doble "Bearer"
var cotizaciones = new List<CotizacionGrano>(); var cotizaciones = new List<CotizacionGrano>();
var fechaDesde = DateTime.Now.AddDays(-3).ToString("yyyy-MM-dd");
var fechaHasta = DateTime.Now.ToString("yyyy-MM-dd");
foreach (var grain in _grainIds) foreach (var grain in _grainIds)
{ {
var response = await client.GetFromJsonAsync<BcrPreciosResponse>( var requestUrl = $"{BaseUrl}/PreciosCamara?idGrano={grain.Value}&fechaConcertacionDesde={fechaDesde}&fechaConcertacionHasta={fechaHasta}";
$"{BaseUrl}/PreciosCamara?idGrano={grain.Value}&fechaConcertacionDesde={DateTime.Now.AddDays(-3):yyyy-MM-dd}&fechaConcertacionHasta={DateTime.Now:yyyy-MM-dd}");
// Usamos el mismo cliente que ya tiene el token configurado.
var response = await client.GetFromJsonAsync<BcrPreciosResponse>(requestUrl);
var latestRecord = response?.Data?.OrderByDescending(r => r.FechaOperacionPizarra).FirstOrDefault(); var latestRecord = response?.Data?.OrderByDescending(r => r.FechaOperacionPizarra).FirstOrDefault();
if (latestRecord != null) if (latestRecord != null)
@@ -92,12 +103,12 @@ namespace Mercados.Infrastructure.DataFetchers
}); });
} }
} }
if (!cotizaciones.Any()) return (false, "No se obtuvieron datos de granos de BCR."); if (!cotizaciones.Any()) return (false, "No se obtuvieron datos de granos de BCR.");
await _cotizacionRepository.GuardarMuchosAsync(cotizaciones); await _cotizacionRepository.GuardarMuchosAsync(cotizaciones);
await UpdateSourceInfoAsync(); await UpdateSourceInfoAsync();
_logger.LogInformation("Fetch para {SourceName} completado. Se guardaron {Count} registros.", SourceName, cotizaciones.Count); _logger.LogInformation("Fetch para {SourceName} completado. Se guardaron {Count} registros.", SourceName, cotizaciones.Count);
return (true, $"Proceso completado. Se guardaron {cotizaciones.Count} registros."); return (true, $"Proceso completado. Se guardaron {cotizaciones.Count} registros.");
} }
@@ -113,14 +124,14 @@ namespace Mercados.Infrastructure.DataFetchers
var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/Login"); var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/Login");
request.Headers.Add("api_key", _configuration["ApiKeys:Bcr:Key"]); request.Headers.Add("api_key", _configuration["ApiKeys:Bcr:Key"]);
request.Headers.Add("secret", _configuration["ApiKeys:Bcr:Secret"]); request.Headers.Add("secret", _configuration["ApiKeys:Bcr:Secret"]);
var response = await client.SendAsync(request); var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var tokenResponse = await response.Content.ReadFromJsonAsync<BcrTokenResponse>(); var tokenResponse = await response.Content.ReadFromJsonAsync<BcrTokenResponse>();
return tokenResponse?.Data?.Token; return tokenResponse?.Data?.Token;
} }
private async Task UpdateSourceInfoAsync() private async Task UpdateSourceInfoAsync()
{ {
var fuente = await _fuenteDatoRepository.ObtenerPorNombreAsync(SourceName); var fuente = await _fuenteDatoRepository.ObtenerPorNombreAsync(SourceName);

View File

@@ -9,6 +9,7 @@
<PackageReference Include="Dapper" Version="2.1.66" /> <PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" /> <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
<PackageReference Include="ThreeFourteen.Finnhub.Client" Version="1.2.0" /> <PackageReference Include="ThreeFourteen.Finnhub.Client" Version="1.2.0" />
<PackageReference Include="YahooFinanceApi" Version="2.3.3" /> <PackageReference Include="YahooFinanceApi" Version="2.3.3" />
</ItemGroup> </ItemGroup>

View File

@@ -1,9 +1,11 @@
using System.Text;
using Mercados.Infrastructure; using Mercados.Infrastructure;
using Mercados.Infrastructure.DataFetchers; using Mercados.Infrastructure.DataFetchers;
using Mercados.Infrastructure.Persistence; using Mercados.Infrastructure.Persistence;
using Mercados.Infrastructure.Persistence.Repositories; using Mercados.Infrastructure.Persistence.Repositories;
using Mercados.Worker; using Mercados.Worker;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// --- Configuración del Host --- // --- Configuración del Host ---
// Esto prepara el host del servicio, permitiendo la inyección de dependencias, // Esto prepara el host del servicio, permitiendo la inyección de dependencias,
// la configuración desde appsettings.json y el logging. // la configuración desde appsettings.json y el logging.