feat(Worker): Implementa servicio de notificación para alertas de fallos críticos - Se remueve .env y se utilizan appsettings.Development.json y User Secrets
This commit is contained in:
		| @@ -6,90 +6,62 @@ using Mercados.Infrastructure.Persistence.Repositories; | ||||
| using Mercados.Worker; | ||||
| using Polly; | ||||
| using Polly.Extensions.Http; | ||||
| using Mercados.Infrastructure.Services; | ||||
| using DotNetEnv; | ||||
| using DotNetEnv.Configuration; | ||||
|  | ||||
| // Carga las variables de entorno desde el archivo .env en la raíz de la solución. | ||||
| DotNetEnv.Env.Load(); | ||||
| 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); | ||||
| // --- Configuración del Host --- | ||||
| // Esto prepara el host del servicio, permitiendo la inyección de dependencias, | ||||
| // la configuración desde appsettings.json y el logging. | ||||
|  | ||||
| IHost host = Host.CreateDefaultBuilder(args) | ||||
|     .ConfigureServices((hostContext, services) => | ||||
|     { | ||||
|         // Obtenemos la configuración desde el host builder para usarla aquí. | ||||
|         // La línea 'config.AddDotNetEnv(optional: true);' ha sido eliminada. | ||||
|  | ||||
|         IConfiguration configuration = hostContext.Configuration; | ||||
|  | ||||
|         // --- 1. Registro de Servicios de Infraestructura --- | ||||
|  | ||||
|         // Registramos la fábrica de conexiones a la BD. Es un Singleton porque | ||||
|         // solo necesita ser creada una vez para leer la cadena de conexión. | ||||
|         services.AddSingleton<IDbConnectionFactory, SqlConnectionFactory>(); | ||||
|  | ||||
|         // Registramos los repositorios. Se crean "por petición" (Scoped). | ||||
|         // En un worker, "Scoped" significa que se creará una instancia por cada | ||||
|         // ejecución del servicio, lo cual es seguro y eficiente. | ||||
|         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 --- | ||||
|  | ||||
|         // Registramos CADA uno de nuestros fetchers. El contenedor de DI sabrá | ||||
|         // que todos implementan la interfaz IDataFetcher. | ||||
|         // Descomentados para la versión final y funcional. | ||||
|         services.AddScoped<IDataFetcher, MercadoAgroFetcher>(); | ||||
|         services.AddScoped<IDataFetcher, BcrDataFetcher>(); | ||||
|         services.AddScoped<IDataFetcher, FinnhubDataFetcher>(); | ||||
|         services.AddScoped<IDataFetcher, YahooFinanceDataFetcher>(); | ||||
|  | ||||
|         // El cliente HTTP es fundamental para hacer llamadas a APIs externas. | ||||
|         // Le damos un nombre al cliente de Finnhub para cumplir con los requisitos de su constructor. | ||||
|         //services.AddHttpClient("Finnhub"); | ||||
|          | ||||
|          // Configuramos CADA cliente HTTP que nuestros fetchers usan. | ||||
|         // IHttpClientFactory nos permite nombrar y configurar clientes de forma independiente. | ||||
|          | ||||
|         // Cliente para el scraper del MercadoAgro, con una política de reintentos | ||||
|         services.AddHttpClient("MercadoAgroFetcher") | ||||
|             .AddPolicyHandler(GetRetryPolicy()); | ||||
|         // --- 3. Configuración de Clientes HTTP con Polly --- | ||||
|         services.AddHttpClient("MercadoAgroFetcher").AddPolicyHandler(GetRetryPolicy()); | ||||
|         services.AddHttpClient("BcrDataFetcher").AddPolicyHandler(GetRetryPolicy()); | ||||
|         services.AddHttpClient("FinnhubDataFetcher").AddPolicyHandler(GetRetryPolicy()); | ||||
|  | ||||
|         // Cliente para la API de BCR, con la misma política de reintentos | ||||
|         services.AddHttpClient("BcrDataFetcher") | ||||
|             .AddPolicyHandler(GetRetryPolicy()); | ||||
|              | ||||
|         // Cliente para Finnhub, con la misma política de reintentos | ||||
|         services.AddHttpClient("FinnhubDataFetcher") | ||||
|             .AddPolicyHandler(GetRetryPolicy()); | ||||
|              | ||||
|         // Cliente para YahooFinance (aunque es menos probable que falle, es buena práctica incluirlo) | ||||
|         // La librería YahooFinanceApi usa su propio HttpClient, así que esta configuración | ||||
|         // no le afectará directamente. La resiliencia para YahooFinance la manejaremos de otra forma si es necesario. | ||||
|         // Por ahora, lo dejamos así y nos enfocamos en los que usan IHttpClientFactory. | ||||
|  | ||||
|  | ||||
|         // --- 3. Registro del Worker Principal --- | ||||
|  | ||||
|         // Finalmente, registramos nuestro servicio de fondo (el worker en sí). | ||||
|         // --- 4. Registro del Worker Principal --- | ||||
|         services.AddHostedService<DataFetchingService>(); | ||||
|     }) | ||||
|     .Build(); | ||||
|  | ||||
| // Esta función define nuestra política de reintentos. | ||||
| static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy() | ||||
| { | ||||
|     // Polly.Extensions.Http nos da este método conveniente. | ||||
|     return HttpPolicyExtensions | ||||
|         // Maneja errores de red transitorios O códigos de estado de servidor que indican un problema temporal. | ||||
|         .HandleTransientHttpError()  | ||||
|         // También maneja el error 408 Request Timeout | ||||
|         .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.RequestTimeout)  | ||||
|         // Política de reintento con espera exponencial: 3 reintentos, esperando 2^intento segundos. | ||||
|         .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),  | ||||
|         .HandleTransientHttpError() | ||||
|         .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.RequestTimeout) | ||||
|         .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), | ||||
|             onRetry: (outcome, timespan, retryAttempt, context) => | ||||
|             { | ||||
|                 // Registramos un log cada vez que se realiza un reintento. | ||||
|                 // Esta es una forma de hacerlo sin tener acceso directo al ILogger aquí. | ||||
|                 Console.WriteLine($"[Polly] Reintentando petición... Intento {retryAttempt}. Esperando {timespan.TotalSeconds}s. Causa: {outcome.Exception?.Message ?? outcome.Result.ReasonPhrase}"); | ||||
|             }); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user