import { NextRequest, NextResponse } from "next/server"; import { procesarMensaje } from "@/lib/whatsapp-bot"; import { enviarRespuestas, firmaValida, marcarLeido, mensajesDelPayload, textoDeMensaje, type PayloadWebhook, } from "@/lib/whatsapp"; export const dynamic = "force-dynamic"; /** * Webhook de WhatsApp Business Cloud API. * GET → verificación del webhook al configurarlo en el panel de Meta. * POST → mensajes entrantes: se los pasa a la máquina de conversación y se contesta. */ // Meta reintenta el mismo mensaje si tardamos o si algo falla. Guardamos los ids ya // procesados en memoria (un solo container) para no duplicar turnos ni respuestas. const procesados = new Set(); const MAX_PROCESADOS = 500; function yaProcesado(id: string): boolean { if (procesados.has(id)) return true; procesados.add(id); // Set mantiene orden de inserción: soltamos los más viejos. while (procesados.size > MAX_PROCESADOS) { const viejo: string | undefined = procesados.values().next().value; if (viejo === undefined) break; procesados.delete(viejo); } return false; } export async function GET(req: NextRequest) { const params = req.nextUrl.searchParams; const modo = params.get("hub.mode"); const token = params.get("hub.verify_token"); const challenge = params.get("hub.challenge"); const esperado = process.env.WHATSAPP_VERIFY_TOKEN; if (modo === "subscribe" && esperado && token === esperado && challenge) return new NextResponse(challenge, { status: 200, headers: { "Content-Type": "text/plain" } }); return new NextResponse("Forbidden", { status: 403 }); } export async function POST(req: NextRequest) { // La firma se calcula sobre el cuerpo CRUDO: leerlo como texto y recién ahí parsear. const cuerpoCrudo = await req.text(); if (!firmaValida(cuerpoCrudo, req.headers.get("x-hub-signature-256"))) { console.warn("[whatsapp] webhook con firma inválida — descartado"); return new NextResponse("Forbidden", { status: 403 }); } let body: PayloadWebhook; try { body = JSON.parse(cuerpoCrudo) as PayloadWebhook; } catch { return NextResponse.json({ ok: true }); } try { for (const mensaje of mensajesDelPayload(body)) { const telefono = mensaje.from; const messageId = mensaje.id; if (!telefono || !messageId || yaProcesado(messageId)) continue; void marcarLeido(messageId).catch(() => {}); const texto = textoDeMensaje(mensaje); if (texto === null) { await enviarRespuestas(telefono, [ { tipo: "texto", texto: "Por ahora solo entiendo mensajes de texto. Escribime qué necesitás. 🙂", }, ]); continue; } const respuestas = await procesarMensaje(telefono, texto); await enviarRespuestas(telefono, respuestas); } } catch (e) { // Nunca devolvemos error a Meta: reintentaría el mismo mensaje en loop. console.error("[whatsapp] error procesando webhook:", e); } return NextResponse.json({ ok: true }); }