const sdk = require('node-appwrite'); const axios = require('axios'); require('dotenv').config({ path: '.env.local' }); const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://nyc.cloud.appwrite.io/v1'; const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID; 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'; if (!PROJECT_ID || !API_KEY) { console.error('❌ Faltan NEXT_PUBLIC_APPWRITE_PROJECT_ID o APPWRITE_API_KEY en .env.local'); process.exit(1); } const client = new sdk.Client() .setEndpoint(ENDPOINT) .setProject(PROJECT_ID) .setKey(API_KEY); const databases = new sdk.Databases(client); const storage = new sdk.Storage(client); const api = axios.create({ baseURL: ENDPOINT, headers: { 'X-Appwrite-Project': PROJECT_ID, 'X-Appwrite-Key': API_KEY, 'Content-Type': 'application/json', }, }); const delay = (ms) => new Promise((res) => setTimeout(res, ms)); const PUBLIC_READ_WRITE = [ sdk.Permission.read(sdk.Role.any()), sdk.Permission.create(sdk.Role.any()), sdk.Permission.update(sdk.Role.any()), sdk.Permission.delete(sdk.Role.any()), ]; async function ensureCollection(colId, name, permissions = PUBLIC_READ_WRITE) { try { await api.get(`/tablesdb/${DB_ID}/tables/${colId}`); console.log(` ✓ Tabla/Colección '${colId}' ya existe.`); } catch (e) { if (e.response?.status === 404) { try { await api.post(`/tablesdb/${DB_ID}/tables`, { tableId: colId, name, permissions, rowSecurity: false, enabled: true, }); console.log(` + Tabla/Colección '${colId}' (${name}) creada.`); await delay(300); } catch (err) { console.error(` ! Error creando '${colId}':`, err.response?.data?.message || err.message); } } else { console.error(` ! Error verificando '${colId}':`, e.response?.data?.message || e.message); } } } async function ensureAttr(colId, type, key, opts = {}) { try { const payload = { key, required: opts.required || false, default: opts.default !== undefined ? opts.default : null, array: opts.array || false, }; let routeType = type; if (type === 'string') { payload.size = opts.size || 255; } else if (type === 'integer' || type === 'float') { if (opts.min !== undefined) payload.min = opts.min; if (opts.max !== undefined) payload.max = opts.max; } await api.post(`/tablesdb/${DB_ID}/tables/${colId}/columns/${routeType}`, payload); console.log(` + Columna ${colId}.${key} (${type})`); await delay(200); } catch (e) { if (e.response?.status !== 409) { console.warn(` ! Aviso en ${colId}.${key}:`, e.response?.data?.message || e.message); } } } async function ensureDoc(colId, docId, data) { try { await api.post(`/tablesdb/${DB_ID}/tables/${colId}/rows`, { rowId: docId, data, }); console.log(` + Registro sembrado: ${colId}/${docId}`); } catch (e) { if (e.response?.status !== 409) { console.warn(` ! Aviso sembrando ${colId}/${docId}:`, e.response?.data?.message || e.message); } } } async function run() { console.log('==================================================================='); console.log('🚀 RECONSTRUCCIÓN INTEGRAL DE ALMEIRA PRODUCCIONES EN APPWRITE'); console.log(`Endpoint: ${ENDPOINT} | Project: ${PROJECT_ID}`); console.log('===================================================================\n'); // 1. Crear Base de Datos try { await databases.get(DB_ID); console.log(`✓ Base de datos '${DB_ID}' ya existe.`); } catch (e) { if (e.code === 404) { await databases.create(DB_ID, 'Almeira Producciones'); console.log(`+ Base de datos '${DB_ID}' creada exitosamente.`); } else { throw e; } } // 2. Crear Bucket de Medios try { await storage.getBucket(BUCKET_ID); console.log(`✓ Bucket '${BUCKET_ID}' ya existe.`); } catch (e) { if (e.code === 404) { await storage.createBucket( BUCKET_ID, 'Biblioteca de Medios', PUBLIC_READ_WRITE, false, true, 50000000, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'mp4', 'webm', 'mov', 'jfif'] ); console.log(`+ Bucket '${BUCKET_ID}' creado exitosamente.`); } else { console.warn(`! Aviso en Bucket '${BUCKET_ID}':`, e.message); } } // 3. Crear todas las colecciones y atributos console.log('\n--- Configurando Colecciones y Atributos ---'); // posts await ensureCollection('posts', 'Publicaciones'); await ensureAttr('posts', 'string', 'titulo', { size: 255, required: true }); await ensureAttr('posts', 'string', 'contenido', { size: 50000, required: true }); await ensureAttr('posts', 'string', 'resumen_redes', { size: 1000, required: false }); await ensureAttr('posts', 'string', 'tipo', { size: 50, required: false, default: 'noticia' }); await ensureAttr('posts', 'string', 'tipo_publicacion_id', { size: 255, required: false }); await ensureAttr('posts', 'string', 'estado', { size: 50, required: true }); await ensureAttr('posts', 'string', 'imagen_destacada_id', { size: 255, required: false }); await ensureAttr('posts', 'string', 'video_url', { size: 2048, required: false }); await ensureAttr('posts', 'string', 'cliente_id', { size: 255, required: false }); await ensureAttr('posts', 'datetime', 'fecha_publicacion', { required: true }); await ensureAttr('posts', 'datetime', 'fecha_finalizacion', { required: false }); await ensureAttr('posts', 'string', 'url_destino', { size: 2048, required: false }); await ensureAttr('posts', 'string', 'slug', { size: 255, required: true }); await ensureAttr('posts', 'string', 'seo_titulo', { size: 120, required: false }); await ensureAttr('posts', 'string', 'seo_descripcion', { size: 300, required: false }); await ensureAttr('posts', 'string', 'seo_keywords', { size: 500, required: false }); await ensureAttr('posts', 'string', 'og_image_url', { size: 2048, required: false }); // categories & posts_categories await ensureCollection('categories', 'Categorías'); await ensureAttr('categories', 'string', 'nombre', { size: 100, required: true }); await ensureAttr('categories', 'string', 'slug', { size: 100, required: true }); await ensureCollection('posts_categories', 'Publicaciones Categorías'); await ensureAttr('posts_categories', 'string', 'post_id', { size: 255, required: true }); await ensureAttr('posts_categories', 'string', 'categoria_id', { size: 255, required: true }); // publication_types await ensureCollection('publication_types', 'Tipos de Publicación'); await ensureAttr('publication_types', 'string', 'codigo', { size: 50, required: true }); await ensureAttr('publication_types', 'string', 'descripcion', { size: 255, required: true }); // client_types await ensureCollection('client_types', 'Tipos de Cliente'); await ensureAttr('client_types', 'string', 'codigo', { size: 50, required: true }); await ensureAttr('client_types', 'string', 'descripcion', { size: 100, required: true }); // clients await ensureCollection('clients', 'Clientes'); await ensureAttr('clients', 'string', 'nombre_entidad', { size: 255, required: true }); await ensureAttr('clients', 'string', 'contacto_nombre', { size: 255, required: true }); await ensureAttr('clients', 'string', 'numero_whatsapp', { size: 50, required: true }); await ensureAttr('clients', 'boolean', 'estado', { required: true }); await ensureAttr('clients', 'string', 'slug', { size: 255, required: true }); await ensureAttr('clients', 'string', 'seo_descripcion', { size: 255, required: false }); await ensureAttr('clients', 'string', 'nit', { size: 50, required: false }); await ensureAttr('clients', 'string', 'tipo_cliente_id', { size: 255, required: false }); await ensureAttr('clients', 'string', 'direccion', { size: 500, required: false }); await ensureAttr('clients', 'string', 'ciudad_id', { size: 255, required: false }); await ensureAttr('clients', 'string', 'correo_electronico', { size: 255, required: false }); await ensureAttr('clients', 'string', 'sitio_web', { size: 2048, required: false }); // countries, departments, cities await ensureCollection('countries', 'Países'); await ensureAttr('countries', 'string', 'codigo', { size: 10, required: true }); await ensureAttr('countries', 'string', 'descripcion', { size: 100, required: true }); await ensureCollection('departments', 'Departamentos'); await ensureAttr('departments', 'string', 'codigo', { size: 10, required: true }); await ensureAttr('departments', 'string', 'descripcion', { size: 100, required: true }); await ensureAttr('departments', 'string', 'pais_id', { size: 255, required: true }); await ensureCollection('cities', 'Ciudades'); await ensureAttr('cities', 'string', 'codigo', { size: 10, required: true }); await ensureAttr('cities', 'string', 'descripcion', { size: 100, required: true }); await ensureAttr('cities', 'string', 'departamento_id', { size: 255, required: true }); // payment_methods, contracts, contract_details await ensureCollection('payment_methods', 'Formas de Pago'); await ensureAttr('payment_methods', 'string', 'codigo', { size: 50, required: true }); await ensureAttr('payment_methods', 'string', 'descripcion', { size: 255, required: true }); await ensureAttr('payment_methods', 'integer', 'numero_dias', { required: true }); await ensureCollection('contracts', 'Contratos'); await ensureAttr('contracts', 'string', 'numero_contrato', { size: 100, required: true }); await ensureAttr('contracts', 'string', 'eslogan', { size: 255, required: false }); await ensureAttr('contracts', 'string', 'estado', { size: 50, required: true }); await ensureAttr('contracts', 'string', 'cliente_id', { size: 255, required: true }); await ensureAttr('contracts', 'string', 'descripcion', { size: 2000, required: true }); await ensureAttr('contracts', 'datetime', 'fecha_inicial', { required: true }); await ensureAttr('contracts', 'datetime', 'fecha_final', { required: true }); await ensureAttr('contracts', 'float', 'valor_contrato', { required: true }); await ensureAttr('contracts', 'string', 'forma_pago_id', { size: 255, required: true }); await ensureCollection('contract_details', 'Detalles de Contrato'); await ensureAttr('contract_details', 'string', 'contrato_id', { size: 255, required: true }); await ensureAttr('contract_details', 'string', 'tipo_publicacion_id', { size: 255, required: true }); await ensureAttr('contract_details', 'string', 'cantidad_periodo', { size: 50, required: true }); await ensureAttr('contract_details', 'integer', 'cantidad_total', { required: true }); // media await ensureCollection('media', 'Medios y SEO'); await ensureAttr('media', 'string', 'file_id', { size: 255, required: true }); await ensureAttr('media', 'string', 'bucket_id', { size: 255, required: true }); await ensureAttr('media', 'string', 'titulo', { size: 255, required: true }); await ensureAttr('media', 'string', 'alt_text', { size: 500, required: false }); await ensureAttr('media', 'string', 'descripcion_seo', { size: 2000, required: false }); await ensureAttr('media', 'string', 'tipo', { size: 50, required: true }); // settings & entidad await ensureCollection('settings', 'Configuración del Medio'); await ensureAttr('settings', 'string', 'radio_stream_url', { size: 2048, required: false }); await ensureAttr('settings', 'string', 'tv_stream_url', { size: 2048, required: false }); await ensureAttr('settings', 'string', 'whatsapp_recepcion', { size: 50, required: false }); await ensureAttr('settings', 'string', 'seo_site_name', { size: 255, required: false }); await ensureAttr('settings', 'string', 'seo_default_image', { size: 2048, required: false }); await ensureAttr('settings', 'integer', 'ticker_count', { required: false, default: 10 }); await ensureAttr('settings', 'integer', 'rotation_seconds', { required: false, default: 5 }); await ensureCollection('entidad', 'Configuración de Entidad'); await ensureAttr('entidad', 'string', 'logotipo', { size: 500, required: false }); await ensureAttr('entidad', 'string', 'razon_social', { size: 255, required: true }); await ensureAttr('entidad', 'string', 'direccion', { size: 255, required: false }); await ensureAttr('entidad', 'string', 'numero_contacto', { size: 50, required: false }); await ensureAttr('entidad', 'string', 'numero_whatsapp', { size: 50, required: false }); await ensureAttr('entidad', 'string', 'ciudad', { size: 100, required: false }); await ensureAttr('entidad', 'string', 'sitio_web', { size: 500, required: false }); await ensureAttr('entidad', 'string', 'email', { size: 255, required: false }); await ensureAttr('entidad', 'string', 'facebook_url', { size: 500, required: false }); await ensureAttr('entidad', 'string', 'youtube_url', { size: 500, required: false }); await ensureAttr('entidad', 'string', 'instagram_url', { size: 500, required: false }); await ensureAttr('entidad', 'string', 'x_url', { size: 500, required: false }); await ensureAttr('entidad', 'string', 'telegram_url', { size: 500, required: false }); await ensureAttr('entidad', 'boolean', 'mostrar_texto_logo', { required: false, default: true }); // metrics, programming_matrices, whatsapp_messages, whatsapp_chats, live_chat, stream_state await ensureCollection('metrics', 'Métricas'); await ensureAttr('metrics', 'string', 'publicacion_id', { size: 255, required: false }); await ensureAttr('metrics', 'string', 'post_id', { size: 255, required: false }); await ensureAttr('metrics', 'string', 'plataforma', { size: 50, required: false }); await ensureAttr('metrics', 'string', 'tipo_metrica', { size: 50, required: false }); await ensureAttr('metrics', 'integer', 'vistas', { required: false }); await ensureAttr('metrics', 'integer', 'interacciones', { required: false }); await ensureAttr('metrics', 'datetime', 'timestamp', { required: false }); await ensureCollection('programming_matrices', 'Matrices de Programación'); await ensureAttr('programming_matrices', 'string', 'media_type', { size: 50, required: true }); await ensureAttr('programming_matrices', 'string', 'matrix_data', { size: 100000, required: true }); await ensureCollection('whatsapp_messages', 'Mensajes WhatsApp'); await ensureAttr('whatsapp_messages', 'string', 'sender_name', { size: 255, required: true }); await ensureAttr('whatsapp_messages', 'string', 'phone_number', { size: 50, required: true }); await ensureAttr('whatsapp_messages', 'string', 'content', { size: 5000, required: true }); await ensureAttr('whatsapp_messages', 'datetime', 'timestamp', { required: true }); await ensureAttr('whatsapp_messages', 'string', 'status', { size: 50, required: false, default: 'pending' }); await ensureAttr('whatsapp_messages', 'string', 'platform', { size: 50, required: false, default: 'whatsapp' }); await ensureAttr('whatsapp_messages', 'string', 'avatar', { size: 1000, required: false }); await ensureCollection('whatsapp_chats', 'Chats Autorizados WhatsApp'); await ensureAttr('whatsapp_chats', 'string', 'name', { size: 255, required: true }); await ensureAttr('whatsapp_chats', 'string', 'jid', { size: 255, required: false }); await ensureAttr('whatsapp_chats', 'boolean', 'enabled', { required: false, default: true }); await ensureAttr('whatsapp_chats', 'string', 'notes', { size: 1000, required: false }); await ensureCollection('live_chat', 'Live Chat'); await ensureAttr('live_chat', 'string', 'user_id', { size: 50, required: true }); await ensureAttr('live_chat', 'string', 'user_name', { size: 100, required: true }); await ensureAttr('live_chat', 'string', 'message', { size: 500, required: true }); await ensureAttr('live_chat', 'datetime', 'timestamp', { required: true }); await ensureCollection('stream_state', 'Stream State'); await ensureAttr('stream_state', 'string', 'active_overlays', { size: 50000, required: false }); await ensureCollection('entity_config', 'Configuración de Entidad'); for (const col of ['razon_social', 'logotipo', 'ciudad', 'direccion', 'numero_contacto', 'numero_whatsapp', 'email', 'sitio_web', 'tv_url', 'radio_url', 'facebook_url', 'instagram_url', 'x_url', 'youtube_url', 'telegram_url']) { await ensureAttr('entity_config', 'string', col, { size: 500, required: false }); } await ensureAttr('entity_config', 'boolean', 'mostrar_texto_logo', { required: false, default: true }); await ensureCollection('verification_tokens', 'Tokens de Verificación'); await ensureAttr('verification_tokens', 'string', 'user_id', { size: 255, required: true }); await ensureAttr('verification_tokens', 'string', 'otp_code', { size: 50, required: true }); await ensureAttr('verification_tokens', 'string', 'expires_at', { size: 100, required: true }); await ensureAttr('verification_tokens', 'string', 'pending_data', { size: 5000, required: true }); console.log('\n⏳ Esperando 4 segundos para que Appwrite indexe todas las columnas...'); await delay(4000); // 4. Sembrar Catálogos Base console.log('\n--- Sembrando Catálogos Base de Almeira Producciones ---'); const tiposPublicacion = [ { id: '69c2b91400362d5d52b6', codigo: 'NOTICIA', descripcion: 'Noticia' }, { id: 't_evento', codigo: 'EVENTO', descripcion: 'Evento' }, { id: 't_clasif', codigo: 'CLASIFICADO', descripcion: 'Clasificado' }, { id: 't_dbanner', codigo: 'DISPLAY_BANNER', descripcion: 'Display Banner' }, { id: 't_dslider', codigo: 'DISPLAY_SLIDER', descripcion: 'Display Slider' }, ]; for (const t of tiposPublicacion) { await ensureDoc('publication_types', t.id, { codigo: t.codigo, descripcion: t.descripcion }); } await ensureDoc('client_types', 'ct_institucional', { codigo: 'INSTITUCIONAL', descripcion: 'Institucional' }); await ensureDoc('client_types', 'ct_comercial', { codigo: 'COMERCIAL', descripcion: 'Comercial' }); await ensureDoc('countries', 'co_colombia', { codigo: 'CO', descripcion: 'Colombia' }); await ensureDoc('departments', 'dep_cauca', { codigo: '19', descripcion: 'Cauca', pais_id: 'co_colombia' }); await ensureDoc('cities', 'cit_popayan', { codigo: '19001', descripcion: 'Popayán', departamento_id: 'dep_cauca' }); await ensureDoc('cities', 'cit_quilichao', { codigo: '19698', descripcion: 'Santander de Quilichao', departamento_id: 'dep_cauca' }); await ensureDoc('payment_methods', 'pm_contado', { codigo: 'CONTADO', descripcion: 'Contado', numero_dias: 0 }); await ensureDoc('payment_methods', 'pm_credito30', { codigo: 'CREDITO30', descripcion: 'Crédito 30 Días', numero_dias: 30 }); await ensureDoc('clients', 'cli_gob_cauca', { nombre_entidad: 'Gobernación del Cauca', nit: '891580016-8', slug: 'gobernacion-del-cauca', tipo_cliente_id: 'ct_institucional', contacto_nombre: 'Oficina Asesora de Comunicaciones', numero_whatsapp: '573134848203', ciudad_id: 'cit_popayan', estado: true, sitio_web: 'https://www.cauca.gov.co', }); await ensureDoc('clients', 'cli_alcaldia_quilichao', { nombre_entidad: 'Alcaldía de Santander de Quilichao', slug: 'alcaldia-santander-de-quilichao', tipo_cliente_id: 'ct_institucional', contacto_nombre: 'Prensa Alcaldía', numero_whatsapp: '573167455877', ciudad_id: 'cit_quilichao', estado: true, }); await ensureDoc('entidad', 'default', { razon_social: 'Almeira Producciones', ciudad: 'Santander de Quilichao - Cauca', numero_whatsapp: '573167455877', sitio_web: 'https://almeiraproducciones.com', mostrar_texto_logo: true, }); await ensureDoc('entity_config', 'main_profile_singleton', { razon_social: 'Almeira Producciones', ciudad: 'Santander de Quilichao - Cauca', numero_contacto: '3167455877', numero_whatsapp: '573167455877', sitio_web: 'https://almeiraproducciones.com', mostrar_texto_logo: true, }); await ensureDoc('settings', 'global_config', { seo_site_name: 'Almeira Producciones', ticker_count: 10, rotation_seconds: 5, }); await ensureDoc('whatsapp_chats', 'wc_almeira', { name: 'Publicaciones Almeira', jid: '120363419329355281@g.us', enabled: true, notes: 'Grupo oficial de publicaciones de Almeira', }); await ensureDoc('whatsapp_chats', 'wc_gob_cauca', { name: 'Medios y Prensa Gob Cauca', jid: '573134848203-1408546044@g.us', enabled: true, notes: 'Boletines de prensa y noticias de la Gobernación del Cauca', }); await ensureDoc('programming_matrices', 'matrix_radio', { media_type: 'radio', matrix_data: '{}', }); await ensureDoc('programming_matrices', 'matrix_tv', { media_type: 'tv', matrix_data: '{}', }); console.log('\n✅ ¡Estructura y catálogos base de Almeira Producciones listos en Appwrite!'); } run().catch((e) => { console.error('❌ Error fatal:', e); process.exit(1); });