Files
MotoresArgentinosV2/Backend/MotoresArgentinosV2.MigrationTool/AdMigrator.cs

386 lines
15 KiB
C#
Raw Normal View History

2026-01-29 13:43:44 -03:00
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;
}
}