diff --git a/dripzone-app/src/app/admin/feed-whatsapp/page.tsx b/dripzone-app/src/app/admin/feed-whatsapp/page.tsx index 3efac13..9c62453 100644 --- a/dripzone-app/src/app/admin/feed-whatsapp/page.tsx +++ b/dripzone-app/src/app/admin/feed-whatsapp/page.tsx @@ -77,7 +77,7 @@ export default function SupplierFeedPage() { // Target brand selector for publishing const [targetBrand, setTargetBrand] = useState('almeira'); - // Cargar grupos monitoreados, productos existentes y posts en vivo (por defecto PENDIENTES) + // Cargar grupos monitoreados, productos existentes y posts en vivo (TODOS los mensajes) const loadInitialData = async () => { setIsLoading(true); try { @@ -95,8 +95,8 @@ export default function SupplierFeedPage() { setExistingProducts(prodData.products); } - // 3. Posts en vivo desde los grupos de WhatsApp (iniciando con PENDIENTES para que la lista avance) - const feedRes = await fetch('/api/whatsapp-feed?fetchLive=true&limit=60&status=pending'); + // 3. Posts en vivo desde los grupos de WhatsApp (escaneando todo el historial profundo) + const feedRes = await fetch('/api/whatsapp-feed?fetchLive=true&pages=35&limit=3500&status=pending'); const feedData = await feedRes.json(); if (feedData.success && Array.isArray(feedData.posts)) { setPosts(feedData.posts); @@ -112,7 +112,7 @@ export default function SupplierFeedPage() { loadInitialData(); }, []); - // Cambiar filtro de grupo y recargar mensajes + // Cambiar filtro de grupo y recargar todos los mensajes del grupo const handleGroupFilterChange = async (groupJid: string) => { setSelectedGroupFilter(groupJid); setIsSyncingLive(true); @@ -120,13 +120,13 @@ export default function SupplierFeedPage() { try { const url = groupJid !== 'all' - ? `/api/whatsapp-feed?fetchLive=true&groupJid=${encodeURIComponent(groupJid)}&status=${tabStatus}` - : `/api/whatsapp-feed?fetchLive=true&limit=60&status=${tabStatus}`; + ? `/api/whatsapp-feed?fetchLive=true&groupJid=${encodeURIComponent(groupJid)}&status=${tabStatus}&pages=35&limit=3500` + : `/api/whatsapp-feed?fetchLive=true&status=${tabStatus}&pages=35&limit=3500`; const res = await fetch(url); const data = await res.json(); if (data.success && Array.isArray(data.posts)) { setPosts(data.posts); - setActionMessage(`✅ Se cargaron ${data.totalFound || data.posts.length} mensajes.`); + setActionMessage(`✅ Se cargaron ${data.totalFound || data.posts.length} mensajes del historial.`); } } catch (err) { console.error(err); @@ -136,22 +136,24 @@ export default function SupplierFeedPage() { } }; - // Sincronizar mensajes en vivo desde Evolution API - const handleSyncLive = async () => { + // Sincronizar mensajes en vivo desde Evolution API (con escaneo profundo opcional) + const handleSyncLive = async (deep: boolean = false) => { setIsSyncingLive(true); setActionMessage(null); try { + const pagesCount = deep ? 60 : 35; + const limitCount = deep ? 5000 : 3500; const url = selectedGroupFilter !== 'all' - ? `/api/whatsapp-feed?fetchLive=true&groupJid=${encodeURIComponent(selectedGroupFilter)}&status=${tabStatus}` - : `/api/whatsapp-feed?fetchLive=true&limit=60&status=${tabStatus}`; + ? `/api/whatsapp-feed?fetchLive=true&groupJid=${encodeURIComponent(selectedGroupFilter)}&status=${tabStatus}&pages=${pagesCount}&limit=${limitCount}` + : `/api/whatsapp-feed?fetchLive=true&status=${tabStatus}&pages=${pagesCount}&limit=${limitCount}`; const res = await fetch(url); const data = await res.json(); if (data.success && Array.isArray(data.posts)) { setPosts(data.posts); setActionMessage( - `✅ ¡Sincronización completa! Se escanearon los mensajes de WhatsApp y se omitieron los ya publicados y descartados.` + `✅ ¡Sincronización completa! Se escanearon ${data.totalFound || data.posts.length} mensajes de WhatsApp sin límite.` ); } else { setActionMessage('⚠️ No se encontraron mensajes nuevos en los grupos seleccionados.'); @@ -352,7 +354,7 @@ export default function SupplierFeedPage() { }); }; - // Cambiar pestaña y actualizar vista + // Cambiar pestaña y actualizar vista con todo el historial const handleTabChange = async (status: 'pending' | 'published' | 'discarded' | 'all') => { setTabStatus(status); setSelectedPostIds(new Set()); @@ -360,8 +362,8 @@ export default function SupplierFeedPage() { try { const url = selectedGroupFilter !== 'all' - ? `/api/whatsapp-feed?fetchLive=true&groupJid=${encodeURIComponent(selectedGroupFilter)}&status=${status}` - : `/api/whatsapp-feed?fetchLive=true&limit=60&status=${status}`; + ? `/api/whatsapp-feed?fetchLive=true&groupJid=${encodeURIComponent(selectedGroupFilter)}&status=${status}&pages=35&limit=3500` + : `/api/whatsapp-feed?fetchLive=true&status=${status}&pages=35&limit=3500`; const res = await fetch(url); const data = await res.json(); if (data.success && Array.isArray(data.posts)) { @@ -455,12 +457,23 @@ export default function SupplierFeedPage() { + + @@ -953,15 +966,54 @@ export default function SupplierFeedPage() { {/* Feed Pagination Bar */} {totalPages > 1 && ( -
- - Mostrando {startIndex + 1} a{' '} - {Math.min(startIndex + pageSize, filteredPosts.length)} de{' '} - {filteredPosts.length} mensajes de WhatsApp (Pág.{' '} - {currentPage} de {totalPages}) - +
+
+ + Mostrando {startIndex + 1} a{' '} + {Math.min(startIndex + pageSize, filteredPosts.length)} de{' '} + {filteredPosts.length} mensajes (Página{' '} + {currentPage} de {totalPages}) + + + {/* Page Size Selector */} +
+ Por pág: + +
+
+ +
+ {/* Page Jump Selector */} + {totalPages > 5 && ( +
+ Ir a: + +
+ )} -
- + {currentPage} / {totalPages} diff --git a/dripzone-app/src/app/api/whatsapp-feed/route.ts b/dripzone-app/src/app/api/whatsapp-feed/route.ts index 8481240..61dc74c 100644 --- a/dripzone-app/src/app/api/whatsapp-feed/route.ts +++ b/dripzone-app/src/app/api/whatsapp-feed/route.ts @@ -22,7 +22,8 @@ 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') || '60', 10); + const limit = parseInt(searchParams.get('limit') || '3000', 10); + const maxPages = parseInt(searchParams.get('pages') || '30', 10); const filterJid = searchParams.get('groupJid'); const statusFilter = (searchParams.get('status') || 'pending').toLowerCase(); @@ -128,26 +129,45 @@ export async function GET(request: Request) { }); } - // 4. Consultar mensajes de cada grupo en paralelo desde Evolution API + // 4. Consultar mensajes de los grupos objetivo recorriendo todas las páginas en paralelo const groupFetchPromises = targetJids.map(async (jid) => { - try { - const fetchLimit = Math.max(limit * 2, 80); - const msgRes = await fetch( - `${EVOLUTION_API_URL}/chat/findMessages/${EVOLUTION_INSTANCE_NAME}?page=1&limit=${fetchLimit}`, - { - method: 'POST', - headers: { - apikey: EVOLUTION_API_KEY, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ where: { key: { remoteJid: jid } } }), - } + const allGroupRecords: any[] = []; + const pagesToFetch = Array.from({ length: maxPages }, (_, i) => i + 1); + + // Lotes concurrentes de 10 páginas para máxima velocidad y profundidad + const BATCH_SIZE = 10; + for (let i = 0; i < pagesToFetch.length; i += BATCH_SIZE) { + const batch = pagesToFetch.slice(i, i + BATCH_SIZE); + const batchResults = await Promise.allSettled( + batch.map(async (page) => { + const msgRes = await fetch( + `${EVOLUTION_API_URL}/chat/findMessages/${EVOLUTION_INSTANCE_NAME}?page=${page}&limit=50`, + { + 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 : []); + }) ); - const msgData = await msgRes.json(); - return msgData.messages?.records || msgData.records || (Array.isArray(msgData) ? msgData : []); - } catch (err) { - return []; + + let hitEnd = false; + for (const res of batchResults) { + if (res.status === 'fulfilled' && Array.isArray(res.value)) { + allGroupRecords.push(...res.value); + if (res.value.length === 0) { + hitEnd = true; + } + } + } + if (hitEnd) break; } + return allGroupRecords; }); const results = await Promise.allSettled(groupFetchPromises); @@ -229,25 +249,13 @@ export async function GET(request: Request) { } let imageUrls: string[] = []; + const imgMsg = record.message?.imageMessage; - 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 (imgMsg) { + if (imgMsg.jpegThumbnail) { + imageUrls.push(`data:image/jpeg;base64,${imgMsg.jpegThumbnail}`); + } else if (imgMsg.url) { + imageUrls.push(imgMsg.url); } } @@ -260,7 +268,7 @@ export async function GET(request: Request) { let extracted = existingInAppwrite?.extractedProduct; if (!extracted) { const cleanInputText = text || ''; - extracted = await parseSupplierMessage(cleanInputText, imageUrls[0], [groupName]); + extracted = await parseSupplierMessage(cleanInputText, undefined, [groupName]); } else if (extracted && extracted.name) { extracted.name = sanitizePublicationTitle(extracted.name, [groupName], extracted.brand); } diff --git a/zapply-app/src/app/api/whatsapp-feed/route.ts b/zapply-app/src/app/api/whatsapp-feed/route.ts index 9d6ad5f..eb2780a 100644 --- a/zapply-app/src/app/api/whatsapp-feed/route.ts +++ b/zapply-app/src/app/api/whatsapp-feed/route.ts @@ -13,7 +13,8 @@ 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 limit = parseInt(searchParams.get('limit') || '3000', 10); + const maxPages = parseInt(searchParams.get('pages') || '30', 10); const filterJid = searchParams.get('groupJid'); const statusFilter = (searchParams.get('status') || 'pending').toLowerCase(); @@ -99,26 +100,44 @@ export async function GET(request: Request) { }); } - // 3. Consultar mensajes de cada grupo objetivo en paralelo + // 3. Consultar mensajes de cada grupo objetivo recorriendo múltiples páginas en paralelo const groupFetchPromises = targetJids.map(async (jid) => { - try { - const fetchLimit = Math.max(limit * 2, 80); - const msgRes = await fetch( - `${EVOLUTION_API_URL}/chat/findMessages/${EVOLUTION_INSTANCE_NAME}?page=1&limit=${fetchLimit}`, - { - method: 'POST', - headers: { - apikey: EVOLUTION_API_KEY, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ where: { key: { remoteJid: jid } } }), - } + const allGroupRecords: any[] = []; + const pagesToFetch = Array.from({ length: maxPages }, (_, i) => i + 1); + + const BATCH_SIZE = 10; + for (let i = 0; i < pagesToFetch.length; i += BATCH_SIZE) { + const batch = pagesToFetch.slice(i, i + BATCH_SIZE); + const batchResults = await Promise.allSettled( + batch.map(async (page) => { + const msgRes = await fetch( + `${EVOLUTION_API_URL}/chat/findMessages/${EVOLUTION_INSTANCE_NAME}?page=${page}&limit=50`, + { + 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 : []); + }) ); - const msgData = await msgRes.json(); - return msgData.messages?.records || msgData.records || (Array.isArray(msgData) ? msgData : []); - } catch (err) { - return []; + + let hitEnd = false; + for (const res of batchResults) { + if (res.status === 'fulfilled' && Array.isArray(res.value)) { + allGroupRecords.push(...res.value); + if (res.value.length === 0) { + hitEnd = true; + } + } + } + if (hitEnd) break; } + return allGroupRecords; }); const results = await Promise.allSettled(groupFetchPromises); @@ -132,7 +151,7 @@ export async function GET(request: Request) { // 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 + // 5. Procesar cada mensaje con control de identificador de WhatsApp const processedPosts: SupplierPost[] = []; const seenMessageIds = new Set(); @@ -191,26 +210,13 @@ export async function GET(request: Request) { } let imageUrls: string[] = []; + const imgMsg = record.message?.imageMessage; - // 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 (imgMsg) { + if (imgMsg.jpegThumbnail) { + imageUrls.push(`data:image/jpeg;base64,${imgMsg.jpegThumbnail}`); + } else if (imgMsg.url) { + imageUrls.push(imgMsg.url); } }