Configuracion de prompt Luisa

This commit is contained in:
unknown
2026-06-02 22:48:59 -04:00
parent 9a8f84ff37
commit 9c5e6bc7fa
8 changed files with 841 additions and 196 deletions

View File

@@ -3,38 +3,44 @@ import {
Logger,
OnModuleInit,
OnModuleDestroy,
} from '@nestjs/common';
} from "@nestjs/common";
import makeWASocket, {
DisconnectReason,
useMultiFileAuthState,
fetchLatestBaileysVersion,
WASocket,
downloadMediaMessage,
proto,
} from '@whiskeysockets/baileys';
import { Boom } from '@hapi/boom';
import * as path from 'path';
const pino = require('pino');
const QRCode = require('qrcode-terminal');
import { LeadsService } from '../leads/leads.service';
import { ConversacionService } from '../conversacion/conversacion.service';
import { ClaudeService } from '../claude/claude.service';
import { MediaService } from '../media/media.service';
import { Lead } from '../leads/lead.entity';
import { wrapSocket } from 'baileys-antiban';
} from "@whiskeysockets/baileys";
import { Boom } from "@hapi/boom";
import * as path from "path";
const pino = require("pino");
const QRCode = require("qrcode-terminal");
import { LeadsService } from "../leads/leads.service";
import { ConversacionService } from "../conversacion/conversacion.service";
import { ClaudeService } from "../claude/claude.service";
import { MediaService } from "../media/media.service";
import { wrapSocket } from "baileys-antiban";
const ESTADOS_TERMINALES = [
"completado",
"no_viable",
"perdido",
"fin_viable",
"fin_no_viable",
];
@Injectable()
export class WhatsappService implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(WhatsappService.name);
private sock: WASocket | null = null;
private authDir = path.join(process.cwd(), 'auth_info_baileys');
private authDir = path.join(process.cwd(), "auth_info_baileys");
constructor(
private readonly leadsService: LeadsService,
private readonly conversacionService: ConversacionService,
private readonly claudeService: ClaudeService,
private readonly mediaService: MediaService,
) { }
) {}
async onModuleInit() {
await this.conectar();
@@ -46,6 +52,21 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
}
}
private normalizarTelefono(jid: string): string {
return jid.split("@")[0].replace(/\D/g, "");
}
private calcularDelayEscritura(longitudTexto: number): number {
const min = 1500;
const max = 4000;
const factor = Math.min(longitudTexto / 120, 1);
return Math.round(min + (max - min) * factor);
}
private delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
private async conectar() {
const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
const { version } = await fetchLatestBaileysVersion();
@@ -54,23 +75,23 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
version,
auth: state,
printQRInTerminal: false,
logger: pino({ level: 'info' }) as any,
logger: pino({ level: "info" }) as any,
markOnlineOnConnect: false,
generateHighQualityLinkPreview: false,
syncFullHistory: false,
});
this.sock.ev.on('creds.update', saveCreds);
this.sock.ev.on("creds.update", saveCreds);
this.sock.ev.on('connection.update', (update) => {
this.sock.ev.on("connection.update", (update) => {
const { connection, lastDisconnect, qr } = update;
if (qr) {
QRCode.generate(qr, { small: true });
console.log('\n📲 Escanea este QR con WhatsApp\n');
console.log("\n📲 Escanea este QR con WhatsApp\n");
}
if (connection === 'close') {
if (connection === "close") {
const shouldReconnect =
(lastDisconnect?.error as Boom)?.output?.statusCode !==
DisconnectReason.loggedOut;
@@ -83,23 +104,34 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
setTimeout(() => this.conectar(), 5000);
} else {
this.logger.error(
'Sesion cerrada (logged out). Elimina auth_info_baileys y reinicia.',
"Sesion cerrada (logged out). Elimina auth_info_baileys y reinicia.",
);
}
} else if (connection === 'open') {
} else if (connection === "open") {
this.logger.log(
'✅ WhatsApp conectado. Luisa esta lista para recibir mensajes.',
"✅ WhatsApp conectado. Luisa esta lista para recibir mensajes.",
);
}
});
this.sock.ev.on('messages.upsert', async ({ messages, type }) => {
if (type !== 'notify') return;
this.sock.ev.on("messages.upsert", async ({ messages, type }) => {
if (type !== "notify") return;
for (const msg of messages) {
if (msg.key.fromMe) continue;
if (!msg.key.remoteJid) continue;
if (msg.key.remoteJid.includes('@g.us')) continue;
if (msg.key.remoteJid.includes("@g.us")) continue;
const telefonoNormalizado = this.normalizarTelefono(msg.key.remoteJid);
const allowedNumber = process.env.ALLOWED_NUMBER?.replace(/\D/g, "");
if (allowedNumber && telefonoNormalizado !== allowedNumber) {
this.logger.debug(
`Mensaje ignorado: ${telefonoNormalizado} no coincide con ALLOWED_NUMBER`,
);
continue;
}
await this.procesarMensaje(msg);
}
});
@@ -108,46 +140,45 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
private async procesarMensaje(msg: any): Promise<void> {
const jid = msg.key.remoteJid!;
// Ignorar grupos
if (jid.includes('@g.us')) return;
if (jid.includes("@g.us")) return;
// Normalizar JID para envio (manejar formato LID de Baileys v7)
const jidNormalizado = jid.includes('@lid')
? `${jid.split('@')[0]}@s.whatsapp.net`
: jid;
const telefono = jidNormalizado.replace('@s.whatsapp.net', '');
const telefono = jid.split("@")[0];
try {
const lead = await this.leadsService.findOrCreate(telefono);
if (['completado', 'no_viable', 'perdido'].includes(lead.estado_actual)) {
this.logger.log(`Lead id=${lead.id} en estado=${lead.estado_actual}. Ignorando.`);
if (ESTADOS_TERMINALES.includes(lead.estado_actual)) {
this.logger.log(
`Lead id=${lead.id} en estado=${lead.estado_actual}. Ignorando.`,
);
return;
}
let textoNormalizado = '';
let textoNormalizado = "";
const msgContent = msg.message;
if (!msgContent) return;
if (msgContent.conversation || msgContent.extendedTextMessage) {
textoNormalizado =
msgContent.conversation ||
msgContent.extendedTextMessage?.text ||
'';
msgContent.conversation || msgContent.extendedTextMessage?.text || "";
} else if (msgContent.audioMessage) {
this.logger.log(`Audio recibido de lead id=${lead.id}. Transcribiendo...`);
const buffer = await downloadMediaMessage(msg as any, 'buffer', {});
const mimeType = msgContent.audioMessage.mimetype || 'audio/ogg; codecs=opus';
this.logger.log(
`Audio recibido de lead id=${lead.id}. Transcribiendo...`,
);
const buffer = await downloadMediaMessage(msg as any, "buffer", {});
const mimeType =
msgContent.audioMessage.mimetype || "audio/ogg; codecs=opus";
textoNormalizado = await this.mediaService.transcribirAudio(
buffer as Buffer,
mimeType,
);
} else if (msgContent.imageMessage) {
this.logger.log(`Imagen recibida de lead id=${lead.id}. Analizando con Vision...`);
const buffer = await downloadMediaMessage(msg as any, 'buffer', {});
const mimeType = msgContent.imageMessage.mimetype || 'image/jpeg';
this.logger.log(
`Imagen recibida de lead id=${lead.id}. Analizando con Vision...`,
);
const buffer = await downloadMediaMessage(msg as any, "buffer", {});
const mimeType = msgContent.imageMessage.mimetype || "image/jpeg";
textoNormalizado = await this.mediaService.inferirImagen(
buffer as Buffer,
mimeType,
@@ -157,7 +188,9 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
textoNormalizado = `${msgContent.imageMessage.caption}\n\n[Contenido de la imagen: ${textoNormalizado}]`;
}
} else {
this.logger.log(`Tipo de mensaje no soportado de lead id=${lead.id}. Ignorando.`);
this.logger.log(
`Tipo de mensaje no soportado de lead id=${lead.id}. Ignorando.`,
);
return;
}
@@ -165,34 +198,47 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
this.logger.log(`USUARIO [${telefono}]: ${textoNormalizado}`);
await this.conversacionService.guardarMensaje(lead.id, 'user', textoNormalizado);
const historial = await this.conversacionService.obtenerHistorialComoMessages(lead.id);
const { respuesta, entidad, viable } = await this.claudeService.llamarClaude(
lead,
historial.slice(0, -1),
await this.conversacionService.guardarMensaje(
lead.id,
"user",
textoNormalizado,
);
const historial =
await this.conversacionService.obtenerHistorialComoMessages(lead.id);
const { respuesta, entidad, viable, nuevoEstado } =
await this.claudeService.llamarClaude(
lead,
historial.slice(0, -1),
textoNormalizado,
);
this.logger.log(`LUISA [${telefono}]: ${respuesta}`);
if (entidad && Object.keys(entidad).length > 0) {
await this.leadsService.updateDatos(lead.id, entidad);
}
if (viable !== undefined && viable !== null) {
if (nuevoEstado === "fin_viable" || nuevoEstado === "fin_no_viable") {
await this.leadsService.marcarViable(
lead,
nuevoEstado === "fin_viable",
);
this.logger.log(`Lead id=${lead.id} finalizado como ${nuevoEstado}`);
} else if (nuevoEstado) {
await this.leadsService.updateEstado(lead, nuevoEstado);
this.logger.log(`Lead id=${lead.id} avanzado a estado=${nuevoEstado}`);
} else if (viable !== undefined && viable !== null) {
await this.leadsService.marcarViable(lead, viable);
this.logger.log(`Lead id=${lead.id} marcado como viable=${viable}`);
} else {
if (lead.estado_actual === 'nuevo') {
await this.leadsService.updateEstado(lead, 'en_proceso');
}
}
await this.conversacionService.guardarMensaje(lead.id, 'assistant', respuesta);
await this.conversacionService.guardarMensaje(
lead.id,
"assistant",
respuesta,
);
await this.enviarMensaje(jid, respuesta);
} catch (error) {
this.logger.error(
`Error procesando mensaje de ${telefono}: ${error.message}`,
@@ -200,12 +246,19 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
);
}
}
async enviarMensaje(jid: string, texto: string): Promise<void> {
if (!this.sock) {
this.logger.error('Socket de WhatsApp no disponible');
return;
}
if (!this.sock) return;
try {
const jidPresencia = jid.includes("@lid")
? `${jid.split("@")[0]}@s.whatsapp.net`
: jid;
await this.sock.sendPresenceUpdate("composing", jidPresencia);
await this.delay(this.calcularDelayEscritura(texto.length));
await this.sock.sendPresenceUpdate("paused", jidPresencia);
const safeSock = wrapSocket(this.sock);
await safeSock.sendMessage(jid, { text: texto });
this.logger.log(`Mensaje enviado a ${jid}`);
@@ -214,7 +267,10 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
}
}
async enviarApertura(telefono: string, mensajeApertura: string): Promise<void> {
async enviarApertura(
telefono: string,
mensajeApertura: string,
): Promise<void> {
const jid = `${telefono}@s.whatsapp.net`;
await this.enviarMensaje(jid, mensajeApertura);
}
@@ -222,4 +278,4 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
isConectado(): boolean {
return this.sock !== null;
}
}
}