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);
});