fix(publications): restablecer publicaciones automaticas en app y catalogo whatsapp, corregir webhooks y sincronizacion
This commit is contained in:
parent
2aa0b8560d
commit
ff95bbd98a
@ -4,20 +4,26 @@ import { generateWhatsAppCatalogCsv } from '@/lib/whatsapp-catalog';
|
|||||||
import { getProductsFromAppwrite } from '@/lib/server-appwrite';
|
import { getProductsFromAppwrite } from '@/lib/server-appwrite';
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
export const revalidate = 0;
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
let products = dataService.getProducts();
|
let products = dataService.getProducts();
|
||||||
if (products.length === 0) {
|
|
||||||
try {
|
// Sincronizar siempre con Appwrite para garantizar catálogo en tiempo real
|
||||||
const appwriteProducts = await getProductsFromAppwrite();
|
try {
|
||||||
if (appwriteProducts && appwriteProducts.length > 0) {
|
const appwriteProducts = await getProductsFromAppwrite();
|
||||||
appwriteProducts.forEach((p) => dataService.addProduct(p));
|
if (appwriteProducts && appwriteProducts.length > 0) {
|
||||||
products = dataService.getProducts();
|
const existingIds = new Set(products.map((p) => p.id));
|
||||||
|
for (const ap of appwriteProducts) {
|
||||||
|
if (!existingIds.has(ap.id)) {
|
||||||
|
dataService.addProduct(ap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
products = dataService.getProducts();
|
||||||
console.warn('Error sincronizando productos con Appwrite para CSV Feed:', e);
|
|
||||||
}
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.warn('Error sincronizando productos con Appwrite para CSV Feed:', e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const csv = generateWhatsAppCatalogCsv(products);
|
const csv = generateWhatsAppCatalogCsv(products);
|
||||||
@ -27,7 +33,7 @@ export async function GET() {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/csv; charset=utf-8',
|
'Content-Type': 'text/csv; charset=utf-8',
|
||||||
'Content-Disposition': 'inline; filename="dripzone_whatsapp_catalog.csv"',
|
'Content-Disposition': 'inline; filename="dripzone_whatsapp_catalog.csv"',
|
||||||
'Cache-Control': 'public, max-age=300, stale-while-revalidate=600',
|
'Cache-Control': 'public, max-age=60, s-maxage=60, stale-while-revalidate=120',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@ -4,20 +4,26 @@ import { generateWhatsAppCatalogXml } from '@/lib/whatsapp-catalog';
|
|||||||
import { getProductsFromAppwrite } from '@/lib/server-appwrite';
|
import { getProductsFromAppwrite } from '@/lib/server-appwrite';
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
export const revalidate = 0;
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
let products = dataService.getProducts();
|
let products = dataService.getProducts();
|
||||||
if (products.length === 0) {
|
|
||||||
try {
|
// Sincronizar siempre con Appwrite para garantizar catálogo en tiempo real
|
||||||
const appwriteProducts = await getProductsFromAppwrite();
|
try {
|
||||||
if (appwriteProducts && appwriteProducts.length > 0) {
|
const appwriteProducts = await getProductsFromAppwrite();
|
||||||
appwriteProducts.forEach((p) => dataService.addProduct(p));
|
if (appwriteProducts && appwriteProducts.length > 0) {
|
||||||
products = dataService.getProducts();
|
const existingIds = new Set(products.map((p) => p.id));
|
||||||
|
for (const ap of appwriteProducts) {
|
||||||
|
if (!existingIds.has(ap.id)) {
|
||||||
|
dataService.addProduct(ap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
products = dataService.getProducts();
|
||||||
console.warn('Error sincronizando productos con Appwrite para XML Feed:', e);
|
|
||||||
}
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.warn('Error sincronizando productos con Appwrite para XML Feed:', e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = dataService.getSettings();
|
const settings = dataService.getSettings();
|
||||||
@ -27,7 +33,7 @@ export async function GET() {
|
|||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/xml; charset=utf-8',
|
'Content-Type': 'application/xml; charset=utf-8',
|
||||||
'Cache-Control': 'public, max-age=300, stale-while-revalidate=600',
|
'Cache-Control': 'public, max-age=60, s-maxage=60, stale-while-revalidate=120',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@ -4,7 +4,14 @@ import { parseSupplierMessage, detectCategory } from '@/lib/gemini-parser';
|
|||||||
import { sanitizePublicationTitle } from '@/lib/sanitizer';
|
import { sanitizePublicationTitle } from '@/lib/sanitizer';
|
||||||
import { generateAttractiveProductCopy } from '@/lib/attractive-copy';
|
import { generateAttractiveProductCopy } from '@/lib/attractive-copy';
|
||||||
import { getProductSocialProof } from '@/lib/social-proof';
|
import { getProductSocialProof } from '@/lib/social-proof';
|
||||||
import { notifyNewProductPublished, sendWhatsAppMessage, sendWhatsAppMedia } from '@/lib/evolution-api';
|
import {
|
||||||
|
notifyNewProductPublished,
|
||||||
|
sendWhatsAppMessage,
|
||||||
|
sendWhatsAppMedia,
|
||||||
|
getWhatsAppGroupSubject,
|
||||||
|
getBase64FromMediaMessage,
|
||||||
|
fetchRecentMediaFromSameChat,
|
||||||
|
} from '@/lib/evolution-api';
|
||||||
import {
|
import {
|
||||||
getMonitoredGroupsFromAppwrite,
|
getMonitoredGroupsFromAppwrite,
|
||||||
saveProductToAppwrite,
|
saveProductToAppwrite,
|
||||||
@ -220,27 +227,39 @@ export async function POST(request: Request) {
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
// CASO B: GRUPOS MAYORISTAS DE PROVEEDORES (Publicación Automática Incondicional)
|
// CASO B: GRUPOS MAYORISTAS DE PROVEEDORES (Publicación Automática Incondicional)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
const groupSubject =
|
// Resolver el nombre del grupo de forma precisa usando Evolution API si no viene en el payload
|
||||||
data.groupInfo?.subject ||
|
let groupSubject = data.groupInfo?.subject || data.chatName || '';
|
||||||
data.chatName ||
|
if (!groupSubject || groupSubject.length < 3 || groupSubject === data.pushName) {
|
||||||
data.pushName ||
|
try {
|
||||||
'Grupo Mayorista';
|
const resolved = await getWhatsAppGroupSubject(remoteJid);
|
||||||
|
if (resolved) {
|
||||||
|
groupSubject = resolved;
|
||||||
|
} else {
|
||||||
|
groupSubject = data.pushName || 'Grupo Mayorista';
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
groupSubject = data.pushName || 'Grupo Mayorista';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. FILTRAR EXCLUSIVAMENTE GRUPOS DE PRENSA Y GOBIERNO (Reenviar a portal de noticias)
|
// 1. FILTRAR EXCLUSIVAMENTE GRUPOS DE PRENSA Y GOBIERNO (Reenviar a portal de noticias Almeira)
|
||||||
const isNonProductNews = /gob cauca|gobernacion|alcaldia|prensa|comunicaciones almeira|sedes asociadas|notificaciones almeira|inelpac|meliextra/i.test(
|
const isNonProductNews = /gob cauca|gobernacion|alcaldia|prensa|medios|noticias|comunicaciones|sedes asociadas|notificaciones almeira|inelpac|meliextra|almeira producciones|publicaciones almeira|quilichao en medios/i.test(
|
||||||
groupSubject
|
groupSubject
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isNonProductNews) {
|
if (isNonProductNews) {
|
||||||
// Reenviar asíncronamente al webhook del portal de noticias Almeira
|
try {
|
||||||
fetch('https://news.almeira.com.co/api/webhooks/whatsapp', {
|
await fetch('https://news.almeira.com.co/api/webhooks/whatsapp', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
signal: AbortSignal.timeout(10000),
|
signal: AbortSignal.timeout(15000),
|
||||||
}).catch(() => {});
|
});
|
||||||
|
console.log(`[DripZone Webhook] Chat de prensa reenviado a news.almeira.com.co: "${groupSubject}" (${remoteJid})`);
|
||||||
|
} catch (newsErr: any) {
|
||||||
|
console.warn('[DripZone Webhook] Error reenviando a portal de noticias:', newsErr?.message);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`[DripZone Webhook] Chat de prensa redirigido a news: "${groupSubject}" (${remoteJid})`);
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
ignored: 'Grupo de noticias institucionales reenviado a portal de prensa',
|
ignored: 'Grupo de noticias institucionales reenviado a portal de prensa',
|
||||||
@ -249,6 +268,17 @@ export async function POST(request: Request) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1.1 FILTRAR GRUPOS FAMILIARES O PERSONALES (Evitar publicar mensajes de chats familiares)
|
||||||
|
const isFamilyOrPersonal = /flia|familia|partner bold|almeira familia/i.test(groupSubject);
|
||||||
|
if (isFamilyOrPersonal) {
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
ignored: 'Chat familiar/personal omitido de publicaciones comerciales',
|
||||||
|
chat: groupSubject,
|
||||||
|
remoteJid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-registrar grupo como monitoreado y activo si no existía previamente
|
// Auto-registrar grupo como monitoreado y activo si no existía previamente
|
||||||
try {
|
try {
|
||||||
const currentGroups = dataService.getMonitoredGroups();
|
const currentGroups = dataService.getMonitoredGroups();
|
||||||
@ -285,11 +315,33 @@ export async function POST(request: Request) {
|
|||||||
|
|
||||||
// 3. EXTRACCIÓN MULTIMEDIA (IMAGEN HD Y THUMBNAIL)
|
// 3. EXTRACCIÓN MULTIMEDIA (IMAGEN HD Y THUMBNAIL)
|
||||||
let imageBase64: string | undefined = data.base64 || undefined;
|
let imageBase64: string | undefined = data.base64 || undefined;
|
||||||
|
|
||||||
|
// Si no vino base64 directo, solicitar la imagen Full HD a Evolution API
|
||||||
|
if (!imageBase64 && (data.message?.imageMessage || data.message?.extendedTextMessage)) {
|
||||||
|
try {
|
||||||
|
const media = await getBase64FromMediaMessage(data);
|
||||||
|
if (media?.base64) {
|
||||||
|
imageBase64 = media.base64.startsWith('data:') ? media.base64 : `data:${media.mimetype || 'image/jpeg'};base64,${media.base64}`;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si aún no hay base64 pero viene jpegThumbnail, usarlo como fallback
|
||||||
if (!imageBase64 && data.message?.imageMessage?.jpegThumbnail) {
|
if (!imageBase64 && data.message?.imageMessage?.jpegThumbnail) {
|
||||||
const thumb = data.message.imageMessage.jpegThumbnail;
|
const thumb = data.message.imageMessage.jpegThumbnail;
|
||||||
imageBase64 = thumb.startsWith('data:') ? thumb : `data:image/jpeg;base64,${thumb}`;
|
imageBase64 = thumb.startsWith('data:') ? thumb : `data:image/jpeg;base64,${thumb}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Si aún no hay imagen (ej. el proveedor envió la foto antes del texto), recolectar fotos recientes del chat
|
||||||
|
if (!imageBase64) {
|
||||||
|
try {
|
||||||
|
const recentMedia = await fetchRecentMediaFromSameChat(remoteJid, 5, 240);
|
||||||
|
if (recentMedia.length > 0 && recentMedia[0].base64) {
|
||||||
|
imageBase64 = `data:${recentMedia[0].mimeType || 'image/jpeg'};base64,${recentMedia[0].base64}`;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
// Guardar imagen en disco local inmediatamente para disponibilidad instantánea
|
// Guardar imagen en disco local inmediatamente para disponibilidad instantánea
|
||||||
if (imageBase64) {
|
if (imageBase64) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
const EVOLUTION_API_URL = process.env.EVOLUTION_API_URL || 'https://evolution.almeira.com.co';
|
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_API_KEY = process.env.EVOLUTION_FEED_API_KEY || process.env.EVOLUTION_API_KEY || 'EE49029EEB85-4568-8F81-C9FDFC6E2943';
|
||||||
const EVOLUTION_INSTANCE = process.env.EVOLUTION_INSTANCE_NAME || 'AutomataAlmeira';
|
const EVOLUTION_INSTANCE = process.env.EVOLUTION_FEED_INSTANCE_NAME || 'AutomataAlmeira';
|
||||||
|
|
||||||
// Carpeta local para almacenar en caché las imágenes descargadas en alta resolución
|
// Carpeta local para almacenar en caché las imágenes descargadas en alta resolución
|
||||||
const CACHE_DIR = path.join(process.cwd(), 'public', 'cache', 'whatsapp-media');
|
const CACHE_DIR = path.join(process.cwd(), 'public', 'cache', 'whatsapp-media');
|
||||||
|
|||||||
@ -125,6 +125,151 @@ export async function sendWhatsAppMedia(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In-memory cache for group subjects: jid -> { subject, expiresAt }
|
||||||
|
const groupCache = new Map<string, { subject: string; expiresAt: number }>();
|
||||||
|
const GROUP_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hora
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener todos los grupos de WhatsApp desde Evolution API
|
||||||
|
*/
|
||||||
|
export async function fetchAllWhatsAppGroups(): Promise<Array<{ id: string; subject: string }>> {
|
||||||
|
try {
|
||||||
|
const url = `${EVOLUTION_API_URL}/group/fetchAllGroups/${EVOLUTION_FEED_INSTANCE_NAME}?getParticipants=false`;
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
headers: { apikey: EVOLUTION_FEED_API_KEY, 'Content-Type': 'application/json' },
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Array.isArray(response.data)) {
|
||||||
|
const groups = response.data
|
||||||
|
.map((g: any) => ({
|
||||||
|
id: g.id || g.jid || '',
|
||||||
|
subject: g.subject || g.name || '',
|
||||||
|
}))
|
||||||
|
.filter((g: any) => Boolean(g.id));
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
for (const group of groups) {
|
||||||
|
if (group.id && group.subject) {
|
||||||
|
groupCache.set(group.id, { subject: group.subject, expiresAt: now + GROUP_CACHE_TTL_MS });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.warn('[Evolution API] Error fetching WhatsApp groups:', err?.response?.data || err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolver el nombre exacto de un grupo por su JID (ej. 120363049363072784@g.us)
|
||||||
|
*/
|
||||||
|
export async function getWhatsAppGroupSubject(groupJid: string): Promise<string | null> {
|
||||||
|
if (!groupJid) return null;
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const cached = groupCache.get(groupJid);
|
||||||
|
if (cached && cached.expiresAt > now) {
|
||||||
|
return cached.subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups = await fetchAllWhatsAppGroups();
|
||||||
|
const found = groups.find((g) => g.id === groupJid);
|
||||||
|
if (found && found.subject) {
|
||||||
|
return found.subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extraer base64 de un mensaje multimedia desde Evolution API v2
|
||||||
|
*/
|
||||||
|
export async function getBase64FromMediaMessage(messagePayload: any): Promise<{ base64: string; mimetype: string } | null> {
|
||||||
|
try {
|
||||||
|
const url = `${EVOLUTION_API_URL}/chat/getBase64FromMediaMessage/${EVOLUTION_FEED_INSTANCE_NAME}`;
|
||||||
|
const response = await axios.post(
|
||||||
|
url,
|
||||||
|
{ message: messagePayload },
|
||||||
|
{ headers: { apikey: EVOLUTION_FEED_API_KEY, 'Content-Type': 'application/json' }, timeout: 12000 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.data && response.data.base64) {
|
||||||
|
return {
|
||||||
|
base64: response.data.base64,
|
||||||
|
mimetype: response.data.mimetype || 'image/jpeg',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.warn('[Evolution API] Error getBase64FromMediaMessage:', err?.response?.data || err.message);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consultar multimedia reciente del mismo chat para recolectar álbumes de calzado
|
||||||
|
*/
|
||||||
|
export async function fetchRecentMediaFromSameChat(
|
||||||
|
remoteJid: string,
|
||||||
|
maxItems: number = 8,
|
||||||
|
windowSeconds: number = 240
|
||||||
|
): Promise<Array<{ base64: string; mimeType: string; messageId: string }>> {
|
||||||
|
if (!remoteJid) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = `${EVOLUTION_API_URL}/chat/findMessages/${EVOLUTION_FEED_INSTANCE_NAME}?page=1&limit=25`;
|
||||||
|
const response = await axios.post(
|
||||||
|
url,
|
||||||
|
{ where: { key: { remoteJid } } },
|
||||||
|
{ headers: { apikey: EVOLUTION_FEED_API_KEY, 'Content-Type': 'application/json' }, timeout: 8000 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const records = response.data?.messages?.records || response.data?.records || (Array.isArray(response.data) ? response.data : []);
|
||||||
|
const nowSec = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
const mediaRecords = records
|
||||||
|
.filter((r: any) => {
|
||||||
|
const isMedia = Boolean(r.message?.imageMessage);
|
||||||
|
if (!isMedia || !r.messageTimestamp) return false;
|
||||||
|
const diff = Math.abs(nowSec - r.messageTimestamp);
|
||||||
|
return diff <= windowSeconds;
|
||||||
|
})
|
||||||
|
.slice(0, maxItems);
|
||||||
|
|
||||||
|
if (mediaRecords.length === 0) return [];
|
||||||
|
|
||||||
|
const fetchPromises = mediaRecords.map(async (record: any) => {
|
||||||
|
const media = await getBase64FromMediaMessage(record);
|
||||||
|
if (media && media.base64) {
|
||||||
|
return {
|
||||||
|
base64: media.base64,
|
||||||
|
mimeType: media.mimetype,
|
||||||
|
messageId: record.key?.id || String(Date.now()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await Promise.allSettled(fetchPromises);
|
||||||
|
const validMedia: Array<{ base64: string; mimeType: string; messageId: string }> = [];
|
||||||
|
|
||||||
|
for (const res of results) {
|
||||||
|
if (res.status === 'fulfilled' && res.value) {
|
||||||
|
validMedia.push(res.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validMedia;
|
||||||
|
} catch (err: any) {
|
||||||
|
console.warn('[Evolution API] Error fetchRecentMediaFromSameChat:', err?.message);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifica inmediatamente la publicación automática de un nuevo producto
|
* Notifica inmediatamente la publicación automática de un nuevo producto
|
||||||
* exclusivamente al WhatsApp personal de Carlos (+57 316 745 5877).
|
* exclusivamente al WhatsApp personal de Carlos (+57 316 745 5877).
|
||||||
|
|||||||
@ -85,23 +85,20 @@ def main():
|
|||||||
|
|
||||||
print("[4/4] Enviando reporte y APK al WhatsApp de Carlos (+57 316 745 5877)...")
|
print("[4/4] Enviando reporte y APK al WhatsApp de Carlos (+57 316 745 5877)...")
|
||||||
message_text = (
|
message_text = (
|
||||||
"🔥 *DripZone Android v2.1 - Tarjetas Comerciales de Alto Impacto Desplegadas*\n\n"
|
"🚀 *DripZone Android & Catálogo WhatsApp - Publicaciones Automáticas Restablecidas*\n\n"
|
||||||
"Hola Carlos, se ha actualizado la aplicación nativa en Android con todas las etiquetas llamativas, prueba social y visuales de conversión solicitadas:\n\n"
|
"Hola Carlos, se ha corregido y verificado con éxito el sistema de publicaciones automáticas en la aplicación móvil y el catálogo de WhatsApp:\n\n"
|
||||||
"🏷️ *1. Badges Llamativos de Descuento (-30% OFF):*\n"
|
"📡 *1. Configuración de Webhooks en Evolution API:*\n"
|
||||||
"• Cada tarjeta ahora cuenta con su badge de descuento en gradiente morado a fucsia sobre la foto del calzado.\n"
|
"• La instancia `AutomataAlmeira` (tu número +57 316 745 5877 en los 20 grupos) y la instancia `DripZone` (+57 310 639 0296) ahora apuntan directamente a `https://drip-zone.online/api/webhook/whatsapp`.\n"
|
||||||
"• Cálculo dinámico contra el precio original o porcentaje estimado de ahorro.\n\n"
|
"• Se eliminó el descarte accidental que ocurría cuando los mensajes pasaban por el portal de noticias.\n\n"
|
||||||
"⭐ *2. Prueba Social & Contador de Compras:*\n"
|
"👟 *2. Publicación Inmediata en la App y Appwrite:*\n"
|
||||||
"• Calificación por estrellas doradas (4.8 - 5.0) con número de opiniones reales verificadas.\n"
|
"• Cada calzado recibido desde los grupos mayoristas (Jovic Store, CC Mío, Team Emporio, etc.) se procesa con Gemini AI, calcula márgenes de ganancia y se publica de inmediato en la App móvil y en la web.\n\n"
|
||||||
"• Conteo de ventas por modelo (+75 a +340 pedidos despachados) visible directamente en la vitrina.\n\n"
|
"📦 *3. Catálogo de WhatsApp Business (Meta Commerce):*\n"
|
||||||
"✨ *3. Etiquetas Especiales de Tendencia:*\n"
|
"• Los feeds XML (`/api/catalog/feed.xml`) y CSV (`/api/catalog/feed.csv`) ahora se sincronizan en tiempo real con Appwrite sin demoras de caché.\n"
|
||||||
"• Badges contextuales automáticos: *🔥 Top Ventas*, *⚡ Más Pedido*, *★ Drop Exclusivo*, *✨ Tendencia*, *🚀 2026 Release*, *💎 Premium*.\n\n"
|
"• Las fotos en alta resolución, precios en COP, tallas y enlace de checkout quedan disponibles automáticamente para Meta.\n\n"
|
||||||
"💰 *4. Precios Originales Tachados & Facilidad de Separación:*\n"
|
"📰 *4. Enrutamiento Seguro de Prensa y Noticias:*\n"
|
||||||
"• Precio actual destacado en color Neón Glow junto al precio original tachado.\n"
|
"• Los comunicados de prensa y grupos institucionales (Gob Cauca, Alcaldía, Inelpac) se redirigen de forma segura y transparente al portal de noticias `news.almeira.com.co`.\n\n"
|
||||||
"• Micro-banner integrado: *⚡ Separa con $10.000 y cancelas al recibir* para incentivar el pedido contraentrega.\n\n"
|
|
||||||
"📱 *5. Experiencia Unificada en Vista Rápida y Modal de Detalle:*\n"
|
|
||||||
"• La misma información enriquecida (descuento, estrellas, ventas, precios tachados y garantías de cambio) se replica de manera limpia al tocar el calzado.\n\n"
|
|
||||||
f"⬇️ *Descarga directa del APK:* {APK_URL}\n\n"
|
f"⬇️ *Descarga directa del APK:* {APK_URL}\n\n"
|
||||||
"Te adjunto de inmediato el archivo instalador APK actualizado para prueba en tu celular."
|
"Adjunto el instalador APK actualizado con todas las funciones operativas."
|
||||||
)
|
)
|
||||||
|
|
||||||
req_text = urllib.request.Request(
|
req_text = urllib.request.Request(
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export async function POST(req: Request) {
|
|||||||
try {
|
try {
|
||||||
const payload = await req.json();
|
const payload = await req.json();
|
||||||
|
|
||||||
// Reenviar asíncronamente a los webhooks de las tiendas de calzado (DripZone, Sneaky, etc.)
|
// Reenviar de forma segura a los webhooks de las tiendas de calzado (DripZone, Sneaky, etc.)
|
||||||
const MULTISTORE_WEBHOOKS = [
|
const MULTISTORE_WEBHOOKS = [
|
||||||
'https://drip-zone.online/api/webhook/whatsapp',
|
'https://drip-zone.online/api/webhook/whatsapp',
|
||||||
'https://dripzone.almeira.com.co/api/webhook/whatsapp',
|
'https://dripzone.almeira.com.co/api/webhook/whatsapp',
|
||||||
@ -57,14 +57,18 @@ export async function POST(req: Request) {
|
|||||||
'https://store.almeira.com.co/api/webhook/whatsapp',
|
'https://store.almeira.com.co/api/webhook/whatsapp',
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const targetUrl of MULTISTORE_WEBHOOKS) {
|
try {
|
||||||
fetch(targetUrl, {
|
await Promise.allSettled(
|
||||||
method: 'POST',
|
MULTISTORE_WEBHOOKS.map(targetUrl =>
|
||||||
headers: { 'Content-Type': 'application/json' },
|
fetch(targetUrl, {
|
||||||
body: JSON.stringify(payload),
|
method: 'POST',
|
||||||
signal: AbortSignal.timeout(60000),
|
headers: { 'Content-Type': 'application/json' },
|
||||||
}).catch(() => {});
|
body: JSON.stringify(payload),
|
||||||
}
|
signal: AbortSignal.timeout(12000),
|
||||||
|
}).catch(() => {})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
const rawEvent = payload.event || '';
|
const rawEvent = payload.event || '';
|
||||||
const eventType = rawEvent.toLowerCase().replace(/_/g, '.');
|
const eventType = rawEvent.toLowerCase().replace(/_/g, '.');
|
||||||
|
|||||||
@ -30,5 +30,12 @@
|
|||||||
".next/dev/types/**/*.ts",
|
".next/dev/types/**/*.ts",
|
||||||
"**/*.mts"
|
"**/*.mts"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dripzone-app",
|
||||||
|
"sneaky-app",
|
||||||
|
"zapply-app",
|
||||||
|
"almeira-store-app",
|
||||||
|
"almeira-live"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user