const fs = require('fs'); const path = require('path'); const { Client, Databases, Storage, ID, Query } = require('node-appwrite'); const { InputFile } = require('node-appwrite/file'); require('dotenv').config({ path: '.env.local' }); const { applyTablesDbShim } = require('../src/lib/appwrite-tables-shim'); const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://nyc.cloud.appwrite.io/v1'; const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '6aef63d9002c07371b40'; const API_KEY = process.env.APPWRITE_API_KEY; const DB_ID = process.env.APPWRITE_DATABASE_ID || 'almeira'; const BUCKET_ID = process.env.NEXT_PUBLIC_APPWRITE_MEDIA_BUCKET_ID || 'media_bucket'; const NOTICIA_TYPE_ID = '69c2b91400362d5d52b6'; const WP_DATA_PATH = path.join(__dirname, 'wp_extracted_data.json'); const IMG_DIR = 'C:/tmp/wp_images'; const client = new Client().setEndpoint(ENDPOINT).setProject(PROJECT_ID).setKey(API_KEY); applyTablesDbShim(client); const databases = new Databases(client); const storage = new Storage(client); function safeFileKey(relPath) { return relPath.replace(/[^a-zA-Z0-9._-]/g, '_'); } function stripHtml(html) { if (!html) return ''; return html .replace(/<[^>]*>?/gm, ' ') .replace(/ /gi, ' ') .replace(/&/gi, '&') .replace(/"/gi, '"') .replace(/“|”/gi, '"') .replace(/‘|’/gi, "'") .replace(/–|—/gi, '-') .replace(/\s+/g, ' ') .trim(); } function formatWpContent(raw) { if (!raw) return '

'; let content = raw.trim(); if (!content.includes(' p.trim()) .filter(Boolean) .map((p) => `

${p.replace(/\r?\n/g, '
')}

`) .join('\n'); } return content.substring(0, 95000); } async function runPool(items, concurrency, fn) { let idx = 0; const workers = Array.from({ length: concurrency }, async () => { while (true) { const current = idx++; if (current >= items.length) break; await fn(items[current], current); } }); await Promise.all(workers); } async function run() { const wpData = JSON.parse(fs.readFileSync(WP_DATA_PATH, 'utf8')); console.log('==================================================================='); console.log('🚀 IMPORTACIÓN HISTÓRICA DE WORDPRESS A APPWRITE CLOUD'); console.log(`Categorías: ${wpData.categories.length} | Posts históricos: ${wpData.posts.length}`); console.log('===================================================================\n'); // 1. Cargar categorías existentes console.log('1️⃣ Sincronizando categorías de WordPress...'); const existingCatSlugs = new Map(); let cursor = null; while (true) { const q = [Query.limit(100)]; if (cursor) q.push(Query.cursorAfter(cursor)); const res = await databases.listDocuments(DB_ID, 'categories', q); for (const c of res.documents) { existingCatSlugs.set(c.slug, c.$id); } if (res.documents.length < 100) break; cursor = res.documents[res.documents.length - 1].$id; } let catsCreated = 0; await runPool(wpData.categories, 8, async (cat) => { const slug = (cat.slug || '').substring(0, 95); const nombre = stripHtml(cat.nombre || slug).substring(0, 95); if (!slug || existingCatSlugs.has(slug)) return; try { const doc = await databases.createDocument(DB_ID, 'categories', ID.unique(), { nombre, slug, }); existingCatSlugs.set(slug, doc.$id); catsCreated++; } catch (_) {} }); console.log(`✓ Categorías listas (nuevas creadas: ${catsCreated}, total: ${existingCatSlugs.size})\n`); // 2. Cargar slugs de posts existentes para evitar duplicados si se re-ejecuta console.log('2️⃣ Verificando posts existentes en Appwrite Cloud...'); const existingPostSlugs = new Set(); cursor = null; while (true) { const q = [Query.limit(100), Query.select(['$id', 'slug'])]; if (cursor) q.push(Query.cursorAfter(cursor)); const res = await databases.listDocuments(DB_ID, 'posts', q); for (const p of res.documents) { if (p.slug) existingPostSlugs.add(p.slug); } if (res.documents.length < 100) break; cursor = res.documents[res.documents.length - 1].$id; } console.log(`✓ Slugs ya existentes en Appwrite: ${existingPostSlugs.size}\n`); // 3. Ordenar posts del más reciente al más antiguo (2026 -> 2020) const sortedPosts = [...wpData.posts].sort((a, b) => new Date(b.date) - new Date(a.date)); const toImport = sortedPosts.filter((p) => p.slug && !existingPostSlugs.has(p.slug.substring(0, 250))); console.log(`3️⃣ Importando ${toImport.length} publicaciones históricas con sus imágenes y links originales...\n`); const mediaCache = new Map(); // imageRelPath -> Promise let importedPosts = 0; let uploadedImages = 0; let failedPosts = 0; async function getOrUploadMedia(post) { if (!post.imageRelPath) return null; const cleanRel = post.imageRelPath.replace(/^\/+/, ''); if (mediaCache.has(cleanRel)) { return mediaCache.get(cleanRel); } const promise = (async () => { const localImgPath = path.join(IMG_DIR, safeFileKey(cleanRel) + '.jpg'); if (!fs.existsSync(localImgPath)) return null; try { const file = await storage.createFile( BUCKET_ID, ID.unique(), InputFile.fromPath(localImgPath, path.posix.basename(cleanRel).replace(/[^a-zA-Z0-9._-]/g, '_') || 'wp_image.jpg') ); const tituloMedia = stripHtml(post.imageTitle || post.title || 'Imagen WordPress').substring(0, 250) || 'Imagen'; const altMedia = stripHtml(post.imageAlt || post.title || '').substring(0, 490); const mediaDoc = await databases.createDocument(DB_ID, 'media', ID.unique(), { file_id: file.$id, bucket_id: BUCKET_ID, titulo: tituloMedia, alt_text: altMedia, tipo: 'imagen', }); uploadedImages++; return mediaDoc.$id; } catch (err) { return null; } })(); mediaCache.set(cleanRel, promise); return promise; } await runPool(toImport, 6, async (post, i) => { try { const mediaDocId = await getOrUploadMedia(post); const cleanTitle = stripHtml(post.title || 'Sin título').substring(0, 250) || 'Sin título'; const plainExcerpt = stripHtml(post.excerpt || post.content || cleanTitle); const resumen = plainExcerpt.substring(0, 480) || cleanTitle; const seoDesc = plainExcerpt.substring(0, 280) || cleanTitle; const slug = post.slug.substring(0, 250); let isoDate; try { isoDate = new Date(post.date.replace(' ', 'T') + 'Z').toISOString(); } catch (_) { isoDate = new Date().toISOString(); } const data = { titulo: cleanTitle, slug, tipo: 'noticia', tipo_publicacion_id: NOTICIA_TYPE_ID, estado: 'publicado', fecha_publicacion: isoDate, contenido: formatWpContent(post.content), resumen_redes: resumen, seo_titulo: cleanTitle.substring(0, 115), seo_descripcion: seoDesc, ...(mediaDocId ? { imagen_destacada_id: mediaDocId } : {}), }; await databases.createDocument(DB_ID, 'posts', ID.unique(), data); importedPosts++; if (importedPosts % 50 === 0 || importedPosts === toImport.length) { console.log( `[Progreso WP] ${importedPosts}/${toImport.length} posts publicados | Imágenes subidas: ${uploadedImages} | Última fecha: ${post.date.slice(0, 10)} (${slug.slice(0, 45)})` ); } } catch (err) { failedPosts++; if (failedPosts <= 20) { console.error(`! Error en post WP ${post.id} (${post.slug}):`, err.message); } } }); console.log('\n==================================================================='); console.log(`🎉 ¡MIGRACIÓN HISTÓRICA DE WORDPRESS COMPLETADA!`); console.log(` - Publicaciones importadas: ${importedPosts}`); console.log(` - Imágenes destacadas subidas: ${uploadedImages}`); console.log(` - Fallos: ${failedPosts}`); console.log('==================================================================='); } run().catch((e) => { console.error('Fatal error:', e); process.exit(1); });