Bot: endpoint /debug para diagnosticar entrantes + estado de conexión
GET /debug (Basic auth, QR_TOKEN) devuelve el estado de la conexión de WhatsApp y un anillo de los últimos mensajes entrantes (remoteJid real, fromMe, tipo) y el resultado del matching lead↔teléfono. Para diagnosticar por qué el bot no responde (conexión zombi vs formato de jid @lid vs no llega el mensaje). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,9 @@ export class WebhookListener implements OnApplicationBootstrap {
|
||||
// Estado de vinculación de WhatsApp, alimentado por WhatsappService (sin dependencia circular).
|
||||
private qrActual: string | null = null;
|
||||
private conectado = false;
|
||||
// Diagnóstico: estado de conexión + últimos eventos entrantes (anillo de 15).
|
||||
private connState: Record<string, unknown> = {};
|
||||
private inbound: any[] = [];
|
||||
|
||||
constructor(private readonly api: ApiClient) {}
|
||||
|
||||
@@ -33,12 +36,20 @@ export class WebhookListener implements OnApplicationBootstrap {
|
||||
this.conectado = b;
|
||||
if (b) this.qrActual = null;
|
||||
}
|
||||
setConnState(o: Record<string, unknown>) {
|
||||
this.connState = o;
|
||||
}
|
||||
pushInbound(o: Record<string, unknown>) {
|
||||
this.inbound.unshift(o);
|
||||
if (this.inbound.length > 15) this.inbound.pop();
|
||||
}
|
||||
|
||||
private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse) {
|
||||
const url = req.url || '';
|
||||
|
||||
if (req.method === 'GET') {
|
||||
if (url.startsWith('/qr')) return this.handleQrPage(req, res);
|
||||
if (url.startsWith('/debug')) return this.handleDebug(req, res);
|
||||
res.writeHead(404).end('Not Found');
|
||||
return;
|
||||
}
|
||||
@@ -84,15 +95,37 @@ export class WebhookListener implements OnApplicationBootstrap {
|
||||
return crypto.timingSafeEqual(ha, hb);
|
||||
}
|
||||
|
||||
// Página de vinculación: muestra el QR de Baileys como imagen escaneable.
|
||||
// Auth por cabecera (HTTP Basic, contraseña = QR_TOKEN), nunca por query string.
|
||||
private async handleQrPage(req: http.IncomingMessage, res: http.ServerResponse) {
|
||||
// Auth Basic: contraseña = QR_TOKEN (en cabecera, nunca en la URL).
|
||||
private basicAuthOk(req: http.IncomingMessage): boolean {
|
||||
const expected = process.env.QR_TOKEN || '';
|
||||
const auth = (req.headers['authorization'] as string) || '';
|
||||
const pass = auth.startsWith('Basic ')
|
||||
? Buffer.from(auth.slice(6), 'base64').toString('utf8').split(':').slice(1).join(':')
|
||||
: '';
|
||||
if (!this.tokenValido(pass, expected)) {
|
||||
return this.tokenValido(pass, expected);
|
||||
}
|
||||
|
||||
// Diagnóstico: estado de conexión + últimos eventos entrantes. Misma auth que /qr.
|
||||
private handleDebug(req: http.IncomingMessage, res: http.ServerResponse) {
|
||||
if (!this.basicAuthOk(req)) {
|
||||
res
|
||||
.writeHead(401, {
|
||||
'WWW-Authenticate': 'Basic realm="Reformix QR", charset="UTF-8"',
|
||||
'Content-Type': 'application/json',
|
||||
'Referrer-Policy': 'no-referrer',
|
||||
})
|
||||
.end(JSON.stringify({ ok: false, error: 'No autorizado' }));
|
||||
return;
|
||||
}
|
||||
res
|
||||
.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store', 'Referrer-Policy': 'no-referrer' })
|
||||
.end(JSON.stringify({ conectado: this.conectado, connState: this.connState, inbound: this.inbound }, null, 2));
|
||||
}
|
||||
|
||||
// Página de vinculación: muestra el QR de Baileys como imagen escaneable.
|
||||
// Auth por cabecera (HTTP Basic, contraseña = QR_TOKEN), nunca por query string.
|
||||
private async handleQrPage(req: http.IncomingMessage, res: http.ServerResponse) {
|
||||
if (!this.basicAuthOk(req)) {
|
||||
res
|
||||
.writeHead(401, {
|
||||
'WWW-Authenticate': 'Basic realm="Reformix QR", charset="UTF-8"',
|
||||
|
||||
@@ -196,6 +196,13 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
|
||||
this.sock.ev.on('connection.update', (update) => {
|
||||
const { connection, lastDisconnect, qr } = update;
|
||||
|
||||
this.webhookListener.setConnState({
|
||||
connection: connection ?? null,
|
||||
hasQr: !!qr,
|
||||
lastDisconnect: (lastDisconnect?.error as Boom)?.output?.statusCode ?? null,
|
||||
at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (qr) {
|
||||
QRCode.generate(qr, { small: true });
|
||||
console.log('\n📲 Escanea este QR con WhatsApp (o abre la página /qr, protegida con QR_TOKEN)\n');
|
||||
@@ -218,6 +225,12 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
|
||||
this.sock.ev.on('messages.upsert', async ({ messages, type }) => {
|
||||
if (type !== 'notify') return;
|
||||
for (const msg of messages) {
|
||||
this.webhookListener.pushInbound({
|
||||
remoteJid: msg.key.remoteJid ?? null,
|
||||
fromMe: !!msg.key.fromMe,
|
||||
msgType: msg.message ? Object.keys(msg.message)[0] : null,
|
||||
at: new Date().toISOString(),
|
||||
});
|
||||
if (msg.key.fromMe) continue;
|
||||
if (!msg.key.remoteJid) continue;
|
||||
if (msg.key.remoteJid.includes('@g.us')) continue;
|
||||
@@ -274,6 +287,7 @@ export class WhatsappService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
this.webhookListener.pushInbound({ stage: 'match', telefono, leadId: leadId ?? null, at: new Date().toISOString() });
|
||||
if (!leadId) {
|
||||
this.logger.log(`Mensaje ignorado de ${telefono}: lead no registrado. Debe iniciarse desde la web.`);
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user