From a8b6d62dd62dc559bc2e63afa6d5d6cf3220efc8 Mon Sep 17 00:00:00 2001 From: Carlos Narro Date: Tue, 9 Jun 2026 17:28:56 +0200 Subject: [PATCH] =?UTF-8?q?Bot=20responde=20a=20mensajes=20entrantes:=20re?= =?UTF-8?q?cupera=20lead=20por=20tel=C3=A9fono=20+=20fix=20matching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El bot no respondía a las réplicas del cliente: la sesión lead↔teléfono vivía solo en memoria y no casaba (se pierde al reiniciar el contenedor). Ahora si no está en memoria, getOrCreateContext busca el lead en la BD por teléfono vía un EP nuevo GET /api/leads/by-phone (match por últimos 9 dígitos) y re-registra la sesión. Aparte, los ids de modelo de Claude del bot estaban mal (-4-5 vs 4.5). Co-Authored-By: Claude Opus 4.8 --- .../src/api/api-client.service.ts | 14 +++++++ .../src/webhook/webhook-listener.ts | 8 ++++ .../src/whatsapp/whatsapp.service.ts | 11 +++++- mvp/b2c/src/app/api/leads/by-phone/route.ts | 37 +++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 mvp/b2c/src/app/api/leads/by-phone/route.ts diff --git a/mvp/Whatsapp-bot/src/api/api-client.service.ts b/mvp/Whatsapp-bot/src/api/api-client.service.ts index 127e081..6ebe0c2 100644 --- a/mvp/Whatsapp-bot/src/api/api-client.service.ts +++ b/mvp/Whatsapp-bot/src/api/api-client.service.ts @@ -45,6 +45,20 @@ export class ApiClient { } } + async buscarLeadPorTelefono(telefono: string): Promise { + try { + const { data } = await axios.get(`${this.baseUrl}/api/leads/by-phone`, { + headers: this.headers, + params: { telefono }, + }); + return data?.leadId ?? null; + } catch (err: any) { + if (err.response?.status === 404) return null; + this.logger.error(`buscarLeadPorTelefono error: ${err.message}`); + return null; + } + } + async guardarConversacion( leadId: string, rol: 'user' | 'assistant' | 'system', diff --git a/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts b/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts index 1f0855d..6de1640 100644 --- a/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts +++ b/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts @@ -171,6 +171,14 @@ export class WebhookListener implements OnApplicationBootstrap { } } + // Re-registra una sesión recuperada de la BD (cuando no estaba en memoria, p. ej. tras reinicio). + ensureSession(telefono: string, leadId: string, nombre = '') { + const tel = this.normTel(telefono); + if (!this.leadSessions.has(tel)) { + this.leadSessions.set(tel, { leadId, telefono: tel, nombre, jid: null }); + } + } + private async handleWhatsappPdf(payload: { leadId: string; telefono: string; pdfBase64: string; filename: string }) { this.logger.log(`[PDF] leadId=${payload.leadId}, filename=${payload.filename}`); const { pdfEmitter } = await import('../whatsapp/whatsapp.service'); diff --git a/mvp/Whatsapp-bot/src/whatsapp/whatsapp.service.ts b/mvp/Whatsapp-bot/src/whatsapp/whatsapp.service.ts index 5e14ccc..9fce962 100644 --- a/mvp/Whatsapp-bot/src/whatsapp/whatsapp.service.ts +++ b/mvp/Whatsapp-bot/src/whatsapp/whatsapp.service.ts @@ -263,7 +263,16 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy { } private async getOrCreateContext(telefono: string, jid: string): Promise { - const leadId = this.webhookListener.getLeadIdByTelefono(telefono); + let leadId = this.webhookListener.getLeadIdByTelefono(telefono); + + // Fallback: si no está en memoria (reinicio del bot), recuperarlo de la BD por teléfono. + if (!leadId) { + leadId = await this.api.buscarLeadPorTelefono(telefono); + if (leadId) { + this.webhookListener.ensureSession(telefono, leadId); + this.logger.log(`Lead ${leadId} recuperado por teléfono ${telefono} (sin sesión en memoria).`); + } + } if (!leadId) { this.logger.log(`Mensaje ignorado de ${telefono}: lead no registrado. Debe iniciarse desde la web.`); diff --git a/mvp/b2c/src/app/api/leads/by-phone/route.ts b/mvp/b2c/src/app/api/leads/by-phone/route.ts new file mode 100644 index 0000000..d8570b3 --- /dev/null +++ b/mvp/b2c/src/app/api/leads/by-phone/route.ts @@ -0,0 +1,37 @@ +import { desc, like } from 'drizzle-orm'; +import { db } from '@/db'; +import { leads } from '@/db/schema'; +import { autorizado, jsonResponse } from '@/lib/api/funnel-auth'; + +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; + +// Busca el lead más reciente por teléfono (comparando los últimos 9 dígitos, ignorando prefijos y +// formato). Lo usa el bot de WhatsApp para recuperar el leadId de un mensaje entrante cuando no +// tiene la sesión en memoria (p. ej. tras un reinicio del contenedor). +export async function GET(req: Request) { + if (!autorizado(req)) return jsonResponse({ ok: false, error: 'No autorizado.' }, 401); + + const tel = (new URL(req.url).searchParams.get('telefono') ?? '').replace(/\D/g, ''); + if (tel.length < 6) return jsonResponse({ ok: false, error: 'telefono inválido.' }, 422); + const last9 = tel.slice(-9); + + const [lead] = await db + .select({ + id: leads.id, + nombre: leads.nombre, + telefono: leads.telefono, + botStep: leads.botStep, + viable: leads.viable, + }) + .from(leads) + .where(like(leads.telefono, `%${last9}%`)) + .orderBy(desc(leads.createdAt)) + .limit(1); + + if (!lead) return jsonResponse({ ok: false, error: 'Lead no encontrado.' }, 404); + return jsonResponse( + { leadId: lead.id, nombre: lead.nombre, telefono: lead.telefono, botStep: lead.botStep, viable: lead.viable }, + 200, + ); +}