Mismo que anterior Commit
This commit is contained in:
		| @@ -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" /> | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
							
								
								
									
										27
									
								
								src/Mercados.Api/Utils/UtcDateTimeConverter.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/Mercados.Api/Utils/UtcDateTimeConverter.cs
									
									
									
									
									
										Normal 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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": "" | ||||
|   } | ||||
| } | ||||
| @@ -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)) | ||||
|             { | ||||
|   | ||||
| @@ -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> | ||||
|  | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -20,5 +20,13 @@ | ||||
|       "Key": "", | ||||
|       "Secret": "" | ||||
|     } | ||||
|   }, | ||||
|   "SmtpSettings": { | ||||
|     "Host": "", | ||||
|     "Port": 587, | ||||
|     "User": "", | ||||
|     "Pass": "", | ||||
|     "SenderName": "Servicio de Mercados", | ||||
|     "Recipient": "" | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user