Mismo que anterior Commit

This commit is contained in:
2025-07-03 15:56:06 -03:00
parent 20b6babc37
commit 5e317ab304
9 changed files with 67 additions and 57 deletions

15
.env
View File

@@ -1,15 +0,0 @@
# --- Conexión a la Base de Datos ---
ConnectionStrings__DefaultConnection="Server=TECNICA3;Database=MercadosDb;User Id=mercadosuser;Password=@mercados1351@;Trusted_Connection=False;Encrypt=False;"
# --- Claves de APIs Externas ---
ApiKeys__Finnhub="cuvhr0hr01qs9e81st2gcuvhr0hr01qs9e81st30"
ApiKeys__Bcr__Key="D1782A51-A5FD-EF11-9445-00155D09E201"
ApiKeys__Bcr__Secret="da96378186bc5a256fa821fbe79261ec7172dec283214da0aacca41c640f80e3"
# --- Configuración de Email para Alertas ---
SMTP_HOST="mail.eldia.com"
SMTP_PORT="587"
SMTP_USER="alertas@eldia.com"
SMTP_PASS="@Alertas713550@"
EMAIL_SENDER_NAME="Servicio de Mercados"
EMAIL_RECIPIENT="dmolinari@eldia.com"

View File

@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>28c6a673-1f1e-4140-aa75-a0d894d1fbc4</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DotNetEnv" Version="3.1.1" />
<PackageReference Include="FluentMigrator.Runner" Version="7.1.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.1" />

View File

@@ -1,19 +1,9 @@
using DotNetEnv;
using FluentMigrator.Runner;
using Mercados.Database.Migrations;
using Mercados.Infrastructure;
using Mercados.Infrastructure.Persistence;
using Mercados.Infrastructure.Persistence.Repositories;
using DotNetEnv.Configuration;
var envFilePath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../../.env"));
// Cargamos el archivo .env desde la ruta explícita.
if (!Env.Load(envFilePath).Any())
{
Console.WriteLine($"ADVERTENCIA: No se pudo encontrar el archivo .env en la ruta: {envFilePath}");
}
using Mercados.Api.Utils;
var builder = WebApplication.CreateBuilder(args);
@@ -50,7 +40,14 @@ builder.Services
.ScanIn(typeof(CreateInitialTables).Assembly).For.Migrations())
.AddLogging(lb => lb.AddFluentMigratorConsole());
// Servicios del contenedor estándar (perfecto)
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
// Añadimos nuestro convertidor personalizado para manejar las fechas.
options.JsonSerializerOptions.Converters.Add(new UtcDateTimeConverter());
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

View File

@@ -0,0 +1,27 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Mercados.Api.Utils
{
/// <summary>
/// Un convertidor de JSON personalizado para asegurar que los objetos DateTime
/// se serialicen al formato ISO 8601 en UTC (con el designador 'Z').
/// </summary>
public class UtcDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Al leer un string de fecha, nos aseguramos de que se interprete como UTC
return reader.GetDateTime().ToUniversalTime();
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
// Antes de escribir el string, especificamos que el 'Kind' es Utc.
// Si ya es Utc, no hace nada. Si es Local o Unspecified, lo trata como si fuera Utc.
// Esto es seguro porque sabemos que todas nuestras fechas en la BD son UTC.
var utcValue = DateTime.SpecifyKind(value, DateTimeKind.Utc);
writer.WriteStringValue(utcValue);
}
}
}

View File

@@ -9,11 +9,24 @@
"ConnectionStrings": {
"DefaultConnection": ""
},
"Schedules": {
"MercadoAgroganadero": "0 11 * * 1-5",
"BCR": "30 11 * * 1-5",
"Bolsas": "10 11-17 * * 1-5"
},
"ApiKeys": {
"Finnhub": "",
"Bcr": {
"Key": "",
"Secret": ""
}
},
"SmtpSettings": {
"Host": "",
"Port": 587,
"User": "",
"Pass": "",
"SenderName": "Servicio de Mercados",
"Recipient": ""
}
}

View File

@@ -20,12 +20,12 @@ namespace Mercados.Infrastructure.Services
public async Task SendFailureAlertAsync(string subject, string message, DateTime? eventTimeUtc = null)
{
// Leemos la configuración de forma segura desde IConfiguration (que a su vez lee el .env)
var smtpHost = _configuration["SMTP_HOST"];
var smtpPort = int.Parse(_configuration["SMTP_PORT"] ?? "587");
var smtpUser = _configuration["SMTP_USER"];
var smtpPass = _configuration["SMTP_PASS"];
var senderName = _configuration["EMAIL_SENDER_NAME"];
var recipient = _configuration["EMAIL_RECIPIENT"];
var smtpHost = _configuration["SmtpSettings:Host"];
var smtpPort = _configuration.GetValue<int>("SmtpSettings:Port");
var smtpUser = _configuration["SmtpSettings:User"];
var smtpPass = _configuration["SmtpSettings:Pass"];
var senderName = _configuration["SmtpSettings:SenderName"];
var recipient = _configuration["SmtpSettings:Recipient"];
if (string.IsNullOrEmpty(smtpHost) || string.IsNullOrEmpty(smtpUser) || string.IsNullOrEmpty(smtpPass))
{

View File

@@ -9,7 +9,6 @@
<ItemGroup>
<PackageReference Include="Cronos" Version="0.11.0" />
<PackageReference Include="DotNetEnv" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
</ItemGroup>

View File

@@ -7,49 +7,30 @@ using Mercados.Worker;
using Polly;
using Polly.Extensions.Http;
using Mercados.Infrastructure.Services;
using DotNetEnv;
using DotNetEnv.Configuration;
var envFilePath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../../.env"));
// Cargamos el archivo .env desde la ruta explícita.
// Si no lo encuentra, Load retornará false.
if (!Env.Load(envFilePath).Any())
{
Console.WriteLine($"ADVERTENCIA: No se pudo encontrar el archivo .env en la ruta: {envFilePath}");
}
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
// La línea 'config.AddDotNetEnv(optional: true);' ha sido eliminada.
IConfiguration configuration = hostContext.Configuration;
// --- 1. Registro de Servicios de Infraestructura ---
// El resto del código no cambia. IConfiguration recogerá automáticamente
// las variables de entorno que cargamos correctamente.
services.AddSingleton<IDbConnectionFactory, SqlConnectionFactory>();
services.AddScoped<ICotizacionGanadoRepository, CotizacionGanadoRepository>();
services.AddScoped<ICotizacionGranoRepository, CotizacionGranoRepository>();
services.AddScoped<ICotizacionBolsaRepository, CotizacionBolsaRepository>();
services.AddScoped<IFuenteDatoRepository, FuenteDatoRepository>();
//services.AddScoped<INotificationService, ConsoleNotificationService>();
services.AddScoped<INotificationService, EmailNotificationService>();
// --- 2. Registro de los Data Fetchers ---
// Descomentados para la versión final y funcional.
services.AddScoped<IDataFetcher, MercadoAgroFetcher>();
services.AddScoped<IDataFetcher, BcrDataFetcher>();
services.AddScoped<IDataFetcher, FinnhubDataFetcher>();
services.AddScoped<IDataFetcher, YahooFinanceDataFetcher>();
// --- 3. Configuración de Clientes HTTP con Polly ---
services.AddHttpClient("MercadoAgroFetcher").AddPolicyHandler(GetRetryPolicy());
services.AddHttpClient("BcrDataFetcher").AddPolicyHandler(GetRetryPolicy());
services.AddHttpClient("FinnhubDataFetcher").AddPolicyHandler(GetRetryPolicy());
// --- 4. Registro del Worker Principal ---
services.AddHostedService<DataFetchingService>();
})
.Build();

View File

@@ -20,5 +20,13 @@
"Key": "",
"Secret": ""
}
},
"SmtpSettings": {
"Host": "",
"Port": 587,
"User": "",
"Pass": "",
"SenderName": "Servicio de Mercados",
"Recipient": ""
}
}