feat: Add visual summary cards for Agro/Grains and implement 24h time format
This commit is contained in:
		| @@ -12,12 +12,12 @@ namespace Mercados.Api.Controllers | ||||
|         private readonly ICotizacionGranoRepository _granoRepo; | ||||
|         private readonly ICotizacionGanadoRepository _ganadoRepo; | ||||
|         private readonly ILogger<MercadosController> _logger; | ||||
|          | ||||
|  | ||||
|         // Inyectamos TODOS los repositorios que necesita el controlador. | ||||
|         public MercadosController( | ||||
|             ICotizacionBolsaRepository bolsaRepo,  | ||||
|             ICotizacionGranoRepository granoRepo,  | ||||
|             ICotizacionGanadoRepository ganadoRepo,  | ||||
|             ICotizacionBolsaRepository bolsaRepo, | ||||
|             ICotizacionGranoRepository granoRepo, | ||||
|             ICotizacionGanadoRepository ganadoRepo, | ||||
|             ILogger<MercadosController> logger) | ||||
|         { | ||||
|             _bolsaRepo = bolsaRepo; | ||||
| @@ -61,7 +61,7 @@ namespace Mercados.Api.Controllers | ||||
|                 return StatusCode(500, "Ocurrió un error interno en el servidor."); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
|         // --- Endpoints de Bolsa --- | ||||
|         [HttpGet("bolsa/eeuu")] | ||||
|         [ProducesResponseType(typeof(IEnumerable<CotizacionBolsa>), StatusCodes.Status200OK)] | ||||
| @@ -96,5 +96,22 @@ namespace Mercados.Api.Controllers | ||||
|                 return StatusCode(500, "Ocurrió un error interno en el servidor."); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [HttpGet("bolsa/history/{ticker}")] | ||||
|         [ProducesResponseType(typeof(IEnumerable<CotizacionBolsa>), StatusCodes.Status200OK)] | ||||
|         [ProducesResponseType(StatusCodes.Status500InternalServerError)] | ||||
|         public async Task<IActionResult> GetBolsaHistory(string ticker, [FromQuery] string mercado = "Local", [FromQuery] int dias = 30) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var data = await _bolsaRepo.ObtenerHistorialPorTickerAsync(ticker, mercado, dias); | ||||
|                 return Ok(data); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger.LogError(ex, "Error al obtener historial para el ticker {Ticker}.", ticker); | ||||
|                 return StatusCode(500, "Ocurrió un error interno en el servidor."); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +1,5 @@ | ||||
| @Mercados.Api_HostAddress = http://localhost:5045 | ||||
| @Mercados.Api_HostAddress = http://192.168.10.78:5045 | ||||
|  | ||||
| GET {{Mercados.Api_HostAddress}}/weatherforecast/ | ||||
| Accept: application/json | ||||
|  | ||||
| ### | ||||
| ### | ||||
| @@ -16,7 +16,7 @@ builder.Services.AddCors(options => | ||||
|     options.AddPolicy(name: MyAllowSpecificOrigins, | ||||
|                       policy => | ||||
|                       { | ||||
|                           policy.WithOrigins("http://localhost:5173")  | ||||
|                           policy.WithOrigins("http://localhost:5173", "http://192.168.10.78:5173") | ||||
|                                 .AllowAnyHeader() | ||||
|                                 .AllowAnyMethod(); | ||||
|                       }); | ||||
| @@ -47,7 +47,6 @@ builder.Services | ||||
|  | ||||
| // Add services to the container. | ||||
| builder.Services.AddControllers(); | ||||
| // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle | ||||
| builder.Services.AddEndpointsApiExplorer(); | ||||
| builder.Services.AddSwaggerGen(); | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
|       "commandName": "Project", | ||||
|       "dotnetRunMessages": true, | ||||
|       "launchBrowser": false, | ||||
|       "applicationUrl": "http://localhost:5045", | ||||
|       "applicationUrl": "http://0.0.0.0:5045", | ||||
|       "environmentVariables": { | ||||
|         "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|       } | ||||
| @@ -14,7 +14,7 @@ | ||||
|       "commandName": "Project", | ||||
|       "dotnetRunMessages": true, | ||||
|       "launchBrowser": false, | ||||
|       "applicationUrl": "https://localhost:7256;http://localhost:5045", | ||||
|       "applicationUrl": "https://0.0.0.0:7256;http://0.0.0.0:5045", | ||||
|       "environmentVariables": { | ||||
|         "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|       } | ||||
|   | ||||
| @@ -23,12 +23,12 @@ namespace Mercados.Infrastructure.Persistence.Repositories | ||||
|  | ||||
|             await connection.ExecuteAsync(sql, cotizaciones); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         public async Task<IEnumerable<CotizacionBolsa>> ObtenerUltimasPorMercadoAsync(string mercado) | ||||
|         { | ||||
|             using IDbConnection connection = _connectionFactory.CreateConnection(); | ||||
|  | ||||
|             // Esta consulta SQL es un poco más avanzada. Usa una "Common Table Expression" (CTE) | ||||
|             // Esta consulta usa una "Common Table Expression" (CTE) | ||||
|             // y la función ROW_NUMBER() para obtener el registro más reciente para cada Ticker | ||||
|             // dentro del mercado especificado. Es extremadamente eficiente. | ||||
|             const string sql = @" | ||||
| @@ -50,5 +50,24 @@ namespace Mercados.Infrastructure.Persistence.Repositories | ||||
|  | ||||
|             return await connection.QueryAsync<CotizacionBolsa>(sql, new { Mercado = mercado }); | ||||
|         } | ||||
|          | ||||
|         public async Task<IEnumerable<CotizacionBolsa>> ObtenerHistorialPorTickerAsync(string ticker, string mercado, int dias) | ||||
|         { | ||||
|             using IDbConnection connection = _connectionFactory.CreateConnection(); | ||||
|  | ||||
|             const string sql = @" | ||||
|                 SELECT  | ||||
|                     Id, Ticker, Mercado, PrecioActual, Apertura, CierreAnterior, PorcentajeCambio, FechaRegistro | ||||
|                 FROM  | ||||
|                     CotizacionesBolsa | ||||
|                 WHERE | ||||
|                     Ticker = @Ticker | ||||
|                     AND Mercado = @Mercado | ||||
|                     AND FechaRegistro >= DATEADD(day, -@Dias, GETUTCDATE()) | ||||
|                 ORDER BY | ||||
|                     FechaRegistro ASC;"; // ASC es importante para dibujar la línea del gráfico | ||||
|  | ||||
|             return await connection.QueryAsync<CotizacionBolsa>(sql, new { Ticker = ticker, Mercado = mercado, Dias = dias }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -6,5 +6,6 @@ namespace Mercados.Infrastructure.Persistence.Repositories | ||||
|     { | ||||
|         Task GuardarMuchosAsync(IEnumerable<CotizacionBolsa> cotizaciones); | ||||
|         Task<IEnumerable<CotizacionBolsa>> ObtenerUltimasPorMercadoAsync(string mercado); | ||||
|         Task<IEnumerable<CotizacionBolsa>> ObtenerHistorialPorTickerAsync(string ticker, string mercado, int dias); | ||||
|     } | ||||
| } | ||||
| @@ -35,7 +35,7 @@ IHost host = Host.CreateDefaultBuilder(args) | ||||
|         // que todos implementan la interfaz IDataFetcher. | ||||
|         services.AddScoped<IDataFetcher, MercadoAgroFetcher>(); | ||||
|         services.AddScoped<IDataFetcher, BcrDataFetcher>(); | ||||
|         //services.AddScoped<IDataFetcher, FinnhubDataFetcher>(); | ||||
|         services.AddScoped<IDataFetcher, FinnhubDataFetcher>(); | ||||
|         services.AddScoped<IDataFetcher, YahooFinanceDataFetcher>(); | ||||
|          | ||||
|         // El cliente HTTP es fundamental para hacer llamadas a APIs externas. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user