feat: Add visual summary cards for Agro/Grains and implement 24h time format

This commit is contained in:
2025-07-01 16:05:26 -03:00
parent c4eead1033
commit ab9e77fa81
22 changed files with 908 additions and 125 deletions

View File

@@ -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.");
}
}
}
}

View File

@@ -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
###
###

View File

@@ -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();

View File

@@ -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"
}

View File

@@ -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 });
}
}
}

View File

@@ -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);
}
}

View File

@@ -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.