import { NextResponse } from 'next/server'; import { dataService } from '@/lib/data-service'; import { parseSupplierMessage } from '@/lib/gemini-parser'; import { getProductSocialProof } from '@/lib/social-proof'; import { generateAttractiveProductCopy } from '@/lib/attractive-copy'; import { SupplierPost, MonitoredWhatsAppGroup } from '@/types'; const EVOLUTION_API_URL = process.env.EVOLUTION_API_URL || 'https://evolution.almeira.com.co'; const EVOLUTION_API_KEY = process.env.EVOLUTION_API_KEY || 'EE49029EEB85-4568-8F81-C9FDFC6E2943'; const EVOLUTION_INSTANCE_NAME = process.env.EVOLUTION_INSTANCE_NAME || 'AutomataAlmeira'; export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const fetchLive = searchParams.get('fetchLive') !== 'false'; const limit = parseInt(searchParams.get('limit') || '50', 10); const filterJid = searchParams.get('groupJid'); // 1. Obtener grupos locales y descubrir grupos en vivo desde Evolution API const localGroups: MonitoredWhatsAppGroup[] = dataService.getMonitoredGroups(); const groupsMap = new Map(); localGroups.forEach((g) => groupsMap.set(g.jid, g.name)); let liveGroups: any[] = []; try { const chatsRes = await fetch(`${EVOLUTION_API_URL}/chat/findChats/${EVOLUTION_INSTANCE_NAME}`, { method: 'POST', headers: { apikey: EVOLUTION_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); const allChats = await chatsRes.json(); if (Array.isArray(allChats)) { liveGroups = allChats.filter( (c: any) => (c.remoteJid && c.remoteJid.includes('@g.us')) || (c.id && c.id.includes('@g.us')) ); liveGroups.forEach((g: any) => { const jid = g.remoteJid || g.id || g.jid; const name = g.name || g.pushName || g.subject || 'Grupo WhatsApp'; if (jid) groupsMap.set(jid, name); }); } } catch (e) { console.warn('Error resolviendo chats de WhatsApp:', e); } // 2. Determinar JIDs objetivo a consultar let targetJids: string[] = []; if (filterJid && filterJid !== 'all') { targetJids = [filterJid]; } else { const monitored = localGroups.filter((g) => g.isMonitored).map((g) => g.jid); if (monitored.length > 0) { targetJids = monitored; } else if (liveGroups.length > 0) { targetJids = liveGroups.map((g: any) => g.remoteJid || g.id); } else { targetJids = Array.from(groupsMap.keys()); } } if (targetJids.length === 0) { return NextResponse.json({ success: true, isLive: true, totalFound: 0, posts: dataService.getSupplierPosts(), groups: Array.from(groupsMap.entries()).map(([jid, name]) => ({ jid, name })), }); } // 3. Consultar mensajes de cada grupo objetivo en paralelo const groupFetchPromises = targetJids.map(async (jid) => { try { const msgRes = await fetch( `${EVOLUTION_API_URL}/chat/findMessages/${EVOLUTION_INSTANCE_NAME}?page=1&limit=${limit}`, { method: 'POST', headers: { apikey: EVOLUTION_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ where: { key: { remoteJid: jid } } }), } ); const msgData = await msgRes.json(); return msgData.messages?.records || msgData.records || (Array.isArray(msgData) ? msgData : []); } catch (err) { return []; } }); const results = await Promise.allSettled(groupFetchPromises); const allRecords: any[] = []; for (const res of results) { if (res.status === 'fulfilled' && Array.isArray(res.value)) { allRecords.push(...res.value); } } // 4. Ordenar cronológicamente (más recientes primero) allRecords.sort((a, b) => (b.messageTimestamp || 0) - (a.messageTimestamp || 0)); // 5. Procesar cada mensaje y descargar imágenes/IA const processedPosts: SupplierPost[] = []; // Limitar a los 60 más relevantes para rendimiento óptimo const sliceRecords = allRecords.slice(0, 60); for (const record of sliceRecords) { const remoteJid = record.key?.remoteJid || ''; const text = record.message?.conversation || record.message?.extendedTextMessage?.text || record.message?.imageMessage?.caption || record.message?.videoMessage?.caption || ''; const groupName = groupsMap.get(remoteJid) || record.pushName || 'Grupo Mayorista'; const hasImage = Boolean(record.message?.imageMessage); // Si no tiene texto ni imagen, omitir if (!text && !hasImage) continue; let imageUrls: string[] = []; // Descargar imagen en Base64 desde Evolution API si está presente if (hasImage) { try { const mediaUrl = `${EVOLUTION_API_URL}/chat/getBase64FromMediaMessage/${EVOLUTION_INSTANCE_NAME}`; const mediaRes = await fetch(mediaUrl, { method: 'POST', headers: { apikey: EVOLUTION_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ message: record, convertToMp4: false }), }); const mediaData = await mediaRes.json(); if (mediaData.base64) { const mime = mediaData.mimetype || 'image/jpeg'; imageUrls.push(`data:${mime};base64,${mediaData.base64}`); } } catch (mediaErr) { console.warn('Error descargando imagen de WhatsApp:', mediaErr); } } if (imageUrls.length === 0) { imageUrls.push( 'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80' ); } // Parsear oferta de calzado con IA const extracted = await parseSupplierMessage(text || groupName); const timestamp = record.messageTimestamp ? new Date(record.messageTimestamp * 1000).toLocaleString('es-CO', { hour: '2-digit', minute: '2-digit', day: '2-digit', month: 'short', }) : 'Reciente'; const post: SupplierPost = { id: `post-${record.key?.id || Date.now()}-${Math.random().toString(36).slice(2, 6)}`, channelName: groupName, channelJid: remoteJid, rawMessage: text || `[Foto de Calzado enviada en ${groupName}]`, rawImages: imageUrls, extractedProduct: extracted, status: 'pending_review', receivedAt: timestamp, supplierName: groupName, supplierPhone: record.key?.participant?.replace(/\D/g, '') || '573167455877', }; processedPosts.push(post); } // Guardar en el servicio local los nuevos posts if (processedPosts.length > 0) { dataService.saveSupplierPosts(processedPosts); } return NextResponse.json({ success: true, isLive: true, totalFound: processedPosts.length, posts: processedPosts.length > 0 ? processedPosts : dataService.getSupplierPosts(), groups: Array.from(groupsMap.entries()).map(([jid, name]) => ({ jid, name })), }); } catch (error: any) { console.error('Error en /api/whatsapp-feed:', error); return NextResponse.json({ success: false, error: error.message }, { status: 500 }); } } export async function POST(request: Request) { try { const body = await request.json(); const { action, post, posts, customRetailPrice, customName, customColorName, customSizes } = body; // 1. PUBLICAR UN PRODUCTO A LA TIENDA (APP MÓVIL Y WEB) if (action === 'publish' && post) { const retailPrice = customRetailPrice || post.extractedProduct?.suggestedRetailPrice || (post.extractedProduct?.supplierPrice ? post.extractedProduct.supplierPrice + 35000 : 125000); const prodName = customName || post.extractedProduct?.name || 'Zapatillas Urbanas'; const prodBrand = post.extractedProduct?.brand || 'Nike'; const proof = getProductSocialProof({ name: prodName, brand: prodBrand }); const newProduct = { id: `snk-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, name: prodName, modelName: prodName, colorName: customColorName || post.extractedProduct?.colorName, brand: prodBrand, supplierPrice: post.extractedProduct?.supplierPrice || 75000, retailPrice: retailPrice, originalPrice: Math.round(retailPrice * 1.35), sizes: customSizes || post.extractedProduct?.sizes || ['37', '38', '39', '40', '41', '42', '43'], images: Array.isArray(post.rawImages) && post.rawImages.length > 0 ? post.rawImages : ['https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80'], description: generateAttractiveProductCopy({ name: prodName, brand: prodBrand, retailPrice }), category: post.extractedProduct?.category || 'Calzado / Sneakers', inStock: true, isActive: true, rating: proof.rating, reviewsCount: proof.reviewsCount, salesCount: proof.salesCount, originBadge: proof.badge, supplierName: post.supplierName || post.channelName || 'Proveedor Mayorista', supplierPhone: post.supplierPhone || '573167455877', }; const added = dataService.addProduct(newProduct as any); dataService.updateSupplierPostStatus(post.id, 'published'); return NextResponse.json({ success: true, message: `¡${prodName} publicado y agrupado en el catálogo!`, product: added, }); } // 2. PUBLICACIÓN MASIVA (BULK) if (action === 'bulk-publish' && Array.isArray(posts)) { const createdProducts = []; for (const p of posts) { const retailPrice = p.extractedProduct?.suggestedRetailPrice || (p.extractedProduct?.supplierPrice ? p.extractedProduct.supplierPrice + 35000 : 125000); const prodName = p.extractedProduct?.name || 'Zapatillas Urbanas'; const prodBrand = p.extractedProduct?.brand || 'Nike'; const proof = getProductSocialProof({ name: prodName, brand: prodBrand }); const newProd = { id: `snk-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, name: prodName, modelName: prodName, brand: prodBrand, supplierPrice: p.extractedProduct?.supplierPrice || 75000, retailPrice, originalPrice: Math.round(retailPrice * 1.35), sizes: p.extractedProduct?.sizes || ['37', '38', '39', '40', '41', '42', '43'], images: p.rawImages || ['https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80'], description: generateAttractiveProductCopy({ name: prodName, brand: prodBrand, retailPrice }), category: p.extractedProduct?.category || 'Calzado / Sneakers', inStock: true, isActive: true, rating: proof.rating, reviewsCount: proof.reviewsCount, salesCount: proof.salesCount, originBadge: proof.badge, supplierName: p.supplierName || p.channelName || 'Proveedor Mayorista', supplierPhone: p.supplierPhone || '573167455877', }; const added = dataService.addProduct(newProd as any); dataService.updateSupplierPostStatus(p.id, 'published'); createdProducts.push(added); } return NextResponse.json({ success: true, message: `¡Se publicaron y agruparon ${createdProducts.length} productos en la App Móvil y Web!`, products: createdProducts, }); } // 3. DESCARTAR POST if (action === 'discard' && post?.id) { dataService.updateSupplierPostStatus(post.id, 'discarded'); return NextResponse.json({ success: true, message: 'Mensaje descartado' }); } return NextResponse.json({ success: false, error: 'Acción no válida' }, { status: 400 }); } catch (err: any) { console.error('Error en POST /api/whatsapp-feed:', err); return NextResponse.json({ success: false, error: err.message }, { status: 500 }); } }