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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user