diff --git a/almeira-web-deploy.zip b/almeira-web-deploy.zip index 77025b7..040ad2f 100644 Binary files a/almeira-web-deploy.zip and b/almeira-web-deploy.zip differ diff --git a/publish-scraper.js b/publish-scraper.js new file mode 100644 index 0000000..ee653b7 --- /dev/null +++ b/publish-scraper.js @@ -0,0 +1,135 @@ +const axios = require('axios'); +const fs = require('fs'); +const { Client, Databases, Storage, ID, Query } = require('node-appwrite'); +require('dotenv').config({ path: '.env.local' }); + +let InputFile; +try { InputFile = require('node-appwrite/file').InputFile; } catch (e) { InputFile = null; } + +const client = new Client() + .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT) + .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID) + .setKey(process.env.APPWRITE_API_KEY); + +const databases = new Databases(client); +const storage = new Storage(client); +const DB_ID = process.env.APPWRITE_DATABASE_ID; +const BUCKET_ID = process.env.NEXT_PUBLIC_APPWRITE_MEDIA_BUCKET_ID; + +const urls = [ + 'https://www.multivisiondecolombia.com/node/962', + 'https://www.multivisiondecolombia.com/node/963', + 'https://www.multivisiondecolombia.com/node/895', + 'https://www.multivisiondecolombia.com/node/894', + 'https://www.multivisiondecolombia.com/node/893', + 'https://www.multivisiondecolombia.com/node/964' +]; + +async function extractData(url) { + const res = await axios.get(url); + const html = res.data; + + let title = ''; + const titleMatch = html.match(/]*>(.*?)<\/h1>/i) || html.match(/(.*?)<\/title>/i); + if (titleMatch) title = titleMatch[1].replace(/\|.*/, '').trim(); + + let image = null; + const imgMatch = html.match(/<img[^>]+src="([^"]+)"[^>]*>/ig); + if (imgMatch) { + // Find the main image, usually in a field like field--name-field-image + for (const imgTag of imgMatch) { + const srcMatch = imgTag.match(/src="([^"]+)"/); + if (srcMatch && (srcMatch[1].includes('public/imagenes') || srcMatch[1].includes('styles/'))) { + image = srcMatch[1].startsWith('http') ? srcMatch[1] : 'https://www.multivisiondecolombia.com' + srcMatch[1]; + break; + } + } + } + + let date = new Date().toISOString(); + // Look for <time datetime="..."> or a date string + const timeMatch = html.match(/<time[^>]+datetime="([^"]+)"/); + if (timeMatch) date = new Date(timeMatch[1]).toISOString(); + + let content = ''; + // Look for the main content body, often in field--name-body or a specific div + const bodyMatch = html.match(/<div[^>]+property="schema:text"[^>]*>([\s\S]*?)<\/div>/i) || html.match(/<div[^>]+class="[^"]*field--name-body[^"]*"[^>]*>([\s\S]*?)<\/div>\s*<\/div>/i); + if (bodyMatch) { + content = bodyMatch[1].trim(); + } else { + // Fallback: extract p tags between article + const articleMatch = html.match(/<article[^>]*>([\s\S]*?)<\/article>/i); + if (articleMatch) { + const pTags = articleMatch[1].match(/<p>([\s\S]*?)<\/p>/ig); + if (pTags) content = pTags.join('\n'); + } + } + + // Strip out tags except p, b, i, strong, em, ul, li, ol + content = content.replace(/<(?!p|b|i|strong|em|ul|li|ol|\/p|\/b|\/i|\/strong|\/em|\/ul|\/li|\/ol)[^>]+>/ig, ''); + + return { title, image, date, content }; +} + +async function run() { + try { + const list = await databases.listCollections(DB_ID); + const map = {}; + list.collections.forEach(c => { map[c.name.toLowerCase()] = c.$id; }); + const postsCollectionId = map['posts'] || 'posts'; + const mediaCollectionId = map['media'] || 'media'; + const pubTypesCollectionId = map['publication types'] || map['publication_types'] || 'publication_types'; + + const typeRes = await databases.listDocuments(DB_ID, pubTypesCollectionId, [Query.equal('codigo', 'NOTICIA')]); + const typeId = typeRes.documents.length > 0 ? typeRes.documents[0].$id : null; + + for (let i = 0; i < urls.length; i++) { + const url = urls[i]; + console.log('Processing:', url); + const data = await extractData(url); + console.log('Extracted Title:', data.title); + console.log('Extracted Date:', data.date); + console.log('Extracted Image:', data.image); + + let mediaId = null; + if (data.image) { + try { + const imgRes = await axios.get(data.image, { responseType: 'arraybuffer' }); + const buffer = Buffer.from(imgRes.data, 'binary'); + const filePath = `/tmp/scraped_img_${i}.jpg`; + fs.writeFileSync(filePath, buffer); + + let fileParam; + if (InputFile && InputFile.fromPath) fileParam = InputFile.fromPath(filePath, `image${i}.jpg`); + else fileParam = fs.createReadStream(filePath); + + const file = await storage.createFile(BUCKET_ID, ID.unique(), fileParam); + const mediaDoc = await databases.createDocument(DB_ID, mediaCollectionId, ID.unique(), { + file_id: file.$id, bucket_id: BUCKET_ID, + titulo: data.title, tipo: 'imagen' + }); + mediaId = mediaDoc.$id; + } catch(err) { + console.error('Error uploading image:', err.message); + } + } + + const doc = { + titulo: data.title.substring(0, 100), + slug: `multivision-${Date.now()}-${i}`, + tipo_publicacion_id: typeId, + estado: 'Publicado', + fecha_publicacion: data.date, + contenido: data.content, + imagen_destacada_id: mediaId + }; + + const res = await databases.createDocument(DB_ID, postsCollectionId, ID.unique(), doc); + console.log('Published! ID:', res.$id); + console.log('---------------------------'); + } + } catch(e) { + console.error(e); + } +} +run(); diff --git a/publish-single.js b/publish-single.js new file mode 100644 index 0000000..50979ce --- /dev/null +++ b/publish-single.js @@ -0,0 +1,41 @@ +const { Client, Databases, ID, Query } = require('node-appwrite'); +require('dotenv').config({ path: '.env.local' }); + +const client = new Client() + .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT) + .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID) + .setKey(process.env.APPWRITE_API_KEY); + +const databases = new Databases(client); +const DB_ID = process.env.APPWRITE_DATABASE_ID; + +async function run() { + try { + const list = await databases.listCollections(DB_ID); + const map = {}; + list.collections.forEach(c => { map[c.name.toLowerCase()] = c.$id; }); + const postsCollectionId = map['posts'] || 'posts'; + const pubTypesCollectionId = map['publication types'] || map['publication_types'] || 'publication_types'; + + const typeRes = await databases.listDocuments(DB_ID, pubTypesCollectionId, [Query.equal('codigo', 'NOTICIA')]); + const typeId = typeRes.documents.length > 0 ? typeRes.documents[0].$id : null; + + const bodyHtml = `<p>En esta Semana Mayor, el departamento se prepara para una de las temporadas turísticas y culturales más importantes del año. Para potenciar la economía local, la Gobernación del Cauca lanzó oficialmente la Ruta "Cauca Lo Es Todo", una estrategia que reúne 5 ferias comerciales diseñadas para enamorar a propios y visitantes.</p><p>Estas vitrinas comerciales, distribuidas estratégicamente, serán el escenario perfecto para disfrutar de la tradición, la gastronomía y las artesanías de la región. Es una apuesta directa por el turismo sostenible, donde el talento de los emprendedores de las diferentes subregiones se convierte en el protagonista.</p><p>La administración departamental invita a la comunidad a recorrer estas ferias, apoyar los negocios locales y descubrir por qué en esta Semana Santa, el #CaucaLoEsTodo.</p><p><em>Etiquetas: #CaucaLoEsTodo #SemanaSanta #Emprendimiento #Cultura #Tradición #GobernaciónDelCauca</em></p>`; + + const doc = { + titulo: "🌅 Semana Santa con sello local: La ruta 'Cauca Lo Es Todo' activa 5 ferias comerciales", + slug: 'ruta-cauca-lo-es-todo-ferias', + tipo_publicacion_id: typeId, + estado: 'Publicado', + fecha_publicacion: new Date('2026-03-30T10:00:00.000Z').toISOString(), + contenido: bodyHtml, + resumen_redes: 'La Gobernación invita a recorrer el departamento durante la Semana Mayor a través de 5 vitrinas comerciales que resaltan el talento de nuestros emprendedores.', + }; + + const res = await databases.createDocument(DB_ID, postsCollectionId, ID.unique(), doc); + console.log('Publicación exitosa! ID:', res.$id); + } catch(e) { + console.error(e); + } +} +run(); diff --git a/src/components/publications/PostCard.tsx b/src/components/publications/PostCard.tsx index 702b0b0..27baaf1 100644 --- a/src/components/publications/PostCard.tsx +++ b/src/components/publications/PostCard.tsx @@ -39,8 +39,8 @@ function resolveImageUrl( return `${endpoint}/storage/buckets/${bucket_id}/files/${file_id}/preview?project=${projectId}&width=600&height=400&quality=80`; } - // Fallback: just an ID, use default 'media' bucket - return `${endpoint}/storage/buckets/media/files/${post.imagen_destacada_id}/preview?project=${projectId}&width=600&height=400&quality=80`; + // If it's a string, we can't generate a valid URL because it's a document ID, not a file ID. + return null; } /** Maps PostStatus to a visual badge style */ @@ -63,6 +63,7 @@ export const PostCard: React.FC<PostCardProps> = ({ appwriteProjectId, globalSettings, }) => { + const [imageError, setImageError] = React.useState(false); const imageUrl = resolveImageUrl(post, appwriteEndpoint, appwriteProjectId); // Safe text rendering — no raw HTML @@ -134,11 +135,12 @@ export const PostCard: React.FC<PostCardProps> = ({ )} {/* Imagen Destacada */} <div className="relative aspect-[16/9] bg-slate-800 overflow-hidden"> - {imageUrl ? ( + {imageUrl && !imageError ? ( // eslint-disable-next-line @next/next/no-img-element <img src={imageUrl} alt={safeTitle} + onError={() => setImageError(true)} className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" /> ) : ( diff --git a/test-appwrite.js b/test-appwrite.js new file mode 100644 index 0000000..920c5f7 --- /dev/null +++ b/test-appwrite.js @@ -0,0 +1,23 @@ +const { Client, Databases } = require('node-appwrite'); +require('dotenv').config({ path: '.env.local' }); + +const client = new Client() + .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT) + .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID) + .setKey(process.env.APPWRITE_API_KEY); + +const databases = new Databases(client); + +async function run() { + try { + const result = await databases.listDocuments( + process.env.APPWRITE_DATABASE_ID, + 'whatsapp_messages' + ); + console.log(`Found ${result.documents.length} messages.`); + console.log(JSON.stringify(result.documents, null, 2)); + } catch (error) { + console.error(error); + } +} +run(); diff --git a/test-evolution.js b/test-evolution.js new file mode 100644 index 0000000..4def4ac --- /dev/null +++ b/test-evolution.js @@ -0,0 +1,30 @@ +const axios = require('axios'); +const fs = require('fs'); + +const EVOLUTION_API_URL = 'https://evolution.almeira.com.co'; +const EVOLUTION_API_KEY = 'mgXxu2eAwyhWNnptFEouUE6qLkvwVm6K'; +const EVOLUTION_INSTANCE_NAME = 'AutomataAlmeira'; +const CHAT_ID = '573134848203-1408546044@g.us'; + +async function testGetBase64() { + try { + const response = await axios.post( + `${EVOLUTION_API_URL}/chat/findMessages/${EVOLUTION_INSTANCE_NAME}?page=1&limit=5`, + { where: { "key": { "remoteJid": CHAT_ID } } }, + { headers: { 'apikey': EVOLUTION_API_KEY, 'Content-Type': 'application/json' } } + ); + const msg = response.data.messages.records[0]; // Assuming it's the imageMessage we saw + + const base64Res = await axios.post( + `${EVOLUTION_API_URL}/chat/getBase64FromMediaMessage/${EVOLUTION_INSTANCE_NAME}`, + { message: msg }, + { headers: { 'apikey': EVOLUTION_API_KEY, 'Content-Type': 'application/json' } } + ); + + console.log("Success getting base64. Base64 length:", base64Res.data.base64 ? base64Res.data.base64.length : 'none'); + } catch (e) { + console.error(e.response ? e.response.data : e.message); + } +} + +testGetBase64();