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, + ); +}