const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://appwrite.almeira.com.co/v1') .setProject('69b84df5003bd8957c5c') .setKey('standard_cce3cdb866ea6433888d40cedd24dc45987372308fdc3dd0697de08648c0e5c7e04b02c3bcee271403fd656b00a5820f04c08b51631284a6e8a3582d8aed2aa33234f3c29cc777e9b162eb54810b789251b0d006a9a3b081ebc2da53b9baa94614b124b3508ad849fd9a4187cc6094f506d4b57f409b69d9562a74df594f3ec3'); const dbs = new sdk.Databases(client); const DB_ID = 'sneakers_multistore_db'; const COLLECTION_ID = 'products'; function cleanTitle(rawTitle, fallbackBrand = 'Sneakers') { if (!rawTitle || typeof rawTitle !== 'string') { return fallbackBrand && fallbackBrand !== 'Urbano / Variado' && fallbackBrand !== 'Desconocida' ? `${fallbackBrand} Urban Edition` : 'Sneakers Urban Classic'; } // 0. Normalizar fuentes estilizadas (Unicode Mathematical / Bold fonts como 𝐍𝐈𝐊𝐄 𝐒𝐁 𝐏𝐋𝐔𝐌𝐀) let cleaned = rawTitle.normalize('NFKD').trim(); // 1. Quitar emojis y pictogramas de todo tipo cleaned = cleaned .replace(/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{27BF}]|[\u{2300}-\u{23FF}]|[\u{200D}\u{FE0F}]|\*/gu, ' ') .replace(/[🔥🔴‼️✨🌸👑💎🛍️🌟💸💰💵💲⭐🌹⚡💥👖👩🏻👫👻☀️🔝🙋🏻‍♀️😍🎩💕⏰🌎]/g, ' '); // 2. Quitar códigos de bodega/proveedor: "Cod: 734-732 Bbo", "Cod: 2236", "Cod: 668/833 Bbo" cleaned = cleaned.replace(/\bcod(?:igo)?\s*[:\-–—]?\s*[\d\s\-\/]+(?:\s*bbo)?\b/gi, ' '); // 3. Quitar paréntesis o corchetes con tallas o números cleaned = cleaned .replace(/\(\s*(?:tallas?|euro|eur|talla|disponibles?|del)?[^\)]*\b(?:3[2-9]|4[0-9]|5[0-6])\b[^\)]*\)/gi, ' ') .replace(/\([^\)]*\b(?:euro|eur|talla|tallas|mil)\b[^\)]*\)/gi, ' ') .replace(/\(\s*\)|\[\s*\]/g, ' '); // 4. Quitar rangos y expresiones de tallas cleaned = cleaned.replace(/\bdel\s+(?:3[0-9]|4[0-9])\s+al\s+(?:3[0-9]|4[0-9])(?:\s*(?:eur|eu|euro))?\b/gi, ' '); cleaned = cleaned.replace(/\b(?:euro|eur|eu)\s*[:\-–—\/\s]+(?:3[0-9]|4[0-9])(?:\s*(?:-|–|—|a|al|\/)\s*(?:3[0-9]|4[0-9]))*/gi, ' '); cleaned = cleaned.replace(/\b(?:3[0-9]|4[0-9])\s+al\s+(?:3[0-9]|4[0-9])(?:\s*(?:eur|eu|euro))\b/gi, ' '); cleaned = cleaned.replace(/\b(?:3[2-9]|4[0-9]|5[0-6])\s*(?:&|y|\/|\-)\s*(?:hombre\s+)?(?:3[2-9]|4[0-9]|5[0-6])(?:\s*(?:eur|eu|euro))?\b/gi, ' '); cleaned = cleaned.replace(/\b(?:3[2-9]|4[0-9]|5[0-6])(?:\s*[-–—\/]\s*(?:3[2-9]|4[0-9]|5[0-6]))+\b/gi, ' '); cleaned = cleaned.replace(/\b(?:talla|tallas|eur|euro|eu)\s+(?:3[2-9]|4[0-8])\b/gi, ' '); cleaned = cleaned.replace(/\bhasta\s+(?:talla\s+)?(?:3[2-9]|4[0-9]|5[0-6])(?:\s*(?:eur|eu|euro))?\b/gi, ' '); cleaned = cleaned.replace(/\b(?:solo\s+)?tallas?\s*(?:[:\-–—\s]+(?:hasta|desde|de|disponibles?|al\s+\d+))?\b/gi, ' '); cleaned = cleaned.replace(/\b(?:al|a)\s+(?:3[2-9]|4[0-8])\b/gi, ' '); cleaned = cleaned.replace(/\b(?:hasta|desde|tallas?|talla|disponibles?)\b/gi, ' '); cleaned = cleaned.replace(/\(\s*(?:vienen?|viene)?\s*reducid[oa]s?\s*\)/gi, ' '); // 5. Quitar '.000' residual y precios monetarios cleaned = cleaned .replace(/(?:^|[\s\(\[\.,\-_])(?:[1-9]\d{0,2})?\.000\b/gi, ' ') .replace(/\b\d{2,3}[\.,]\d{3}\b|\b\d{2,3}k\b|\b\d{2,3}\s*mil\b|\$\s*\d+/gi, ' ') .replace(/\.000\b/gi, ' '); // 6. Quitar palabras residuales cleaned = cleaned .replace(/dama\s*y\s*caballero|recarga|nueva\s*mercanc[ií]a|[uú]ltima\s*unidad/gi, ' ') .replace(/\b(costo|precio|valor|al\s*por\s*mayor|mayorista)\b/gi, ' ') .replace(/\b(hombre|dama|caballero)\b/gi, ' ') .replace(/\b(eur|euro|eu|mil)\b/gi, ' '); // 7. Limpiar símbolos residuales y ampersands al inicio y final cleaned = cleaned .replace(/\s+&\s*$/g, '') .replace(/^\s*&\s+/g, '') .replace(/^[#*_\-•—–~:;,\s\.\/&]+|[#*_\-•—–~:;,\s\.\/&]+$/g, '') .replace(/\s+/g, ' ') .trim(); // 8. Fallback si quedó genérico o vacío const cleanLower = cleaned.toLowerCase(); const isGeneric = cleaned.length < 3 || /^[\d\s\-.,/&]+$/.test(cleaned) || ['euro', 'eur', 'zapatillas', 'sneakers', 'calzado', 'estilo', 'moda', 'solo', 'sin producto'].includes(cleanLower) || cleanLower.startsWith('solo ') || cleanLower.startsWith('euro ') || cleanLower.startsWith('sin producto'); if (isGeneric) { return fallbackBrand && fallbackBrand !== 'Urbano / Variado' && fallbackBrand !== 'Desconocida' && fallbackBrand !== 'Generica' ? `${fallbackBrand} Urban Edition` : 'Sneakers Urban Classic'; } // 9. Capital Case limpio conservando siglas comerciales return cleaned .split(' ') .filter(Boolean) .map((w) => { if (/^(OG|SE|SB|TN|CL|GT|RS-X|BB|LV|NB|XT-6|CR7|V2K|V5K|9060|550|530|1906R|2002R)$/i.test(w)) { return w.toUpperCase(); } if (/^[A-Z0-9]+$/i.test(w) && w.length <= 4 && !/[aeiou]/i.test(w)) { return w.toUpperCase(); } return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase(); }) .join(' '); } async function migrate() { console.log('🚀 Iniciando migración y saneamiento profundo de títulos en Appwrite...'); let allDocs = []; let cursor = null; while (true) { const queries = [sdk.Query.limit(100)]; if (cursor) { queries.push(sdk.Query.cursorAfter(cursor)); } const res = await dbs.listDocuments(DB_ID, COLLECTION_ID, queries); const docs = res.documents || []; if (docs.length === 0) break; allDocs.push(...docs); cursor = docs[docs.length - 1].$id; if (docs.length < 100) break; } console.log(`📦 Total documentos recuperados de Appwrite: ${allDocs.length}`); let updatedCount = 0; for (const doc of allDocs) { const oldName = doc.name || ''; const oldModel = doc.modelName || ''; const brand = doc.brand || 'Sneakers'; const newName = cleanTitle(oldName, brand); const newModel = cleanTitle(oldModel || oldName, brand); if (oldName !== newName || oldModel !== newModel) { try { await dbs.updateDocument(DB_ID, COLLECTION_ID, doc.$id, { name: newName, modelName: newModel, }); console.log(`✅ [FIXED] ${doc.$id}:`); console.log(` OLD: "${oldName}"`); console.log(` NEW: "${newName}"`); updatedCount++; } catch (err) { console.error(`❌ [ERROR] ${doc.$id}:`, err.message); } } } console.log(`\n🎉 Migración finalizada con éxito: ${updatedCount} productos saneados en Appwrite.`); } migrate();