Bot: enviar apertura proactiva al recibir /whatsapp-start + fix clave teléfono
El funnel promete "te escribimos por WhatsApp" pero el bot solo registraba la sesión y esperaba a que el cliente escribiera primero → no llegaba nada. Ahora WhatsappService escucha un startEmitter y manda el mensaje de apertura de Luisa al teléfono (verifica el número con onWhatsApp), persiste estadoWa/botStep y el intento. Además normaliza la clave de teléfono a solo-dígitos en leadSessions (antes "+34..." no casaba con los dígitos del jid entrante → ignoraba al cliente). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -143,20 +143,29 @@ export class WebhookListener implements OnApplicationBootstrap {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private leadSessions = new Map<string, { leadId: string; telefono: string; nombre: string; jid: string | null }>();
|
||||
|
||||
private normTel(t: string): string {
|
||||
return (t || '').replace(/\D/g, '');
|
||||
}
|
||||
|
||||
private async handleWhatsappStart(payload: { leadId: string; telefono: string; nombre: string; empresa: string }) {
|
||||
const { leadId, telefono, nombre } = payload;
|
||||
const { leadId, nombre, empresa } = payload;
|
||||
const telefono = this.normTel(payload.telefono);
|
||||
this.logger.log(`[START] leadId=${leadId}, telefono=${telefono}, nombre=${nombre}`);
|
||||
|
||||
this.leadSessions.set(telefono, { leadId, telefono, nombre, jid: null });
|
||||
this.logger.log(`Lead ${leadId} registrado en sesiones.`);
|
||||
|
||||
// Dispara la apertura proactiva (la envía WhatsappService, evitando dependencia circular).
|
||||
const { startEmitter } = await import('../whatsapp/whatsapp.service');
|
||||
startEmitter.emit('start', { leadId, telefono, nombre, empresa });
|
||||
this.logger.log(`Lead ${leadId} registrado; apertura disparada.`);
|
||||
}
|
||||
|
||||
getLeadIdByTelefono(telefono: string): string | null {
|
||||
return this.leadSessions.get(telefono)?.leadId ?? null;
|
||||
return this.leadSessions.get(this.normTel(telefono))?.leadId ?? null;
|
||||
}
|
||||
|
||||
registerJid(telefono: string, jid: string) {
|
||||
const session = this.leadSessions.get(telefono);
|
||||
const session = this.leadSessions.get(this.normTel(telefono));
|
||||
if (session) {
|
||||
session.jid = jid;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import { ApiClient } from '../api/api-client.service';
|
||||
import { wrapSocket } from 'baileys-antiban';
|
||||
|
||||
export const pdfEmitter = new EventEmitter();
|
||||
export const startEmitter = new EventEmitter();
|
||||
|
||||
interface LeadContext {
|
||||
leadId: string;
|
||||
@@ -62,6 +63,7 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
|
||||
async onModuleInit() {
|
||||
await this.conectar();
|
||||
this.escucharPdf();
|
||||
this.escucharStart();
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
@@ -98,6 +100,66 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
// Apertura proactiva: cuando el funnel dispara /whatsapp-start, Luisa escribe ella el primer
|
||||
// mensaje (el bot ya no es solo reactivo).
|
||||
private escucharStart() {
|
||||
startEmitter.on(
|
||||
'start',
|
||||
async (p: { leadId: string; telefono: string; nombre: string; empresa: string }) => {
|
||||
try {
|
||||
await this.enviarApertura(p);
|
||||
} catch (err: any) {
|
||||
this.logger.error(`[APERTURA] Error: ${err.message}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async enviarApertura(p: { leadId: string; telefono: string; nombre: string; empresa: string }) {
|
||||
if (!this.sock) {
|
||||
this.logger.warn(`[APERTURA] WhatsApp no conectado; no se envía a ${p.telefono}`);
|
||||
return;
|
||||
}
|
||||
const tel = (p.telefono || '').replace(/\D/g, '');
|
||||
let jid = `${tel}@s.whatsapp.net`;
|
||||
try {
|
||||
const res = await this.sock.onWhatsApp(tel);
|
||||
if (res && res[0]?.exists && res[0]?.jid) jid = res[0].jid;
|
||||
else if (!res || !res[0]?.exists) this.logger.warn(`[APERTURA] ${tel} no parece estar en WhatsApp`);
|
||||
} catch {
|
||||
/* seguimos con el jid por defecto */
|
||||
}
|
||||
|
||||
const primerNombre = (p.nombre || '').trim().split(' ')[0] || 'hola';
|
||||
const empresa = p.empresa || 'Reformix';
|
||||
const apertura =
|
||||
`¡Hola ${primerNombre}! Soy Luisa, del equipo de ${empresa}. 😊\n\n` +
|
||||
`Acabas de pedir presupuesto para tu reforma y te ayudo a prepararlo (con un render de cómo ` +
|
||||
`quedaría incluido). Para empezar, cuéntame: ¿qué espacio quieres reformar? (cocina, baño, salón…)`;
|
||||
|
||||
// Contexto para los siguientes mensajes del cliente.
|
||||
this.jidToLeadId.set(jid, p.leadId);
|
||||
this.webhookListener.registerJid(tel, jid);
|
||||
this.leadCache.set(p.leadId, {
|
||||
leadId: p.leadId,
|
||||
telefono: tel,
|
||||
nombre: p.nombre || '',
|
||||
botStep: 'apertura',
|
||||
viable: null,
|
||||
});
|
||||
|
||||
await this.enviarMensaje(jid, apertura);
|
||||
this.logger.log(`[APERTURA] Enviada a ${jid} (lead ${p.leadId})`);
|
||||
|
||||
try {
|
||||
await this.api.actualizarPerfil(p.leadId, { estadoWa: 'enviado', botStep: 'apertura', canalOrigen: 'whatsapp' });
|
||||
await this.conversacionService.guardarMensaje(p.leadId, 'assistant', apertura, { botStep: 'apertura' });
|
||||
await this.api.registrarIntento(p.leadId, 'whatsapp', 1, 'exitoso', true);
|
||||
} catch (err: any) {
|
||||
this.logger.warn(`[APERTURA] No se pudo persistir en la app: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private normalizarTelefono(jid: string): string {
|
||||
return jid.split('@')[0].replace(/\D/g, '');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user