diff --git a/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts b/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts index 6de1640..5965ad7 100644 --- a/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts +++ b/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts @@ -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 = {}; + 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) { + this.connState = o; + } + pushInbound(o: Record) { + 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"', diff --git a/mvp/Whatsapp-bot/src/whatsapp/whatsapp.service.ts b/mvp/Whatsapp-bot/src/whatsapp/whatsapp.service.ts index 9fce962..7aa0077 100644 --- a/mvp/Whatsapp-bot/src/whatsapp/whatsapp.service.ts +++ b/mvp/Whatsapp-bot/src/whatsapp/whatsapp.service.ts @@ -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;