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(queryLegacy, new { FechaCorte })).ToList(); Console.WriteLine($"✅ Encontrados {usersLegacy.Count} usuarios activos para procesar."); var processedUsernames = new HashSet(); 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( "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 UserExistsInDb(SqlConnection db, string username) { var count = await db.ExecuteScalarAsync( "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; } }