164 lines
5.8 KiB
TypeScript
164 lines
5.8 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { dataService } from '@/lib/data-service';
|
|
import { parseSupplierMessage } from '@/lib/gemini-parser';
|
|
import { SupplierPost, Product } from '@/types';
|
|
import { calculateRetailPrice } from '@/lib/pricing-engine';
|
|
|
|
export async function GET() {
|
|
const posts = dataService.getSupplierPosts();
|
|
const settings = dataService.getSettings();
|
|
return NextResponse.json({
|
|
success: true,
|
|
autoPublishEnabled: settings.autoPublishSupplierPosts ?? true,
|
|
postsCount: posts.length,
|
|
posts,
|
|
});
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const payload = await request.json();
|
|
console.log('📥 [WhatsApp Webhook] Mensaje recibido:', JSON.stringify(payload).slice(0, 300));
|
|
|
|
const data = payload.data || payload;
|
|
const settings = dataService.getSettings();
|
|
|
|
// 1. Extraer texto del mensaje de WhatsApp
|
|
const messageContent =
|
|
data.message?.conversation ||
|
|
data.message?.extendedTextMessage?.text ||
|
|
data.message?.imageMessage?.caption ||
|
|
data.text ||
|
|
'';
|
|
|
|
// 2. Extraer nombre del canal o grupo
|
|
const senderPhone = data.key?.remoteJid?.replace(/\D/g, '') || '';
|
|
const isGroup = data.key?.remoteJid?.includes('g.us');
|
|
const channelName =
|
|
data.groupInfo?.subject ||
|
|
data.pushName ||
|
|
(isGroup ? 'Grupo Mayorista WhatsApp' : 'Chat Proveedor Directo');
|
|
|
|
if (!messageContent || messageContent.trim().length < 6) {
|
|
return NextResponse.json({
|
|
success: true,
|
|
ignored: 'Mensaje sin texto suficiente para extraer calzado.',
|
|
});
|
|
}
|
|
|
|
// 3. Buscar proveedor asociado por teléfono o grupo
|
|
const suppliers = dataService.getSuppliers();
|
|
const matchedSupplier = suppliers.find(
|
|
(s) =>
|
|
s.phone.includes(senderPhone) ||
|
|
(channelName && s.name.toLowerCase().includes(channelName.toLowerCase())) ||
|
|
(channelName && channelName.toLowerCase().includes(s.name.toLowerCase()))
|
|
);
|
|
|
|
const supplierName = matchedSupplier?.name || channelName;
|
|
const supplierPhone = matchedSupplier?.phone || senderPhone || '573167455877';
|
|
|
|
// 4. Procesar y extraer información con Gemini AI
|
|
const extracted = await parseSupplierMessage(messageContent);
|
|
|
|
// Recalcular precio con el margen y redondeo al $5.000 oficial
|
|
const finalRetailPrice = calculateRetailPrice(
|
|
extracted.supplierPrice,
|
|
settings.defaultProfitMargin || 35
|
|
);
|
|
|
|
// Extraer imágenes (si Evolution API envió imagen adjunta o usar fotos de alta resolución)
|
|
const images: string[] = [];
|
|
if (data.message?.imageMessage?.url) {
|
|
images.push(data.message.imageMessage.url);
|
|
} else {
|
|
images.push(
|
|
'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80'
|
|
);
|
|
}
|
|
|
|
// 5. Verificar Anti-Duplicados en el Catálogo
|
|
const existingProducts = dataService.getProducts();
|
|
const duplicateProduct = existingProducts.find(
|
|
(p) => p.name.toLowerCase() === extracted.name.toLowerCase()
|
|
);
|
|
|
|
let registeredProduct: Product | null = null;
|
|
const autoPublish = settings.autoPublishSupplierPosts ?? true;
|
|
|
|
if (autoPublish) {
|
|
if (duplicateProduct) {
|
|
// Actualizar stock y tallas existentes sin duplicar
|
|
const mergedSizes = Array.from(new Set([...duplicateProduct.sizes, ...extracted.sizes]));
|
|
registeredProduct = dataService.updateProduct(duplicateProduct.id, {
|
|
supplierPrice: extracted.supplierPrice,
|
|
retailPrice: finalRetailPrice,
|
|
sizes: mergedSizes,
|
|
inStock: true,
|
|
isActive: true,
|
|
supplierName,
|
|
supplierPhone,
|
|
});
|
|
console.log(`🔄 [Auto-Sync] Zapatilla "${extracted.name}" actualizada en catálogo (Anti-Duplicados).`);
|
|
} else {
|
|
// Registrar y publicar automáticamente
|
|
registeredProduct = dataService.addProduct({
|
|
name: extracted.name,
|
|
brand: extracted.brand,
|
|
supplierPrice: extracted.supplierPrice,
|
|
retailPrice: finalRetailPrice,
|
|
originalPrice: Math.round(finalRetailPrice * 1.35),
|
|
sizes: extracted.sizes,
|
|
images,
|
|
description: `Zapatillas ${extracted.brand} ${extracted.name} garantizadas. Calidad seleccionada con horma nacional y suela antideslizante.`,
|
|
category: extracted.category || 'Sneakers / Urbano',
|
|
inStock: true,
|
|
isActive: true,
|
|
isFlashDeal: true,
|
|
rating: 4.9,
|
|
reviewsCount: 15,
|
|
salesCount: 30,
|
|
originBadge: 'Bodega Mayorista',
|
|
supplierId: matchedSupplier?.id || 'sup-auto',
|
|
supplierName,
|
|
supplierPhone,
|
|
});
|
|
console.log(`✨ [Auto-Publish] Nueva zapatilla "${extracted.name}" publicada automáticamente en tienda.`);
|
|
}
|
|
}
|
|
|
|
// 6. Registrar en el historial de publicaciones recibidas
|
|
const newPost: SupplierPost = {
|
|
id: `post-${Date.now()}`,
|
|
channelName,
|
|
supplierName,
|
|
supplierPhone,
|
|
rawMessage: messageContent,
|
|
rawImages: images,
|
|
extractedProduct: {
|
|
...extracted,
|
|
suggestedRetailPrice: finalRetailPrice,
|
|
supplierName,
|
|
supplierPhone,
|
|
},
|
|
status: autoPublish ? 'published_store' : 'pending_review',
|
|
receivedAt: 'Justo ahora',
|
|
};
|
|
|
|
dataService.addSupplierPost(newPost);
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
autoPublished: autoPublish,
|
|
product: registeredProduct,
|
|
post: newPost,
|
|
message: autoPublish
|
|
? `🔥 ¡Zapatilla "${extracted.name}" procesada y publicada AUTOMÁTICAMENTE en la tienda!`
|
|
: `Mensaje ingresado al feed para aprobación manual.`,
|
|
});
|
|
} catch (error: any) {
|
|
console.error('Error procesando webhook de WhatsApp:', error);
|
|
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
|
}
|
|
}
|