Init Commit
This commit is contained in:
386
Backend/MotoresArgentinosV2.MigrationTool/AdMigrator.cs
Normal file
386
Backend/MotoresArgentinosV2.MigrationTool/AdMigrator.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using MotoresArgentinosV2.MigrationTool.Models;
|
||||
|
||||
namespace MotoresArgentinosV2.MigrationTool;
|
||||
|
||||
public class AdMigrator
|
||||
{
|
||||
private readonly string _connStringAutos;
|
||||
private readonly string _connStringV2;
|
||||
|
||||
public AdMigrator(string connAutos, string connV2)
|
||||
{
|
||||
_connStringAutos = connAutos;
|
||||
_connStringV2 = connV2;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync()
|
||||
{
|
||||
Console.WriteLine("🚗 INICIANDO MIGRACIÓN DE AUTOS...");
|
||||
|
||||
using var dbAutos = new SqlConnection(_connStringAutos);
|
||||
using var dbV2 = new SqlConnection(_connStringV2);
|
||||
|
||||
// 1. CARGAR CACHÉS DE V2
|
||||
Console.WriteLine(" 📥 Cargando diccionarios de Marcas, Modelos y Usuarios...");
|
||||
|
||||
var brandsV2 = (await dbV2.QueryAsync("SELECT BrandID, LegacyID, Name FROM Brands WHERE VehicleTypeID = 1")).ToList();
|
||||
var usersV2 = (await dbV2.QueryAsync("SELECT UserID, Email, PhoneNumber FROM Users")).ToDictionary(u => (string)u.Email, u => u);
|
||||
|
||||
// 2. OBTENER AVISOS LEGACY (QUERY MEJORADA PARA ADMINS)
|
||||
Console.WriteLine(" 📥 Leyendo avisos activos del Legacy...");
|
||||
|
||||
// Esta query intenta obtener el email de dos fuentes:
|
||||
// 1. De la tabla Particulares (prioridad)
|
||||
// 2. De la tabla de autenticación (aspnet_Membership) si no está en Particulares
|
||||
var queryAds = @"
|
||||
SELECT
|
||||
A.Auto_Id,
|
||||
A.Auto_Fecha_Publicacion,
|
||||
A.Auto_Cant_Visitas,
|
||||
COALESCE(P.Part_Mail, Mem.Email) as EmailVendedor,
|
||||
M.Marca_Detalle,
|
||||
Mo.Modelo_Detalle,
|
||||
A.Auto_Version,
|
||||
An.Año_Detalle,
|
||||
A.Auto_precio,
|
||||
Mon.Mone_Detalle,
|
||||
A.Auto_Kilometros,
|
||||
A.Auto_Detalle as Descripcion,
|
||||
C.Comb_Detalle,
|
||||
Col.Color_detalle,
|
||||
Car.Carroceria_Nombre,
|
||||
CASE WHEN D.DetAut_PapelesALDia = 1 THEN 1 ELSE 0 END as PapelesAlDia,
|
||||
COALESCE(P.Part_Telefono, '') as Part_Telefono,
|
||||
COALESCE(P.Part_Celular, '') as Part_Celular
|
||||
FROM Autos A
|
||||
-- Join original con particulares
|
||||
LEFT JOIN Particulares P ON A.Auto_Usuario_Id = P.Part_Usu_Nombre
|
||||
-- Join adicional para rescatar usuarios admin/internos sin perfil particular
|
||||
LEFT JOIN aspnet_Users U ON A.Auto_Usuario_Id = U.UserName
|
||||
LEFT JOIN aspnet_Membership Mem ON U.UserId = Mem.UserId
|
||||
|
||||
-- Joins de datos técnicos
|
||||
LEFT JOIN Marca M ON A.Auto_Marca_Id = M.Marca_Id
|
||||
LEFT JOIN Modelo Mo ON A.Auto_Modelo_Id = Mo.Modelo_Id
|
||||
LEFT JOIN Año An ON A.Auto_Año = An.Año_Id
|
||||
LEFT JOIN Combustible C ON A.Auto_Comb_Id = C.Comb_Id
|
||||
LEFT JOIN Color Col ON A.Auto_Color = Col.Color_Id
|
||||
LEFT JOIN Carroceria Car ON A.Auto_Carroceria_Id = Car.Carroceria_Id
|
||||
LEFT JOIN Moneda Mon ON A.Auto_Moneda_Id = Mon.Mone_id
|
||||
LEFT JOIN Detalle_Auto D ON A.Auto_Id = D.DetAut_Auto_Id
|
||||
|
||||
WHERE A.Auto_Estado = 0
|
||||
AND A.Auto_Tipo_Id = 1
|
||||
AND A.Auto_Fecha_Finalizacion >= GETDATE()";
|
||||
|
||||
var adsLegacy = (await dbAutos.QueryAsync<LegacyAdData>(queryAds)).ToList();
|
||||
Console.WriteLine($" ✅ Encontrados {adsLegacy.Count} avisos candidatos.");
|
||||
|
||||
// 3. OBTENER FOTOS
|
||||
var idsString = string.Join(",", adsLegacy.Select(a => a.Auto_Id));
|
||||
var photosLegacy = new List<LegacyAdPhoto>();
|
||||
if (adsLegacy.Any())
|
||||
{
|
||||
photosLegacy = (await dbAutos.QueryAsync<LegacyAdPhoto>(
|
||||
$"SELECT Foto_Id, Foto_Auto_Id, Foto_Ruta FROM Fotos_Autos WHERE Foto_Auto_Id IN ({idsString})")).ToList();
|
||||
}
|
||||
|
||||
int migrados = 0;
|
||||
int omitidosUsuario = 0;
|
||||
|
||||
foreach (var ad in adsLegacy)
|
||||
{
|
||||
// A. VALIDAR USUARIO
|
||||
string emailKey = ad.EmailVendedor?.ToLower().Trim() ?? "";
|
||||
|
||||
if (!usersV2.TryGetValue(emailKey, out var userV2))
|
||||
{
|
||||
omitidosUsuario++;
|
||||
// Console.WriteLine($" ⚠️ Aviso {ad.Auto_Id} omitido: Usuario {emailKey} no existe en V2.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// B. RESOLVER MARCA Y MODELO
|
||||
var brandV2 = brandsV2.FirstOrDefault(b => b.Name.ToLower() == (ad.Marca_Detalle ?? "").ToLower());
|
||||
if (brandV2 == null)
|
||||
{
|
||||
// Si la marca no existe, saltamos por seguridad o podríamos asignar "Otros"
|
||||
continue;
|
||||
}
|
||||
|
||||
int modelId = await GetOrCreateModelAsync(dbV2, (int)brandV2.BrandID, ad.Modelo_Detalle);
|
||||
|
||||
// C. CONSTRUIR DATOS
|
||||
int.TryParse(ad.Año_Detalle, out int year);
|
||||
|
||||
string versionName = $"{ad.Modelo_Detalle} {ad.Auto_Version}".Trim();
|
||||
if (versionName.Length > 100) versionName = versionName.Substring(0, 100);
|
||||
|
||||
string desc = ad.Descripcion ?? "";
|
||||
if (ad.PapelesAlDia == 1) desc += "\n\n✔️ Papeles al día.";
|
||||
|
||||
// Contacto: Si venía de Particulares, usamos eso. Si no (admins), usamos el del usuario V2
|
||||
string contactPhone = !string.IsNullOrWhiteSpace(ad.Part_Celular) ? ad.Part_Celular : ad.Part_Telefono;
|
||||
if (string.IsNullOrWhiteSpace(contactPhone)) contactPhone = userV2.PhoneNumber;
|
||||
|
||||
string currency = (ad.Mone_Detalle != null && ad.Mone_Detalle.Contains("US")) ? "USD" : "ARS";
|
||||
|
||||
// D. INSERTAR AVISO
|
||||
var insertAdSql = @"
|
||||
INSERT INTO Ads (
|
||||
UserID, VehicleTypeID, BrandID, ModelID, VersionName, Year, KM,
|
||||
Price, Currency, Description, IsFeatured, StatusID, CreatedAt, PublishedAt, ExpiresAt,
|
||||
FuelType, Color, Segment, Location, Condition, Transmission, Steering,
|
||||
ContactPhone, ContactEmail, DisplayContactInfo, LegacyAdID,
|
||||
ViewsCounter
|
||||
) VALUES (
|
||||
@UserID, @VehicleTypeID, @BrandID, @ModelID, @VersionName, @Year, @KM,
|
||||
@Price, @Currency, @Description, 0, 4, @CreatedAt, @PublishedAt, DATEADD(day, 30, @PublishedAt),
|
||||
@FuelType, @Color, @Segment, 'La Plata', 'No Especificado', 'No Especificado', 'No Especificado',
|
||||
@ContactPhone, @ContactEmail, 1, @LegacyAdID,
|
||||
@ViewsCounter
|
||||
);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
|
||||
int newAdId = await dbV2.ExecuteScalarAsync<int>(insertAdSql, new
|
||||
{
|
||||
UserID = userV2.UserID,
|
||||
VehicleTypeID = 1,
|
||||
BrandID = brandV2.BrandID,
|
||||
ModelID = modelId,
|
||||
VersionName = versionName,
|
||||
Year = year,
|
||||
KM = ad.Auto_Kilometros,
|
||||
Price = ad.Auto_precio,
|
||||
Currency = currency,
|
||||
Description = desc,
|
||||
FuelType = ad.Comb_Detalle,
|
||||
Color = ad.Color_detalle,
|
||||
Segment = ad.Carroceria_Nombre,
|
||||
ContactPhone = contactPhone,
|
||||
ContactEmail = userV2.Email,
|
||||
LegacyAdID = ad.Auto_Id,
|
||||
ViewsCounter = ad.Auto_Cant_Visitas,
|
||||
CreatedAt = ad.Auto_Fecha_Publicacion,
|
||||
PublishedAt = ad.Auto_Fecha_Publicacion
|
||||
});
|
||||
|
||||
// E. INSERTAR FOTOS
|
||||
var misFotos = photosLegacy
|
||||
.Where(p => p.Foto_Auto_Id == ad.Auto_Id && !p.Foto_Ruta.Contains("nofoto"))
|
||||
.ToList();
|
||||
|
||||
int order = 0;
|
||||
foreach (var foto in misFotos)
|
||||
{
|
||||
string fileName = Path.GetFileName(foto.Foto_Ruta);
|
||||
// Aquí podrías agregar la lógica para copiar el archivo físico si tienes acceso
|
||||
// Por ahora solo guardamos la referencia en DB
|
||||
string v2Path = $"/uploads/legacy/{fileName}";
|
||||
|
||||
await dbV2.ExecuteAsync(@"
|
||||
INSERT INTO AdPhotos (AdID, FilePath, IsCover, SortOrder)
|
||||
VALUES (@AdID, @FilePath, @IsCover, @SortOrder)",
|
||||
new { AdID = newAdId, FilePath = v2Path, IsCover = (order == 0), SortOrder = order });
|
||||
|
||||
order++;
|
||||
}
|
||||
|
||||
migrados++;
|
||||
}
|
||||
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine($"🏁 MIGRACIÓN DE AUTOS FINALIZADA");
|
||||
Console.WriteLine($"✅ Insertados: {migrados}");
|
||||
Console.WriteLine($"⏩ Omitidos (Sin usuario/marca): {omitidosUsuario}");
|
||||
Console.WriteLine("==================================================");
|
||||
}
|
||||
|
||||
public async Task MigrateMotosAsync()
|
||||
{
|
||||
Console.WriteLine("\n🏍️ INICIANDO MIGRACIÓN DE MOTOS...");
|
||||
|
||||
using var dbAutos = new SqlConnection(_connStringAutos);
|
||||
using var dbV2 = new SqlConnection(_connStringV2);
|
||||
|
||||
// 1. CARGAR CACHÉS (Solo Marcas de Motos)
|
||||
Console.WriteLine(" 📥 Cargando diccionarios...");
|
||||
var brandsV2 = (await dbV2.QueryAsync("SELECT BrandID, LegacyID, Name FROM Brands WHERE VehicleTypeID = 2")).ToList(); // 2 = Motos
|
||||
var usersV2 = (await dbV2.QueryAsync("SELECT UserID, Email, PhoneNumber FROM Users")).ToDictionary(u => (string)u.Email, u => u);
|
||||
|
||||
// 2. QUERY MOTOS
|
||||
var query = @"
|
||||
SELECT
|
||||
M.Moto_Id,
|
||||
M.Moto_Fecha_Publicacion,
|
||||
M.Moto_Cant_Visitas,
|
||||
COALESCE(P.Part_Mail, Mem.Email) as EmailVendedor,
|
||||
|
||||
Ma.Marca_Moto_Detalle as Marca_Detalle,
|
||||
Mo.Modelo_Moto_Detalle as Modelo_Detalle,
|
||||
|
||||
An.Año_Detalle,
|
||||
M.Moto_Precio,
|
||||
Mon.Mone_Detalle,
|
||||
M.Moto_Kilometraje,
|
||||
M.Moto_Detalle as Descripcion,
|
||||
M.Moto_Cilindrada,
|
||||
M.Moto_Tipo_Cuatri_Id,
|
||||
|
||||
COALESCE(P.Part_Telefono, '') as Part_Telefono,
|
||||
COALESCE(P.Part_Celular, '') as Part_Celular
|
||||
FROM Motos M
|
||||
LEFT JOIN Particulares P ON M.Moto_Usuario_Id = P.Part_Usu_Nombre
|
||||
LEFT JOIN aspnet_Users U ON M.Moto_Usuario_Id = U.UserName
|
||||
LEFT JOIN aspnet_Membership Mem ON U.UserId = Mem.UserId
|
||||
|
||||
-- JOINS TÉCNICOS
|
||||
LEFT JOIN Marca_Moto Ma ON M.Moto_Marca_Id = Ma.Marca_Moto_Id
|
||||
LEFT JOIN Modelo_Moto Mo ON M.Moto_Modelo_Id = Mo.Modelo_Moto_Id
|
||||
LEFT JOIN Año An ON M.Moto_Año = An.Año_Id
|
||||
LEFT JOIN Moneda Mon ON M.Moto_Moneda_Id = Mon.Mone_id
|
||||
|
||||
WHERE M.Moto_Estado = 0
|
||||
AND M.Moto_Fecha_Finalizacion >= GETDATE()";
|
||||
|
||||
var adsLegacy = (await dbAutos.QueryAsync<LegacyMotoData>(query)).ToList();
|
||||
Console.WriteLine($" ✅ Encontradas {adsLegacy.Count} motos candidatas.");
|
||||
|
||||
// 3. FOTOS
|
||||
var idsString = string.Join(",", adsLegacy.Select(a => a.Moto_Id));
|
||||
var photosLegacy = new List<LegacyMotoPhoto>();
|
||||
if (adsLegacy.Any())
|
||||
{
|
||||
photosLegacy = (await dbAutos.QueryAsync<LegacyMotoPhoto>(
|
||||
$"SELECT Foto_Id, Foto_Moto_Id, Foto_Ruta FROM Fotos_Motos WHERE Foto_Moto_Id IN ({idsString})")).ToList();
|
||||
}
|
||||
|
||||
int migrados = 0;
|
||||
int omitidos = 0;
|
||||
|
||||
foreach (var ad in adsLegacy)
|
||||
{
|
||||
// A. Validar Usuario
|
||||
string emailKey = ad.EmailVendedor?.ToLower().Trim() ?? "";
|
||||
if (!usersV2.TryGetValue(emailKey, out var userV2))
|
||||
{
|
||||
omitidos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// B. Resolver Marca y Modelo
|
||||
var brandV2 = brandsV2.FirstOrDefault(b => b.Name.ToLower() == (ad.Marca_Detalle ?? "").ToLower());
|
||||
if (brandV2 == null) continue; // Marca no mapeada, saltar
|
||||
|
||||
int modelId = await GetOrCreateModelAsync(dbV2, (int)brandV2.BrandID, ad.Modelo_Detalle);
|
||||
|
||||
// C. Datos
|
||||
int.TryParse(ad.Año_Detalle, out int year);
|
||||
string currency = (ad.Mone_Detalle != null && ad.Mone_Detalle.Contains("US")) ? "USD" : "ARS";
|
||||
|
||||
// Nombre Versión: Concatenamos Modelo + Cilindrada si existe
|
||||
string versionName = ad.Modelo_Detalle;
|
||||
// La cilindrada la ponemos en la descripción como pediste, pero a veces queda bien en el titulo.
|
||||
// Lo dejamos en el título solo si es muy corto, si no, description.
|
||||
|
||||
// Descripción: Concatenar Cilindrada
|
||||
string desc = ad.Descripcion ?? "";
|
||||
if (!string.IsNullOrEmpty(ad.Moto_Cilindrada) && ad.Moto_Cilindrada != "0")
|
||||
{
|
||||
desc += $"\n\nCilindrada: {ad.Moto_Cilindrada}cc";
|
||||
}
|
||||
|
||||
// Segmento: Lógica de Cuatriciclo
|
||||
string segment = ad.Moto_Tipo_Cuatri_Id > 0 ? "Cuatriciclo" : "No Especificado";
|
||||
|
||||
// Contacto
|
||||
string contactPhone = !string.IsNullOrWhiteSpace(ad.Part_Celular) ? ad.Part_Celular : ad.Part_Telefono;
|
||||
if (string.IsNullOrWhiteSpace(contactPhone)) contactPhone = userV2.PhoneNumber;
|
||||
|
||||
// D. Insertar
|
||||
var insertSql = @"
|
||||
INSERT INTO Ads (
|
||||
UserID, VehicleTypeID, BrandID, ModelID, VersionName, Year, KM,
|
||||
Price, Currency, Description, IsFeatured, StatusID, CreatedAt, PublishedAt, ExpiresAt,
|
||||
FuelType, Color, Segment, Location, Condition, Transmission, Steering,
|
||||
ContactPhone, ContactEmail, DisplayContactInfo, LegacyAdID, ViewsCounter
|
||||
) VALUES (
|
||||
@UserID, 2, @BrandID, @ModelID, @VersionName, @Year, @KM,
|
||||
@Price, @Currency, @Description, 0, 4, @CreatedAt, @PublishedAt, DATEADD(day, 30, @PublishedAt),
|
||||
'Nafta', 'No Especificado', @Segment, 'La Plata', 'No Especificado', 'Manual', 'No Especificado',
|
||||
@ContactPhone, @ContactEmail, 1, @LegacyAdID, @ViewsCounter
|
||||
);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
|
||||
int newAdId = await dbV2.ExecuteScalarAsync<int>(insertSql, new
|
||||
{
|
||||
UserID = userV2.UserID,
|
||||
BrandID = brandV2.BrandID,
|
||||
ModelID = modelId,
|
||||
VersionName = versionName,
|
||||
Year = year,
|
||||
KM = ad.Moto_Kilometraje,
|
||||
Price = ad.Moto_Precio,
|
||||
Currency = currency,
|
||||
Description = desc,
|
||||
Segment = segment,
|
||||
ContactPhone = contactPhone,
|
||||
ContactEmail = userV2.Email,
|
||||
LegacyAdID = ad.Moto_Id,
|
||||
ViewsCounter = ad.Moto_Cant_Visitas,
|
||||
CreatedAt = ad.Moto_Fecha_Publicacion,
|
||||
PublishedAt = ad.Moto_Fecha_Publicacion
|
||||
});
|
||||
|
||||
// E. Fotos
|
||||
var misFotos = photosLegacy
|
||||
.Where(p => p.Foto_Moto_Id == ad.Moto_Id && !p.Foto_Ruta.Contains("nofoto"))
|
||||
.ToList();
|
||||
|
||||
int order = 0;
|
||||
foreach (var foto in misFotos)
|
||||
{
|
||||
string fileName = Path.GetFileName(foto.Foto_Ruta);
|
||||
string v2Path = $"/uploads/legacy/{fileName}";
|
||||
|
||||
await dbV2.ExecuteAsync(@"
|
||||
INSERT INTO AdPhotos (AdID, FilePath, IsCover, SortOrder)
|
||||
VALUES (@AdID, @FilePath, @IsCover, @SortOrder)",
|
||||
new { AdID = newAdId, FilePath = v2Path, IsCover = (order == 0), SortOrder = order });
|
||||
|
||||
order++;
|
||||
}
|
||||
|
||||
migrados++;
|
||||
}
|
||||
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine($"🏁 MIGRACIÓN MOTOS FINALIZADA: {migrados} insertados.");
|
||||
Console.WriteLine("==================================================");
|
||||
}
|
||||
|
||||
private async Task<int> GetOrCreateModelAsync(SqlConnection db, int brandId, string modelName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modelName)) modelName = "Modelo Genérico";
|
||||
|
||||
var existing = await db.QueryFirstOrDefaultAsync<int?>(
|
||||
"SELECT ModelID FROM Models WHERE BrandID = @brandId AND Name = @modelName",
|
||||
new { brandId, modelName });
|
||||
|
||||
if (existing.HasValue) return existing.Value;
|
||||
|
||||
var newId = await db.ExecuteScalarAsync<int>(
|
||||
"INSERT INTO Models (BrandID, Name) VALUES (@brandId, @modelName); SELECT CAST(SCOPE_IDENTITY() as int);",
|
||||
new { brandId, modelName });
|
||||
|
||||
return newId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace MotoresArgentinosV2.MigrationTool.Models;
|
||||
|
||||
public class LegacyAdData
|
||||
{
|
||||
public int Auto_Id { get; set; }
|
||||
public string EmailVendedor { get; set; } = string.Empty;
|
||||
public string Marca_Detalle { get; set; } = string.Empty;
|
||||
public string Modelo_Detalle { get; set; } = string.Empty;
|
||||
public string Auto_Version { get; set; } = string.Empty;
|
||||
public string Año_Detalle { get; set; } = string.Empty;
|
||||
public decimal Auto_precio { get; set; }
|
||||
public string Mone_Detalle { get; set; } = string.Empty;
|
||||
public int Auto_Kilometros { get; set; }
|
||||
public string Descripcion { get; set; } = string.Empty;
|
||||
public string Comb_Detalle { get; set; } = string.Empty;
|
||||
public string Color_detalle { get; set; } = string.Empty;
|
||||
public string Carroceria_Nombre { get; set; } = string.Empty;
|
||||
public int PapelesAlDia { get; set; }
|
||||
public string Part_Telefono { get; set; } = string.Empty;
|
||||
public string Part_Celular { get; set; } = string.Empty;
|
||||
public int Auto_Cant_Visitas { get; set; }
|
||||
public DateTime Auto_Fecha_Publicacion { get; set; }
|
||||
}
|
||||
|
||||
public class LegacyAdPhoto
|
||||
{
|
||||
public int Foto_Id { get; set; }
|
||||
public int Foto_Auto_Id { get; set; }
|
||||
public string Foto_Ruta { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Backend/MotoresArgentinosV2.MigrationTool/Models/LegacyMotoData.cs
|
||||
|
||||
namespace MotoresArgentinosV2.MigrationTool.Models;
|
||||
|
||||
public class LegacyMotoData
|
||||
{
|
||||
public int Moto_Id { get; set; }
|
||||
public DateTime Moto_Fecha_Publicacion { get; set; }
|
||||
public int Moto_Cant_Visitas { get; set; }
|
||||
public string EmailVendedor { get; set; } = string.Empty;
|
||||
|
||||
public string Marca_Detalle { get; set; } = string.Empty;
|
||||
public string Modelo_Detalle { get; set; } = string.Empty;
|
||||
public string Año_Detalle { get; set; } = string.Empty;
|
||||
|
||||
public decimal Moto_Precio { get; set; }
|
||||
public string Mone_Detalle { get; set; } = string.Empty;
|
||||
public int Moto_Kilometraje { get; set; }
|
||||
public string Descripcion { get; set; } = string.Empty;
|
||||
|
||||
public string Moto_Cilindrada { get; set; } = string.Empty;
|
||||
public int Moto_Tipo_Cuatri_Id { get; set; } // > 0 es Cuatriciclo
|
||||
|
||||
public string Part_Telefono { get; set; } = string.Empty;
|
||||
public string Part_Celular { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class LegacyMotoPhoto
|
||||
{
|
||||
public int Foto_Id { get; set; }
|
||||
public int Foto_Moto_Id { get; set; }
|
||||
public string Foto_Ruta { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
211
Backend/MotoresArgentinosV2.MigrationTool/Program.cs
Normal file
211
Backend/MotoresArgentinosV2.MigrationTool/Program.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Dapper;
|
||||
|
||||
namespace MotoresArgentinosV2.MigrationTool;
|
||||
|
||||
class Program
|
||||
{
|
||||
// --- CONFIGURACIÓN ---
|
||||
public const string ConnectionStringAutos = "Server=localhost;Database=autos;Trusted_Connection=True;TrustServerCertificate=True;";
|
||||
public const string ConnectionStringV2 = "Server=localhost;Database=MotoresV2;Trusted_Connection=True;TrustServerCertificate=True;";
|
||||
|
||||
// Fecha de corte para usuarios inactivos
|
||||
static readonly DateTime FechaCorte = new DateTime(2021, 01, 01);
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.Clear();
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("🚀 HERRAMIENTA DE MIGRACIÓN MOTORES V2");
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("1. Migrar USUARIOS (Desde 2021)");
|
||||
Console.WriteLine("2. Migrar AVISOS DE AUTOS (Activos)");
|
||||
Console.WriteLine("3. Migrar AVISOS DE MOTOS (Activos)");
|
||||
Console.WriteLine("4. Salir");
|
||||
Console.Write("\nSeleccione una opción: ");
|
||||
|
||||
var key = Console.ReadLine();
|
||||
|
||||
if (key == "1")
|
||||
{
|
||||
await RunUserMigration();
|
||||
Console.WriteLine("\nPresione Enter para volver...");
|
||||
Console.ReadLine();
|
||||
}
|
||||
else if (key == "2")
|
||||
{
|
||||
var adMigrator = new AdMigrator(ConnectionStringAutos, ConnectionStringV2);
|
||||
await adMigrator.ExecuteAsync();
|
||||
Console.WriteLine("\nPresione Enter para volver...");
|
||||
Console.ReadLine();
|
||||
}
|
||||
else if (key == "3")
|
||||
{
|
||||
var adMigrator = new AdMigrator(ConnectionStringAutos, ConnectionStringV2);
|
||||
await adMigrator.MigrateMotosAsync();
|
||||
Console.WriteLine("\nPresione Enter para volver...");
|
||||
Console.ReadLine();
|
||||
}
|
||||
else if (key == "4")
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- LÓGICA DE MIGRACIÓN DE USUARIOS (Encapsulada) ---
|
||||
static async Task RunUserMigration()
|
||||
{
|
||||
Console.WriteLine("\n🔍 Iniciando migración de usuarios...");
|
||||
|
||||
try
|
||||
{
|
||||
using var dbAutos = new SqlConnection(ConnectionStringAutos);
|
||||
using var dbV2 = new SqlConnection(ConnectionStringV2);
|
||||
|
||||
var queryLegacy = @"
|
||||
SELECT
|
||||
u.UserName,
|
||||
m.Email,
|
||||
m.Password as PasswordHash,
|
||||
m.PasswordSalt,
|
||||
u.LastActivityDate,
|
||||
1 as UserType
|
||||
FROM aspnet_Users u
|
||||
INNER JOIN aspnet_Membership m ON u.UserId = m.UserId
|
||||
WHERE u.LastActivityDate >= @FechaCorte";
|
||||
|
||||
var usersLegacy = (await dbAutos.QueryAsync<UserLegacy>(queryLegacy, new { FechaCorte })).ToList();
|
||||
|
||||
Console.WriteLine($"✅ Encontrados {usersLegacy.Count} usuarios activos para procesar.");
|
||||
|
||||
var processedUsernames = new HashSet<string>();
|
||||
int migrados = 0;
|
||||
int saltados = 0;
|
||||
int passReset = 0;
|
||||
|
||||
foreach (var user in usersLegacy)
|
||||
{
|
||||
// A. NORMALIZACIÓN
|
||||
string cleanUsername = NormalizeUsername(user.UserName);
|
||||
if (cleanUsername.Contains("@"))
|
||||
{
|
||||
cleanUsername = cleanUsername.Split('@')[0];
|
||||
cleanUsername = NormalizeUsername(cleanUsername);
|
||||
}
|
||||
|
||||
string finalUsername = cleanUsername;
|
||||
int counter = 1;
|
||||
|
||||
while (processedUsernames.Contains(finalUsername) || await UserExistsInDb(dbV2, finalUsername))
|
||||
{
|
||||
finalUsername = $"{cleanUsername}{counter}";
|
||||
counter++;
|
||||
}
|
||||
|
||||
processedUsernames.Add(finalUsername);
|
||||
|
||||
// B. LÓGICA DE CONTRASEÑAS
|
||||
bool isPlainText = user.PasswordHash.Length < 20;
|
||||
string finalHash = user.PasswordHash;
|
||||
string? finalSalt = user.PasswordSalt;
|
||||
byte migrationStatus = 0;
|
||||
|
||||
if (isPlainText)
|
||||
{
|
||||
finalHash = "INVALID_PASSWORD_NEEDS_RESET";
|
||||
finalSalt = null;
|
||||
passReset++;
|
||||
}
|
||||
|
||||
// C. INSERCIÓN
|
||||
var existeEmail = await dbV2.ExecuteScalarAsync<int>(
|
||||
"SELECT COUNT(1) FROM Users WHERE Email = @Email", new { user.Email });
|
||||
|
||||
if (existeEmail == 0)
|
||||
{
|
||||
var insertQuery = @"
|
||||
INSERT INTO Users (
|
||||
UserName, Email, PasswordHash, PasswordSalt, MigrationStatus,
|
||||
UserType, CreatedAt, IsMFAEnabled, FirstName, LastName
|
||||
)
|
||||
VALUES (
|
||||
@UserName, @Email, @PasswordHash, @PasswordSalt, @MigrationStatus,
|
||||
@UserType, @CreatedAt, 0, NULL, NULL
|
||||
)";
|
||||
|
||||
await dbV2.ExecuteAsync(insertQuery, new
|
||||
{
|
||||
UserName = finalUsername,
|
||||
user.Email,
|
||||
PasswordHash = finalHash,
|
||||
PasswordSalt = finalSalt,
|
||||
MigrationStatus = migrationStatus,
|
||||
user.UserType,
|
||||
CreatedAt = user.LastActivityDate
|
||||
});
|
||||
|
||||
migrados++;
|
||||
}
|
||||
else
|
||||
{
|
||||
saltados++;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"🏁 FIN: {migrados} migrados, {saltados} saltados (email duplicado).");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
static async Task<bool> UserExistsInDb(SqlConnection db, string username)
|
||||
{
|
||||
var count = await db.ExecuteScalarAsync<int>(
|
||||
"SELECT COUNT(1) FROM Users WHERE UserName = @username", new { username });
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
static string NormalizeUsername(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text)) return "usuario_desconocido";
|
||||
text = text.ToLowerInvariant();
|
||||
var normalizedString = text.Normalize(NormalizationForm.FormD);
|
||||
var stringBuilder = new StringBuilder();
|
||||
|
||||
foreach (var c in normalizedString)
|
||||
{
|
||||
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
|
||||
text = stringBuilder.ToString().Normalize(NormalizationForm.FormC);
|
||||
text = Regex.Replace(text, "[^a-z0-9]", "");
|
||||
if (string.IsNullOrEmpty(text)) return "usuario";
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public class UserLegacy
|
||||
{
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string PasswordHash { get; set; } = string.Empty;
|
||||
public string PasswordSalt { get; set; } = string.Empty;
|
||||
public DateTime LastActivityDate { get; set; }
|
||||
public int UserType { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user