From 075710287daf19e023ea0bb5700d894f46ed2b58 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Tue, 1 Jul 2025 11:25:37 -0300 Subject: [PATCH] feat: Add initial database migration with FluentMigrator --- src/Mercados.Api/Mercados.Api.csproj | 1 + src/Mercados.Api/Program.cs | 40 ++++++++++- .../20250701113000_CreateInitialTables.cs | 70 +++++++++++++++++++ 3 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 src/Mercados.Database/Migrations/20250701113000_CreateInitialTables.cs diff --git a/src/Mercados.Api/Mercados.Api.csproj b/src/Mercados.Api/Mercados.Api.csproj index 82b7c07..d2cd28a 100644 --- a/src/Mercados.Api/Mercados.Api.csproj +++ b/src/Mercados.Api/Mercados.Api.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Mercados.Api/Program.cs b/src/Mercados.Api/Program.cs index fdf6797..37020de 100644 --- a/src/Mercados.Api/Program.cs +++ b/src/Mercados.Api/Program.cs @@ -1,20 +1,54 @@ -// Importamos los namespaces necesarios +using FluentMigrator.Runner; // <--- AÑADIR +using Mercados.Database.Migrations; // <--- AÑADIR using Mercados.Infrastructure; using Mercados.Infrastructure.Persistence; +using System.Reflection; // <--- AÑADIR var builder = WebApplication.CreateBuilder(args); -// 1. Registramos nuestra fábrica de conexiones como un Singleton. -// Solo se creará una instancia que leerá la configuration una vez. +// --- V INICIO DE NUESTRO CÓDIGO V --- + +// 1. Registramos nuestra fábrica de conexiones. builder.Services.AddSingleton(); +// 2. Configurar FluentMigrator +builder.Services + .AddFluentMigratorCore() + .ConfigureRunner(rb => rb + // Usar el conector para SQL Server + .AddSqlServer() + // Obtener la cadena de conexión desde appsettings.json + .WithGlobalConnectionString(builder.Configuration.GetConnectionString("DefaultConnection")) + // Definir el ensamblado (proyecto) que contiene las migraciones + .ScanIn(typeof(CreateInitialTables).Assembly).For.Migrations()) + // Habilitar el logging para ver qué hacen las migraciones en la consola + .AddLogging(lb => lb.AddFluentMigratorConsole()); + + +// --- ^ FIN DE NUESTRO CÓDIGO ^ --- + // 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(); var app = builder.Build(); +// --- V INICIO DE NUESTRO CÓDIGO DE EJECUCIÓN V --- + +// 3. Ejecutar las migraciones al iniciar la aplicación (ideal para desarrollo y despliegues sencillos) +// Obtenemos el "scope" de los servicios para poder solicitar el MigrationRunner +using (var scope = app.Services.CreateScope()) +{ + var migrationRunner = scope.ServiceProvider.GetRequiredService(); + // Ejecuta las migraciones pendientes + migrationRunner.MigrateUp(); +} + +// --- ^ FIN DE NUESTRO CÓDIGO DE EJECUCIÓN ^ --- + + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { diff --git a/src/Mercados.Database/Migrations/20250701113000_CreateInitialTables.cs b/src/Mercados.Database/Migrations/20250701113000_CreateInitialTables.cs new file mode 100644 index 0000000..cf2faf4 --- /dev/null +++ b/src/Mercados.Database/Migrations/20250701113000_CreateInitialTables.cs @@ -0,0 +1,70 @@ +using FluentMigrator; + +namespace Mercados.Database.Migrations +{ + // El número es la versión única de esta migración. + // Usar un timestamp es una práctica común y segura. + [Migration(20250701113000)] + public class CreateInitialTables : Migration + { + /// + /// Define las acciones a realizar para aplicar la migración (crear tablas, etc.). + /// + public override void Up() + { + // --- Tabla para Cotizaciones de Ganado --- + Create.Table("CotizacionesGanado") + .WithColumn("Id").AsInt64().PrimaryKey().Identity() // Usamos Int64 (long) para los IDs + .WithColumn("Categoria").AsString(100).NotNullable() + .WithColumn("Especificaciones").AsString(200).NotNullable() + .WithColumn("Maximo").AsDecimal(18, 2).NotNullable() + .WithColumn("Minimo").AsDecimal(18, 2).NotNullable() + .WithColumn("Promedio").AsDecimal(18, 2).NotNullable() + .WithColumn("Mediano").AsDecimal(18, 2).NotNullable() + .WithColumn("Cabezas").AsInt32().NotNullable() + .WithColumn("KilosTotales").AsInt32().NotNullable() + .WithColumn("KilosPorCabeza").AsInt32().NotNullable() + .WithColumn("ImporteTotal").AsDecimal(18, 2).NotNullable() + .WithColumn("FechaRegistro").AsDateTime().NotNullable(); + + // --- Tabla para Cotizaciones de Granos --- + Create.Table("CotizacionesGranos") + .WithColumn("Id").AsInt64().PrimaryKey().Identity() + .WithColumn("Nombre").AsString(50).NotNullable() + .WithColumn("Precio").AsDecimal(18, 2).NotNullable() + .WithColumn("VariacionPrecio").AsDecimal(18, 2).NotNullable() + .WithColumn("FechaOperacion").AsDateTime().NotNullable() + .WithColumn("FechaRegistro").AsDateTime().NotNullable(); + + // --- Tabla para Cotizaciones de Bolsa --- + Create.Table("CotizacionesBolsa") + .WithColumn("Id").AsInt64().PrimaryKey().Identity() + .WithColumn("Ticker").AsString(20).NotNullable() + .WithColumn("Mercado").AsString(50).NotNullable() // "EEUU", "Local" + .WithColumn("PrecioActual").AsDecimal(18, 2).NotNullable() + .WithColumn("Apertura").AsDecimal(18, 2).NotNullable() + .WithColumn("CierreAnterior").AsDecimal(18, 2).NotNullable() + .WithColumn("PorcentajeCambio").AsDecimal(18, 4).NotNullable() // Más precisión para porcentajes + .WithColumn("FechaRegistro").AsDateTime().NotNullable(); + + // --- Tabla para auditar las fuentes de datos --- + Create.Table("FuentesDatos") + .WithColumn("Id").AsInt64().PrimaryKey().Identity() + .WithColumn("Nombre").AsString(100).NotNullable().Unique() // El nombre debe ser único + .WithColumn("UltimaEjecucionExitosa").AsDateTime().NotNullable() + .WithColumn("Url").AsString(500).Nullable(); + } + + /// + /// Define las acciones para revertir la migración (eliminar las tablas). + /// Esto es útil si necesitas deshacer un cambio. + /// + public override void Down() + { + Delete.Table("FuentesDatos"); + Delete.Table("CotizacionesBolsa"); + Delete.Table("CotizacionesGranos"); + Delete.Table("CotizacionesGanado"); + } + } +} \ No newline at end of file