From c6b825931fb9d4e31d9192a24f451585dfdd5d26 Mon Sep 17 00:00:00 2001 From: AI Bot Date: Mon, 24 Aug 2026 12:36:05 -0500 Subject: [PATCH] feat(media): registrar imagenes en biblioteca de medios de Appwrite e insertar como imagen destacada con resolucion 640x360 y 640x640 --- src/app/[slug]/page.tsx | 32 ++++++----- src/app/api/webhooks/whatsapp/route.ts | 73 ++++++++++++++++++++------ 2 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx index d20e817..be37a97 100644 --- a/src/app/[slug]/page.tsx +++ b/src/app/[slug]/page.tsx @@ -2,7 +2,7 @@ import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite'; import { createAdminClient } from '@/lib/appwrite.server'; import { Query } from 'node-appwrite'; import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers'; -import { Calendar, User, ArrowLeft, Newspaper } from 'lucide-react'; +import { Calendar, User, ArrowLeft, Newspaper, Tag } from 'lucide-react'; import Link from 'next/link'; import { decodeHtmlEntities } from '@/lib/utils'; import DOMPurify from 'isomorphic-dompurify'; @@ -222,13 +222,16 @@ export default async function NoticiaDetail({ params }: { params: Promise<{ slug const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '69b84df5003bd8957c5c'; const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1'; let imagenUrl = post.og_image_url || null; + const isSquare = post.tipo === 'evento' || post.tipo === 'aviso' || + (typeof post.tipo_publicacion_id === 'object' && (post.tipo_publicacion_id.codigo === 'EVENTO' || post.tipo_publicacion_id.codigo === 'CLASIFICADO')); + const imgHeight = isSquare ? 640 : 360; if (post.imagen_destacada_id) { try { const client = await createAdminClient(); const mediaRes = await client.databases.getDocument(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', post.imagen_destacada_id); if (mediaRes) { - imagenUrl = `${ENDPOINT}/storage/buckets/${mediaRes.bucket_id}/files/${mediaRes.file_id}/preview?project=${PROJECT_ID}&width=640&height=360&quality=90`; + imagenUrl = `${ENDPOINT}/storage/buckets/${mediaRes.bucket_id}/files/${mediaRes.file_id}/preview?project=${PROJECT_ID}&width=640&height=${imgHeight}&quality=90`; } } catch(e) {} } @@ -248,21 +251,22 @@ export default async function NoticiaDetail({ params }: { params: Promise<{ slug
- VOLVER A NOTICIAS + Volver a la portada -
-
- - {post.tipo?.toUpperCase() || 'NOTICIA'} +
+
+ + + {typeof post.tipo_publicacion_id === 'object' ? post.tipo_publicacion_id.descripcion : (post.tipo || 'Noticia')} + + + + {fechaFormateada} -
- - {fechaFormateada} -
-

+

{decodeHtmlEntities(post.titulo)}

@@ -283,12 +287,12 @@ export default async function NoticiaDetail({ params }: { params: Promise<{ slug }} />
) : imagenUrl ? ( -
+
{post.titulo}
) : ( -
+
Almeira Web diff --git a/src/app/api/webhooks/whatsapp/route.ts b/src/app/api/webhooks/whatsapp/route.ts index 253d1ad..b172055 100644 --- a/src/app/api/webhooks/whatsapp/route.ts +++ b/src/app/api/webhooks/whatsapp/route.ts @@ -110,26 +110,33 @@ export async function POST(req: Request) { // LOGIC 2: PROCESAR NUEVO MENSAJE PARA GEMINI // Evitámos tratar de procesar respuestas cortas o mensajes vacíos - if (!text && !messageData.imageMessage) { + if (!text && !messageData.imageMessage && !messageData.videoMessage && !messageData.documentMessage) { return NextResponse.json({ success: true, message: 'Ignored empty message' }, { status: 200 }); } - // Verificamos si tiene multimedia + // Verificamos si tiene multimedia directa o en mensaje reciente let base64Media = undefined; let mimeType = undefined; - let imageId = undefined; + let storageFileId = undefined; + let featuredMediaDocId = undefined; if (messageData.imageMessage || messageData.videoMessage || messageData.documentMessage) { const media = await getBase64FromMediaMessage(data); if (media) { base64Media = media.base64; mimeType = media.mimetype; + } + } - // Subimos a Appwrite Storage + // Llamar a Gemini para estructurar la publicación + const parsed = await processWhatsAppMessage(text, base64Media, mimeType); + + // Si tenemos multimedia, subimos a Storage y creamos el registro en la Biblioteca de Medios (Colección 'media') + if (base64Media && mimeType) { + try { const buffer = Buffer.from(base64Media, 'base64'); - // Appwrite requiere un InputFile o similar. node-appwrite maneja InputFile.fromBuffer const extension = mimeType.split('/')[1] || 'jpg'; - const fileName = `wa_${Date.now()}.${extension}`; + const fileName = `wa_${parsed.tipo}_${Date.now()}.${extension}`; const file = InputFile.fromBuffer(buffer, fileName); const uploadedFile = await storage.createFile( @@ -137,12 +144,38 @@ export async function POST(req: Request) { ID.unique(), file ); - imageId = uploadedFile.$id; + storageFileId = uploadedFile.$id; + + // Crear registro en la Biblioteca de Medios (Colección 'media') + const mediaDoc = await databases.createDocument( + APPWRITE_ALMEIRA_CONFIG.databaseId, + 'media', + ID.unique(), + { + file_id: storageFileId, + bucket_id: APPWRITE_ALMEIRA_CONFIG.mediaBucketId, + titulo: truncateString(parsed.titulo, 240), + alt_text: truncateString(parsed.titulo, 240), + descripcion_seo: truncateString(parsed.seo_descripcion, 450), + tipo: mimeType.startsWith('video') ? 'video' : 'imagen' + } + ); + featuredMediaDocId = mediaDoc.$id; + console.log(`[WhatsApp Webhook] Imagen subida a Biblioteca de Medios (ID: ${featuredMediaDocId}, Storage: ${storageFileId})`); + } catch (err: any) { + console.error('[WhatsApp Webhook] Error subiendo multimedia a Appwrite:', err.message); } } - // Llamar a Gemini - const parsed = await processWhatsAppMessage(text, base64Media, mimeType); + // Determinar dimensiones según el tipo de publicación + // Noticias: 640x360 (16:9) | Clasificados y Eventos: 640x640 (1:1) + const isSquare = parsed.tipo === 'evento' || parsed.tipo === 'aviso'; + const imgHeight = isSquare ? 640 : 360; + let ogImageUrl = undefined; + + if (storageFileId) { + ogImageUrl = `${APPWRITE_ALMEIRA_CONFIG.endpoint}/storage/buckets/${APPWRITE_ALMEIRA_CONFIG.mediaBucketId}/files/${storageFileId}/preview?project=${APPWRITE_ALMEIRA_CONFIG.projectId}&width=640&height=${imgHeight}&quality=85`; + } // Determinar fecha de vencimiento let fechaVencimiento = parsed.fecha_vencimiento; @@ -150,22 +183,26 @@ export async function POST(req: Request) { fechaVencimiento = getLastDayOfMonth(); } - // Buscar un tipo de publicación por defecto (Requerido por Appwrite Schema) + // Buscar tipo de publicación correspondiente (NOTICIA, EVENTO, CLASIFICADO) let tipoPublicacionId = ''; try { const types = await databases.listDocuments( APPWRITE_ALMEIRA_CONFIG.databaseId, APPWRITE_ALMEIRA_CONFIG.collections.publicationTypes, - [] + [Query.limit(20)] ); - if (types.documents.length > 0) { - tipoPublicacionId = types.documents[0].$id; + const targetCode = parsed.tipo === 'evento' ? 'EVENTO' : parsed.tipo === 'aviso' ? 'CLASIFICADO' : 'NOTICIA'; + const matched = types.documents.find((d: any) => d.codigo === targetCode) || + types.documents.find((d: any) => d.codigo === 'NOTICIA') || + types.documents[0]; + if (matched) { + tipoPublicacionId = matched.$id; } } catch (e) { - console.warn('No se pudo obtener un tipo de publicación por defecto:', e); + console.warn('No se pudo obtener el tipo de publicación en Appwrite:', e); } - // Crear Post en Appwrite + // Crear Post en Appwrite (Vinculando la imagen destacada de la biblioteca de medios) const newPost = await databases.createDocument( APPWRITE_ALMEIRA_CONFIG.databaseId, APPWRITE_ALMEIRA_CONFIG.collections.posts, @@ -178,14 +215,16 @@ export async function POST(req: Request) { seo_titulo: truncateString(parsed.titulo, 60), seo_descripcion: truncateString(parsed.seo_descripcion, 155), contenido: parsed.contenido, - estado: 'borrador', // Se requiere aprobación + estado: 'borrador', // Requiere aprobación fecha_publicacion: new Date().toISOString(), ...(tipoPublicacionId && { tipo_publicacion_id: tipoPublicacionId }), ...(fechaVencimiento && { fecha_finalizacion: new Date(fechaVencimiento).toISOString() }), - ...(imageId && { imagen_destacada_id: imageId }) + ...(featuredMediaDocId && { imagen_destacada_id: featuredMediaDocId }), + ...(ogImageUrl && { og_image_url: ogImageUrl }) } ); + // Notificar al grupo de WhatsApp pidiendo aprobación const requestText = `📰 *Publicación Procesada (Pendiente de Aprobación)*\n\n` + `*Título:* ${parsed.titulo}\n`