386 lines
15 KiB
C#
386 lines
15 KiB
C#
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;
|
||
}
|
||
} |