diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1c43060 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,22 @@ +name: Build +on: + push: + branches: + - main +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + path: backend + - name: Use Node.js 16.x + uses: actions/setup-node@v2 + with: + node-version: '16.x' + - name: Install dependencies + run: npm install + working-directory: backend + - name: Build + run: npm run build + working-directory: backend diff --git a/README.md b/README.md index d3b47f1..f7843fd 100644 --- a/README.md +++ b/README.md @@ -1 +1,237 @@ -# WhaTicket \ No newline at end of file +## Deploy Ubuntu 22.x + +```bash + sudo apt-get install -y libgbm-dev wget unzip fontconfig locales gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils git +``` + +Instalar o pacote build-essential: + +```bash +sudo apt-get install build-essential +``` + +```bash +sudo apt update && sudo apt upgrade +``` + +Instale o node (16.x) e confirme se o comando do node -v e npm -v está disponível: + +```bash +curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - +sudo apt-get install -y nodejs +node -v +npm -v +``` + + +Instale o docker e adicione seu usuário ao grupo do docker: + +```bash +curl -fsSL https://get.docker.com -o get-docker.sh + +sudo sh get-docker.sh + +sudo usermod -aG docker ${USER} + +sudo apt-get install docker-compose +``` + +Instalar o Postgres Docker + +```bash +docker run -e TZ="America/Sao_Paulo" --name postgresql -e POSTGRES_USER=unkbot -e POSTGRES_PASSWORD=Suasenha -p 5432:5432 -d --restart=always -v /data:/var/lib/postgresql/data -d postgres +``` + +Instalar o Redis Docker + +```bash +docker run -e TZ="America/Sao_Paulo" --name redis-unkbot -p 6379:6379 -d --restart=always redis:latest redis-server --appendonly yes --requirepass "suaSenha" +``` + + Clonar este repositório: + +```bash +cd ~ +git clone https://github.com/w3nder/whaticket-free.git +``` + +Crie um arquivo .env de backend e preencha com as informações correta: + +```bash +cp whaticket-free/backend/.env.example whaticket-free/backend/.env +nano whaticket-free/backend/.env +``` + +```bash +NODE_ENV= +BACKEND_URL=http://localhost +FRONTEND_URL=http://localhost:3000 +PROXY_PORT=8081 +PORT=8081 + +DB_DIALECT=postgres +DB_HOST=localhost +DB_USER=unkbot +DB_PASS=Suasenha +DB_NAME=unkbot + +JWT_SECRET=asdsad +JWT_REFRESH_SECRET=asdasd + +REDIS_URI=redis://:suaSenha@127.0.0.1:6379 +REDIS_OPT_LIMITER_MAX=1 +REDIS_OPT_LIMITER_DURATION=3000 + +``` + +Executa o npm install , cria o build cria as tabela e insere os registro padrão + +```bash +cd whaticket-free/backend +npm install +npm run build +npm run db:migrate +npm run db:seed +``` + +Vá para a pasta frontend e instale as dependências: + +```bash +cd ../frontend +cp .env.example .env +nano .env +``` + +```bash +REACT_APP_BACKEND_URL=https://URL_DO_BACKEND(NAO E URL DO FRONTEND) +REACT_APP_HOURS_CLOSE_TICKETS_AUTO = 24 +``` + +```bash +npm install +npm run build +``` + +Instale o pm2 **com sudo** e inicie o backend com ele: + +```bash +sudo npm install -g pm2 + +cd ../backend +pm2 start dist/server.js --name unkbot-backend +cd ../frontend +pm2 start server.js --name unkbot-frontend + +``` + +Iniciar pm2 após a reinicialização: + +```bash +pm2 startup ubuntu -u `YOUR_USERNAME` +``` + +Copie a última saída de linha do comando anterior e execute-o, é algo como: + +```bash +sudo env PATH=\$PATH:/usr/bin pm2 startup ubuntu -u YOUR_USERNAME --hp /home/YOUR_USERNAM +``` + +Instale o nginx: + +```bash +sudo apt install nginx +``` + +Remova o site padrão do nginx: + +```bash +sudo rm /etc/nginx/sites-enabled/default +``` + +Crie o site para o Backend +```bash +sudo nano /etc/nginx/sites-available/unkbot-backend +``` + +```bash +server { + server_name api.mydomain.com; + + location / { + proxy_pass http://127.0.0.1:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_cache_bypass $http_upgrade; + } +} +``` + +Crie o site para o frontend + +```bash +sudo nano /etc/nginx/sites-available/unkbot-frontend +``` + +```bash +server { + server_name app.mydomain.com; + + location / { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_cache_bypass $http_upgrade; + } +} +``` + +Crie os links simbólicos para habilitar os sites: + +```bash +sudo ln -s /etc/nginx/sites-available/unkbot-backend /etc/nginx/sites-enabled +sudo ln -s /etc/nginx/sites-available/unkbot-frontend /etc/nginx/sites-enabled +``` + +Vamos alterar a configuração do nginx para aceitar 20MB de corpo nas requisições: + +```bash +sudo nano /etc/nginx/nginx.conf +... + +http { + ... + client_max_body_size 20M; # HANDLE BIGGER UPLOADS +} + +``` + +Teste a configuração e reinicie o nginx: + +```bash +sudo nginx -t +sudo service nginx restart +``` + +Agora, ative o SSL (https) nos seus sites para utilizar todas as funcionalidades da aplicação como notificações e envio de mensagens áudio. Uma forma fácil de o fazer é utilizar Certbot: + +Instale o certbor com snapd: + +```bash +sudo snap install --classic certbot +``` + +Habilite SSL com nginx: + +```bash +sudo certbot --nginx +``` diff --git a/backend/.env.backup b/backend/.env.backup new file mode 100644 index 0000000..343fbc5 --- /dev/null +++ b/backend/.env.backup @@ -0,0 +1,20 @@ +NODE_ENV= +BACKEND_URL=http://localhost +FRONTEND_URL=http://localhost:3000 +PROXY_PORT=8080 +PORT=8080 + +DB_DIALECT=postgres +DB_HOST=localhost +DB_USER=unkbot +DB_PASS=TuPasswordSeguro1 + +DB_NAME=unkbot + +JWT_SECRET=yaJ10B54UKBn28uTupzrgSE22FFvrbuoOstlE6CL2cMyw9sHd2 +JWT_REFRESH_SECRET=hPPLLi5fMRhsx3z6MjvNJtxK5omlQHUKRnGxDaa16KwJ1jPhkK + +REDIS_URI=redis://:eEES1654987$@127.0.0.1:6379 +REDIS_OPT_LIMITER_MAX=1 +REDIS_OPT_LIMITER_DURATION=3000 +WEBHOOK_URL=http://localhost:5678/webhook/whaticket diff --git a/backend/Dockerfile b/backend/Dockerfile index 99b1a35..c60967f 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14 as build-deps +FROM node:16 as build-deps RUN apt-get update && apt-get install -y wget diff --git a/backend/backend@1.0.0 b/backend/backend@1.0.0 new file mode 100644 index 0000000..e69de29 diff --git a/backend/diagnosticarOnWhatsApp.js b/backend/diagnosticarOnWhatsApp.js new file mode 100644 index 0000000..44ef986 --- /dev/null +++ b/backend/diagnosticarOnWhatsApp.js @@ -0,0 +1,173 @@ +const { default: makeWASocket, useMultiFileAuthState } = require('@whiskeysockets/baileys'); +const fs = require('fs'); +const path = require('path'); + +async function diagnosticarOnWhatsApp() { + console.log('🔍 INICIANDO DIAGNÓSTICO onWhatsApp'); + console.log('===================================='); + + // Verificar si existe sesión + const sessionPath = 'whatsapp-session'; + if (!fs.existsSync(sessionPath)) { + console.log('❌ NO hay carpeta de sesión WhatsApp'); + return; + } + + console.log('📁 Sesión encontrada en:', sessionPath); + + try { + // Cargar sesión existente + const { state, saveCreds } = await useMultiFileAuthState(sessionPath); + + // Verificar credenciales + if (!state.creds.me) { + console.log('❌ Credenciales NO válidas o sesión expirada'); + return; + } + + console.log('✅ Credenciales cargadas'); + console.log(' Usuario:', state.creds.me?.id || 'Desconocido'); + console.log(' Plataforma:', state.creds.platform || 'Desconocida'); + + // Crear socket + const sock = makeWASocket({ + auth: state, + printQRInTerminal: false, + logger: { level: 'silent' } + }); + + let conexionAbierta = false; + let timeoutId; + + // Configurar timeout + const timeout = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + reject(new Error('Timeout: No se pudo conectar en 30 segundos')); + }, 30000); + }); + + // Esperar conexión + const conexion = new Promise((resolve) => { + sock.ev.on('connection.update', (update) => { + const { connection, lastDisconnect } = update; + console.log('📡 Estado conexión:', connection); + + if (connection === 'open') { + clearTimeout(timeoutId); + conexionAbierta = true; + console.log('✅ WhatsApp CONECTADO'); + resolve(true); + } + + if (connection === 'close') { + const error = lastDisconnect?.error; + console.log('❌ WhatsApp DESCONECTADO'); + if (error) { + console.log(' Error:', error.message); + console.log(' Status:', error.status); + } + resolve(false); + } + }); + }); + + // Guardar credenciales + sock.ev.on('creds.update', saveCreds); + + // Esperar conexión o timeout + const conectado = await Promise.race([conexion, timeout]); + + if (!conectado) { + console.log('❌ No se pudo conectar a WhatsApp'); + return; + } + + // ESPERAR 3 segundos para estabilizar + console.log('⏳ Esperando 3 segundos...'); + await new Promise(resolve => setTimeout(resolve, 3000)); + + // TEST 1: Número conocido (debería funcionar) + console.log('\n🧪 TEST 1: Número tradicional conocido'); + console.log('--------------------------------------'); + try { + // Usar un número que sabes que existe (puedes cambiarlo) + const testNumber = '5492478409220@s.whatsapp.net'; + console.log(' Consultando:', testNumber); + + const results = await sock.onWhatsApp(testNumber); + console.log(' ✅ Resultado:', results ? 'Éxito' : 'Fallo'); + + if (results && results.length > 0) { + console.log(' Existe:', results[0].exists); + console.log (' JID:', results[0].jid); + } else { + console.log(' ❌ No hay resultados'); + } + } catch (error) { + console.log(' ❌ Error:', error.message); + console.log(' Status:', error.status); + console.log(' Code:', error.code); + } + + // TEST 2: LID problemático + console.log('\n🧪 TEST 2: LID (28308196585718@lid)'); + console.log('--------------------------------------'); + try { + const testLID = '28308196585718@lid'; + console.log(' Consultando LID:', testLID); + + const results = await sock.onWhatsApp(testLID); + console.log(' ✅ Resultado:', results ? 'Recibido' : 'Vacío'); + + if (results && results.length > 0) { + console.log(' Existe:', results[0].exists); + console.log(' JID:', results[0].jid); + } else { + console.log(' ❌ LID no devolvió resultados'); + } + } catch (error) { + console.log(' ❌ Error LID:', error.message); + console.log(' Status:', error.status); + console.log(' Code:', error.code); + if (error.response) { + console.log(' Response:', JSON.stringify(error.response, null, 2)); + } + } + + // TEST 3: Solo número sin @ + console.log('\n🧪 TEST 3: Solo número (sin @s.whatsapp.net)'); + console.log('--------------------------------------------'); + try { + const testNum = '5492478409220'; + console.log(' Consultando:', testNum); + + const results = await sock.onWhatsApp(testNum); + console.log(' ✅ Resultado:', results ? 'Recibido' : 'Vacío'); + + if (results && results.length > 0) { + console.log(' Existe:', results[0].exists); + console.log(' JID:', results[0].jid); + } else { + console.log(' ❌ No hay resultados'); + } + } catch (error) { + console.log(' ❌ Error:', error.message); + } + + // TEST 4: Verificar estado de conexión + console.log('\n🧪 TEST 4: Estado general'); + console.log('-------------------------'); + console.log(' Conexión activa:', conexionAbierta); + console.log(' Creds.me:', sock.authState.creds.me?.id); + + // Cerrar conexión limpia + await sock.end(); + console.log('\n✅ Diagnóstico completado'); + + } catch (error) { + console.log('❌ Error en diagnóstico:', error.message); + console.log('Stack:', error.stack); + } +} + +diagnosticarOnWhatsApp().catch(console.error); diff --git a/backend/diagnosticarOnWhatsAppV2.js b/backend/diagnosticarOnWhatsAppV2.js new file mode 100644 index 0000000..d3c7ede --- /dev/null +++ b/backend/diagnosticarOnWhatsAppV2.js @@ -0,0 +1,152 @@ +const { default: makeWASocket } = require('@whiskeysockets/baileys'); +const fs = require('fs'); +const path = require('path'); + +// Importar el authState personalizado de Whaticket +async function getAuthState() { + try { + // Intentar cargar el authState de Whaticket + const authStateModule = require('./dist/helpers/authState'); + return await authStateModule.authState('sessions'); + } catch (error) { + console.log('❌ No se pudo cargar authState de Whaticket:', error.message); + + // Fallback: usar implementación simple + console.log('⚠️ Usando implementación simple...'); + + const sessionPath = 'sessions'; + if (!fs.existsSync(sessionPath)) { + throw new Error('No existe carpeta sessions/'); + } + + // Cargar creds.json manualmente + const credsPath = path.join(sessionPath, 'creds.json'); + if (!fs.existsSync(credsPath)) { + throw new Error('No existe creds.json en sessions/'); + } + + const creds = JSON.parse(fs.readFileSync(credsPath, 'utf8')); + + return { + state: { + creds, + keys: { + get: async () => ({}), + set: async () => {} + } + }, + saveCreds: () => {} + }; + } +} + +async function diagnosticarOnWhatsAppV2() { + console.log('🔍 DIAGNÓSTICO onWhatsApp V2'); + console.log('============================='); + + try { + // Obtener authState + const { state, saveCreds } = await getAuthState(); + + if (!state.creds || !state.creds.me) { + console.log('❌ Credenciales NO válidas o sesión expirada'); + console.log(' Creds disponibles:', Object.keys(state.creds || {})); + return; + } + + console.log('✅ Credenciales cargadas'); + console.log(' Usuario:', state.creds.me.id || 'Desconocido'); + console.log(' Plataforma:', state.creds.platform || 'Desconocida'); + + // Crear socket + const sock = makeWASocket({ + auth: state, + printQRInTerminal: false, + logger: { level: 'warn' } + }); + + // Configurar eventos + sock.ev.on('connection.update', (update) => { + const { connection, lastDisconnect } = update; + console.log('📡 Estado conexión:', connection); + + if (connection === 'open') { + console.log('✅ WhatsApp CONECTADO'); + } + + if (connection === 'close') { + const error = lastDisconnect?.error; + console.log('❌ WhatsApp DESCONECTADO'); + if (error) { + console.log(' Error:', error.message); + console.log(' Status:', error.status); + } + } + }); + + sock.ev.on('creds.update', saveCreds); + + // Esperar 10 segundos para que se estabilice la conexión + console.log('⏳ Esperando 10 segundos para conexión...'); + await new Promise(resolve => setTimeout(resolve, 10000)); + + // Verificar si está conectado + if (!sock.user) { + console.log('❌ WhatsApp NO conectado después de 10 segundos'); + return; + } + + console.log('✅ WhatsApp listo para pruebas'); + console.log(' Usuario conectado:', sock.user.id); + + // TEST: LID problemático + console.log('\n🧪 TEST: LID 28308196585718@lid'); + console.log('-------------------------------'); + + try { + const testLID = '28308196585718@lid'; + console.log('Consultando:', testLID); + + // Usar promise con timeout + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Timeout después de 10 segundos')), 10000); + }); + + const queryPromise = sock.onWhatsApp(testLID); + const results = await Promise.race([queryPromise, timeoutPromise]); + + console.log('✅ Resultado recibido'); + + if (results && results.length > 0) { + console.log(' Existe:', results[0].exists); + console.log(' JID:', results[0].jid); + console.log(' ✅ LID CONVERTIDO EXITOSAMENTE'); + } else { + console.log(' ❌ LID no existe o no devolvió resultados'); + console.log(' Results:', JSON.stringify(results, null, 2)); + } + + } catch (error) { + console.log('❌ Error en onWhatsApp:'); + console.log(' Mensaje:', error.message); + console.log(' Código:', error.code); + console.log(' Status:', error.status); + + // Intentar verificar si es error de token + if (error.message.includes('token') || error.message.includes('403')) { + console.log('⚠️ PROBLEMA DE TOKEN/AUTENTICACIÓN'); + console.log(' Posible causa: Sesión expirada o token inválido'); + } + } + + // Cerrar conexión + await sock.end(); + console.log('\n✅ Diagnóstico completado'); + + } catch (error) { + console.log('❌ Error general en diagnóstico:', error.message); + console.log('Stack:', error.stack); + } +} + +diagnosticarOnWhatsAppV2().catch(console.error); diff --git a/backend/diagnostico.txt b/backend/diagnostico.txt new file mode 100644 index 0000000..1799a8a --- /dev/null +++ b/backend/diagnostico.txt @@ -0,0 +1,57 @@ +=== DIAGNÓSTICO WHATICKET === +Fecha: $(date) + +1. PM2 STATUS: +┌────┬───────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ +│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ +├────┼───────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ +│ 2 │ whaticket-backend │ default │ 1.0.0 │ fork │ 28460 │ 46m │ 0 │ online │ 0% │ 151.5mb │ whatick… │ disabled │ +│ 1 │ whaticket-frontend │ default │ 0.1.0 │ fork │ 24511 │ 7h │ 0 │ online │ 0% │ 71.8mb │ whatick… │ disabled │ +└────┴───────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘ + +2. BACKEND LOGS (20 líneas): +[TAILING] Tailing last 20 lines for [whaticket-backend] process (change the value with --lines option) +/home/whaticketapp/.pm2/logs/whaticket-backend-error.log last 20 lines: +2|whaticke | at Module. (node:internal/modules/cjs/loader:1463:12) +2|whaticke | at Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:101:39) +2|whaticke | at require (node:internal/modules/helpers:147:16) +2|whaticke | at Object. (/home/whaticketapp/whaticket-free/backend/dist/helpers/GetWhatsappWbot.js:12:16) +2|whaticke | at Module._compile (node:internal/modules/cjs/loader:1706:14) +2|whaticke | at Object..js (node:internal/modules/cjs/loader:1839:10) +2|whaticke | at Module.load (node:internal/modules/cjs/loader:1441:32) +2|whaticke | at Function._load (node:internal/modules/cjs/loader:1263:12) +2|whaticke | at TracingChannel.traceSync (node:diagnostics_channel:328:14) +2|whaticke | at wrapModuleLoad (node:internal/modules/cjs/loader:237:24) +2|whaticke | at Module. (node:internal/modules/cjs/loader:1463:12) +2|whaticke | at Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:101:39) +2|whaticke | at require (node:internal/modules/helpers:147:16) +2|whaticke | at Object. (/home/whaticketapp/whaticket-free/backend/dist/helpers/SendMessage.js:18:43) +2|whaticke | at Module._compile (node:internal/modules/cjs/loader:1706:14) +2|whaticke | ReferenceError: state is not defined +2|whaticke | at /home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js:110:27 +2|whaticke | at Generator.next () +2|whaticke | at fulfilled (/home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js:28:58) +2|whaticke | at process.processTicksAndRejections (node:internal/process/task_queues:105:5) + +/home/whaticketapp/.pm2/logs/whaticket-backend-out.log last 20 lines: +2|whaticke | privKey: +2|whaticke | }, +2|whaticke | lastRemoteEphemeralKey: , +2|whaticke | previousCounter: 0, +2|whaticke | rootKey: +2|whaticke | }, +2|whaticke | indexInfo: { +2|whaticke | baseKey: , +2|whaticke | baseKeyType: 1, +2|whaticke | closed: -1, +2|whaticke | used: 1766880303589, +2|whaticke | created: 1766880303589, +2|whaticke | remoteIdentityKey: +2|whaticke | }, +2|whaticke | pendingPreKey: { +2|whaticke | signedKeyId: 16709982, +2|whaticke | baseKey: , +2|whaticke | preKeyId: 3038053 +2|whaticke | } +2|whaticke | } + diff --git a/backend/ecosystem.config.js b/backend/ecosystem.config.js new file mode 100644 index 0000000..ba878a2 --- /dev/null +++ b/backend/ecosystem.config.js @@ -0,0 +1,19 @@ +module.exports = { + apps: [{ + name: "whaticket-backend", + script: "src/server.ts", + interpreter: "node", + interpreter_args: "-r ts-node/register", + instances: 1, + autorestart: true, + watch: false, + max_memory_restart: "1G", + env: { + NODE_ENV: "production", + PORT: 8080 + }, + error_file: "/home/whaticketapp/.pm2/logs/whaticket-backend-err.log", + out_file: "/home/whaticketapp/.pm2/logs/whaticket-backend-out.log", + log_date_format: "YYYY-MM-DD HH:mm:ss" + }] +} diff --git a/backend/find_image_decrypt_function.js b/backend/find_image_decrypt_function.js new file mode 100644 index 0000000..8819f01 --- /dev/null +++ b/backend/find_image_decrypt_function.js @@ -0,0 +1,137 @@ +console.log("=== Buscando función para descifrar imágenes ===\n"); + +const fs = require('fs'); +const path = require('path'); + +// 1. Leer messages-media.js completo +const baileysPath = require.resolve('@whiskeysockets/baileys'); +const baileysDir = path.dirname(baileysPath); +const messagesMediaPath = path.join(baileysDir, 'Utils', 'messages-media.js'); + +if (!fs.existsSync(messagesMediaPath)) { + console.log("❌ No se encontró messages-media.js"); + process.exit(1); +} + +const content = fs.readFileSync(messagesMediaPath, 'utf8'); +const lines = content.split('\n'); + +console.log("🔍 Buscando funciones relacionadas con descifrado de medios...\n"); + +// 2. Buscar funciones clave +const targetFunctions = [ + 'downloadMediaMessage', + 'decryptMediaMessage', + 'downloadEncryptedContent', + 'getMediaDecryptionKey', + 'extractMediaInformation' +]; + +let foundFunctions = []; + +for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + for (const funcName of targetFunctions) { + if (line.includes(funcName) && + (line.includes('function') || line.includes('export const') || line.includes('export async'))) { + + // Obtener la definición completa + let funcLines = [line]; + let braceCount = (line.match(/{/g) || []).length - (line.match(/}/g) || []).length; + let j = i; + + while (braceCount > 0 && j < lines.length - 1) { + j++; + funcLines.push(lines[j]); + braceCount += (lines[j].match(/{/g) || []).length - (lines[j].match(/}/g) || []).length; + } + + foundFunctions.push({ + name: funcName, + line: i + 1, + definition: funcLines.join('\n') + }); + + break; + } + } +} + +// 3. Mostrar funciones encontradas +if (foundFunctions.length > 0) { + console.log(`✅ Encontradas ${foundFunctions.length} funciones:\n`); + + foundFunctions.forEach(func => { + console.log(`📌 ${func.name} (línea ${func.line}):`); + console.log("-".repeat(80)); + + // Mostrar solo las primeras 10 líneas de la definición + const defLines = func.definition.split('\n'); + defLines.slice(0, 15).forEach((l, idx) => { + console.log(`${(idx + 1).toString().padStart(3)}: ${l}`); + }); + + if (defLines.length > 15) { + console.log(` ... y ${defLines.length - 15} líneas más`); + } + + console.log(""); + + // Extraer parámetros + const firstLine = defLines[0]; + const paramMatch = firstLine.match(/\((.*?)\)/); + if (paramMatch) { + const params = paramMatch[1].split(',').map(p => p.trim()); + console.log(` 🔑 Parámetros: ${params.join(', ')}`); + } + + console.log(""); + }); +} else { + console.log("❌ No se encontraron las funciones objetivo."); + + // Mostrar todas las exportaciones del archivo como alternativa + console.log("\n🔍 Mostrando todas las exportaciones del archivo:"); + const exportLines = lines.filter(l => l.includes('export const') || l.includes('export async') || l.includes('export function')); + + exportLines.slice(0, 20).forEach(l => { + const match = l.match(/export\s+(?:const|async|function)\s+(\w+)/); + if (match) { + console.log(` - ${match[1]}`); + } + }); +} + +// 4. Buscar específicamente la lógica de descifrado AES +console.log("\n🔐 Buscando lógica de descifrado AES..."); +const aesLines = lines.filter((l, i) => + l.includes('aesDecrypt') && + !l.includes('decryptMediaRetryData') && + i > 500 && i < 700 // Buscar en la misma área +); + +if (aesLines.length > 0) { + console.log(`\nEncontradas ${aesLines.length} líneas con aesDecrypt:\n`); + + // Mostrar contexto alrededor de cada línea + aesLines.slice(0, 5).forEach((line, idx) => { + const lineNum = lines.indexOf(line) + 1; + console.log(`Línea ${lineNum}: ${line.trim()}`); + + // Mostrar 2 líneas antes y después para contexto + const start = Math.max(0, lineNum - 3); + const end = Math.min(lines.length, lineNum + 2); + + for (let i = start; i < end; i++) { + if (i === lineNum - 1) { + console.log(` ${i + 1}: > ${lines[i]}`); + } else { + console.log(` ${i + 1}: ${lines[i]}`); + } + } + console.log(""); + }); +} + +console.log("\n=== Búsqueda completada ==="); diff --git a/backend/inspect_baileys_api.js b/backend/inspect_baileys_api.js new file mode 100644 index 0000000..d27f552 --- /dev/null +++ b/backend/inspect_baileys_api.js @@ -0,0 +1,87 @@ +console.log("=== Inspección de API Baileys 6.7.19 ===\n"); + +const baileys = require('@whiskeysockets/baileys'); + +// 1. Listar todas las exportaciones +console.log("🔍 Exportaciones principales:"); +Object.keys(baileys) + .sort() + .forEach(key => { + const value = baileys[key]; + const type = typeof value; + const isFunc = type === 'function' ? 'ƒ' : ''; + const isObj = type === 'object' && value !== null ? '{}' : ''; + const isUndefined = value === undefined ? 'undefined' : ''; + + console.log(` ${key.padEnd(30)} ${isFunc || isObj || isUndefined || `[${type}]`}`); + }); + +// 2. Buscar específicamente funciones de descifrado +console.log("\n🔑 Funciones de cifrado/descifrado:"); +Object.keys(baileys) + .filter(key => { + const lower = key.toLowerCase(); + return lower.includes('crypt') || + lower.includes('decrypt') || + lower.includes('encrypt') || + lower.includes('media') || + lower.includes('key'); + }) + .forEach(key => { + console.log(` - ${key}`); + }); + +// 3. Probar getMediaKeys específicamente +console.log("\n🧪 Probando getMediaKeys:"); +if (baileys.getMediaKeys) { + console.log(" ✅ getMediaKeys existe"); + + // Probar con datos de prueba + try { + const testKey = Buffer.from('test'.repeat(8)); // 32 bytes + const result = baileys.getMediaKeys(testKey, 'image'); + console.log(" 🔍 Resultado de getMediaKeys:"); + console.log(` Tipo: ${typeof result}`); + console.log(` ¿Objeto? ${result && typeof result === 'object' ? 'Sí' : 'No'}`); + + if (result && typeof result === 'object') { + console.log(" Propiedades:"); + Object.keys(result).forEach(prop => { + const val = result[prop]; + console.log(` ${prop}: ${val ? `${val.constructor.name} (${val.length} bytes)` : 'null/undefined'}`); + }); + } + } catch (e) { + console.log(` ❌ Error: ${e.message}`); + } +} else { + console.log(" ❌ getMediaKeys NO existe"); +} + +// 4. Buscar constantes de media +console.log("\n📁 Constantes de medios:"); +Object.keys(baileys) + .filter(key => { + const value = baileys[key]; + return typeof value === 'object' && value !== null && + (key.includes('MEDIA') || key.includes('media')); + }) + .forEach(key => { + console.log(` ${key}:`); + if (baileys[key]) { + Object.keys(baileys[key]).forEach(subKey => { + console.log(` - ${subKey}: ${baileys[key][subKey]}`); + }); + } + }); + +// 5. Verificar aesDecryptWithIV +console.log("\n🔐 aesDecryptWithIV:"); +if (baileys.aesDecryptWithIV) { + console.log(" ✅ aesDecryptWithIV existe"); + console.log(` Parámetros esperados: ${baileys.aesDecryptWithIV.length}`); +} else { + console.log(" ❌ aesDecryptWithIV NO existe"); +} + +console.log("\n=== Inspección completada ==="); diff --git a/backend/inspect_decrypt_function.js b/backend/inspect_decrypt_function.js new file mode 100644 index 0000000..59ba76c --- /dev/null +++ b/backend/inspect_decrypt_function.js @@ -0,0 +1,104 @@ +console.log("=== Inspección de decryptMediaRetryData ===\n"); + +const fs = require('fs'); +const path = require('path'); + +// 1. Encontrar y leer el archivo messages-media.js +const baileysPath = require.resolve('@whiskeysockets/baileys'); +const baileysDir = path.dirname(baileysPath); +const messagesMediaPath = path.join(baileysDir, 'Utils', 'messages-media.js'); + +console.log(`📁 Ruta del módulo: ${messagesMediaPath}`); + +if (fs.existsSync(messagesMediaPath)) { + const content = fs.readFileSync(messagesMediaPath, 'utf8'); + + // 2. Encontrar decryptMediaRetryData + const lines = content.split('\n'); + let inFunction = false; + let functionLines = []; + let braceCount = 0; + + console.log("\n🔍 Buscando decryptMediaRetryData..."); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + if (line.includes('decryptMediaRetryData') && + (line.includes('function') || line.includes('=') || line.includes('async'))) { + inFunction = true; + console.log(`✅ Encontrada en línea ${i + 1}`); + } + + if (inFunction) { + functionLines.push(line); + + // Contar llaves para saber cuándo termina + braceCount += (line.match(/{/g) || []).length; + braceCount -= (line.match(/}/g) || []).length; + + if (braceCount === 0 && functionLines.length > 1) { + break; + } + } + } + + // 3. Mostrar la función completa + console.log("\n📝 FUNCIÓN decryptMediaRetryData:"); + console.log("=" .repeat(80)); + functionLines.forEach((line, idx) => { + console.log(`${(idx + 1).toString().padStart(3)}: ${line}`); + }); + console.log("=" .repeat(80)); + + // 4. Extraer los parámetros que espera + console.log("\n🔑 Parámetros esperados (de la definición):"); + const firstLine = functionLines[0]; + const paramMatch = firstLine.match(/\((.*?)\)/); + if (paramMatch) { + const params = paramMatch[1].split(',').map(p => p.trim()); + params.forEach((p, i) => { + console.log(` ${i + 1}. ${p}`); + }); + } + + // 5. Buscar llamadas a esta función en el código para ver ejemplos de uso + console.log("\n🔎 Buscando ejemplos de uso en el código..."); + const usageMatches = []; + + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('decryptMediaRetryData(') && !lines[i].includes('function')) { + // Encontrar la llamada completa (puede ser multi-línea) + let callLines = [lines[i]]; + let callBraceCount = (lines[i].match(/\(/g) || []).length - (lines[i].match(/\)/g) || []).length; + let j = i; + + while (callBraceCount > 0 && j < lines.length - 1) { + j++; + callLines.push(lines[j]); + callBraceCount += (lines[j].match(/\(/g) || []).length - (lines[j].match(/\)/g) || []).length; + } + + const fullCall = callLines.join('\n').trim(); + usageMatches.push({ line: i + 1, call: fullCall }); + + // Solo mostrar los primeros 2 usos + if (usageMatches.length >= 2) break; + } + } + + if (usageMatches.length > 0) { + console.log("\n📋 Ejemplos de uso encontrados:"); + usageMatches.forEach((usage, idx) => { + console.log(`\nEjemplo ${idx + 1} (línea ${usage.line}):`); + console.log(usage.call); + }); + } else { + console.log("No se encontraron ejemplos de uso en este archivo."); + } + +} else { + console.log("❌ No se encontró messages-media.js"); +} + +console.log("\n=== Inspección completada ==="); diff --git a/backend/logs_output.txt b/backend/logs_output.txt new file mode 100644 index 0000000..142a0b2 --- /dev/null +++ b/backend/logs_output.txt @@ -0,0 +1,232 @@ +[TAILING] Tailing last 100 lines for [whaticket-backend] process (change the value with --lines option) +/home/whaticketapp/.pm2/logs/whaticket-backend-error.log last 100 lines: +0|whaticke | requireStack: [ +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/helpers/GetWhatsappWbot.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/helpers/SendMessage.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/queues.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/app.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/server.js' +0|whaticke | ] +0|whaticke | } +0|whaticke | Error: Cannot find module '@whiskeysockets/baileys' +0|whaticke | Require stack: +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/helpers/GetWhatsappWbot.js +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/helpers/SendMessage.js +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/queues.js +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/app.js +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/server.js +0|whaticke | at Function.Module._resolveFilename (node:internal/modules/cjs/loader:1028:15) +0|whaticke | at Module.Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:81:25) +0|whaticke | at require (node:internal/modules/cjs/helpers:119:18) +0|whaticke | at Object. (/home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js:30:32) +0|whaticke | at Module._compile (node:internal/modules/cjs/loader:1198:14) +0|whaticke | at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10) +0|whaticke | at Module.load (node:internal/modules/cjs/loader:1076:32) +0|whaticke | at Function.Module._load (node:internal/modules/cjs/loader:911:12) +0|whaticke | at Module.require (node:internal/modules/cjs/loader:1100:19) +0|whaticke | at Module.Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:101:39) +0|whaticke | at require (node:internal/modules/cjs/helpers:119:18) +0|whaticke | at Object. (/home/whaticketapp/whaticket-free/backend/dist/helpers/GetWhatsappWbot.js:3:16) +0|whaticke | at Module._compile (node:internal/modules/cjs/loader:1198:14) +0|whaticke | at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10) +0|whaticke | at Module.load (node:internal/modules/cjs/loader:1076:32) +0|whaticke | at Function.Module._load (node:internal/modules/cjs/loader:911:12) +0|whaticke | at Module.require (node:internal/modules/cjs/loader:1100:19) +0|whaticke | at Module.Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:101:39) +0|whaticke | at require (node:internal/modules/cjs/helpers:119:18) +0|whaticke | at Object. (/home/whaticketapp/whaticket-free/backend/dist/helpers/SendMessage.js:9:43) +0|whaticke | at Module._compile (node:internal/modules/cjs/loader:1198:14) +0|whaticke | at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10) { +0|whaticke | code: 'MODULE_NOT_FOUND', +0|whaticke | requireStack: [ +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/helpers/GetWhatsappWbot.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/helpers/SendMessage.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/queues.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/app.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/server.js' +0|whaticke | ] +0|whaticke | } +0|whaticke | Error: Cannot find module '@whiskeysockets/baileys' +0|whaticke | Require stack: +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/helpers/GetWhatsappWbot.js +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/helpers/SendMessage.js +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/queues.js +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/app.js +0|whaticke | - /home/whaticketapp/whaticket-free/backend/dist/server.js +0|whaticke | at Function.Module._resolveFilename (node:internal/modules/cjs/loader:1028:15) +0|whaticke | at Module.Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:81:25) +0|whaticke | at require (node:internal/modules/cjs/helpers:119:18) +0|whaticke | at Object. (/home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js:30:32) +0|whaticke | at Module._compile (node:internal/modules/cjs/loader:1198:14) +0|whaticke | at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10) +0|whaticke | at Module.load (node:internal/modules/cjs/loader:1076:32) +0|whaticke | at Function.Module._load (node:internal/modules/cjs/loader:911:12) +0|whaticke | at Module.require (node:internal/modules/cjs/loader:1100:19) +0|whaticke | at Module.Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:101:39) +0|whaticke | at require (node:internal/modules/cjs/helpers:119:18) +0|whaticke | at Object. (/home/whaticketapp/whaticket-free/backend/dist/helpers/GetWhatsappWbot.js:3:16) +0|whaticke | at Module._compile (node:internal/modules/cjs/loader:1198:14) +0|whaticke | at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10) +0|whaticke | at Module.load (node:internal/modules/cjs/loader:1076:32) +0|whaticke | at Function.Module._load (node:internal/modules/cjs/loader:911:12) +0|whaticke | at Module.require (node:internal/modules/cjs/loader:1100:19) +0|whaticke | at Module.Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:101:39) +0|whaticke | at require (node:internal/modules/cjs/helpers:119:18) +0|whaticke | at Object. (/home/whaticketapp/whaticket-free/backend/dist/helpers/SendMessage.js:9:43) +0|whaticke | at Module._compile (node:internal/modules/cjs/loader:1198:14) +0|whaticke | at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10) { +0|whaticke | code: 'MODULE_NOT_FOUND', +0|whaticke | requireStack: [ +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/helpers/GetWhatsappWbot.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/helpers/SendMessage.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/queues.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/app.js', +0|whaticke | '/home/whaticketapp/whaticket-free/backend/dist/server.js' +0|whaticke | ] +0|whaticke | } +0|whaticke | Closing open session in favor of incoming prekey bundle +0|whaticke | Closing open session in favor of incoming prekey bundle +0|whaticke | Closing open session in favor of incoming prekey bundle +0|whaticke | Closing open session in favor of incoming prekey bundle +0|whaticke | Closing open session in favor of incoming prekey bundle +0|whaticke | Closing open session in favor of incoming prekey bundle +0|whaticke | Closing open session in favor of incoming prekey bundle +0|whaticke | Closing open session in favor of incoming prekey bundle +0|whaticke | Closing open session in favor of incoming prekey bundle +0|whaticke | Closing open session in favor of incoming prekey bundle +0|whaticke | Closing open session in favor of incoming prekey bundle + +/home/whaticketapp/.pm2/logs/whaticket-backend-out.log last 100 lines: +0|whaticke | remoteIdentityKey: +0|whaticke | } +0|whaticke | } +0|whaticke | +0|whaticke | 📨 ========== NUEVO MENSAJE ========== +0|whaticke | 🆔 ID: AC6EE9AD92... +0|whaticke | 📞 RemoteJid: 220903220748330@lid +0|whaticke | 👤 FromMe: NO +0|whaticke | 👥 Participant: N/A +0|whaticke | 📛 PushName: franciscopiazza0 +0|whaticke | ⏰ Timestamp: 5:57:34 PM +0|whaticke | 🔍 ANALIZANDO JID: 220903220748330@lid +0|whaticke | Tipo: LID +0|whaticke | +0|whaticke | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticke | Input: "220903220748330@lid" +0|whaticke | SessionId: "11" +0|whaticke | Clean input: "220903220748330" (15 dígitos) +0|whaticke | Tipo: Número normal +0|whaticke | +0|whaticke | 🔍 Buscando mapping exacto: lid-mapping-220903220748330_reverse +0|whaticke | ⚠️ No se encontró mapping exacto +0|whaticke | +0|whaticke | 🔍 Buscando coincidencias parciales... +0|whaticke | Total mappings: 98 +0|whaticke | +0|whaticke | 🔍 Buscando LID para número: 220903220748330 +0|whaticke | ℹ️ No se encontró LID para número 220903220748330 +0|whaticke | +0|whaticke | ⚠️ No se encontró mapping para: 220903220748330 +0|whaticke | Devolviendo input original +0|whaticke | ✅ DETECTADO LID CON @lid, intentando onWhatsApp: 220903220748330@lid +0|whaticke | ⚠️ LID NO PUDO CONVERTIRSE CON onWhatsApp: 220903220748330@lid +0|whaticke | 🔄 Usando NormalizadorMejorado como fallback para LID +0|whaticke | 🔍 obtenerNumeroReal - JID recibido: 220903220748330@lid +0|whaticke | 🔍 Detectado como LID: 220903220748330@lid +0|whaticke | 🔧 Número extraído de JID: 220903220748330@lid -> 220903220748330 +0|whaticke | ⚠️ LID largo detectado, posiblemente incorrecto +0|whaticke | ✅ RESULTADO (NormalizadorMejorado para LID): +0|whaticke | LID original: 220903220748330@lid +0|whaticke | Número obtenido: LID_2209032207 +0|whaticke | [17:58:39.565] INFO: Socket pedro Connection Update +0|whaticke | [17:58:39.565] INFO: Socket pedro Connection Update open +0|whaticke | [17:58:39.658] INFO: Socket pedro Connection Update +0|whaticke | [17:58:42.472] INFO: Iniciando processamento de filas +0|whaticke | [17:59:07.484] INFO: Total de conexao Prontas para envio!: 2 +0|whaticke | [17:59:07.488] INFO: Configuração de envio não encontrada da conexão +0|whaticke | [17:59:07.489] INFO: Configuração de envio não encontrada da conexão +0|whaticke | 👤 VERIFICANDO CONTACTO: +0|whaticke | ID recibido: LID_2209032207@s.whatsapp.net +0|whaticke | Número en msgContact: LID_2209032207 +0|whaticke | Es LID?: SÍ +0|whaticke | LID original: 220903220748330@lid +0|whaticke | JID original: 220903220748330@lid +0|whaticke | 🔍 Contacto tiene LID, verificando número real: 220903220748330@lid +0|whaticke | +0|whaticke | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticke | Input: "220903220748330@lid" +0|whaticke | SessionId: "11" +0|whaticke | Clean input: "220903220748330" (15 dígitos) +0|whaticke | Tipo: Número normal +0|whaticke | +0|whaticke | 🔍 Buscando mapping exacto: lid-mapping-220903220748330_reverse +0|whaticke | ⚠️ No se encontró mapping exacto +0|whaticke | +0|whaticke | 🔍 Buscando coincidencias parciales... +0|whaticke | Total mappings: 98 +0|whaticke | +0|whaticke | 🔍 Buscando LID para número: 220903220748330 +0|whaticke | ℹ️ No se encontró LID para número 220903220748330 +0|whaticke | +0|whaticke | ⚠️ No se encontró mapping para: 220903220748330 +0|whaticke | Devolviendo input original +0|whaticke | ✅ [BAILEYS_SESSIONS] LID convertido a número en verifyContact: 220903220748330 +0|whaticke | 📝 DATOS FINALES DEL CONTACTO: +0|whaticke | Nombre: franciscopiazza0 +0|whaticke | Número: 220903220748330 +0|whaticke | LID guardado: 220903220748330@lid +0|whaticke | Company ID: 1 +0|whaticke | +0|whaticke | 📊 DIAGNÓSTICO COMPLETO DEL MENSAJE: +0|whaticke | ====================================== +0|whaticke | 📱 DATOS CRUDOS: +0|whaticke | RemoteJid: 220903220748330@lid +0|whaticke | Participant: undefined +0|whaticke | FromMe: false +0|whaticke | PushName: franciscopiazza0 +0|whaticke | Tipo de mensaje: conversation +0|whaticke | +0|whaticke | 🔍 ANÁLISIS LID: +0|whaticke | JID analizado: 220903220748330@lid +0|whaticke | +0|whaticke | 👤 DATOS DEL CONTACTO: +0|whaticke | Número en contacto BD: 220903220748330 +0|whaticke | Contact ID: 494 +0|whaticke | Nombre: franciscopiazza0 +0|whaticke | LID guardado: 220903220748330@lid +0|whaticke | ====================================== +0|whaticke | +0|whaticke | [17:59:39.478] INFO: Ticket 162 messages read + +0|whaticket-backend | 📨 ========== NUEVO MENSAJE ========== +0|whaticket-backend | 🆔 ID: 3EB0A8BA16... +0|whaticket-backend | 📞 RemoteJid: 5492478409220@s.whatsapp.net +0|whaticket-backend | 👤 FromMe: NO +0|whaticket-backend | 👥 Participant: N/A +0|whaticket-backend | 📛 PushName: Pedro Navarro +0|whaticket-backend | ⏰ Timestamp: 6:04:05 PM +0|whaticket-backend | 🔍 ANALIZANDO JID: 5492478409220@s.whatsapp.net +0|whaticket-backend | Tipo: Tradicional +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "5492478409220@s.whatsapp.net" +0|whaticket-backend | SessionId: "12" +0|whaticket-backend | Clean input: "5492478409220" (13 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-5492478409220_reverse +0|whaticket-backend | ⚠️ No se encontró mapping exacto +0|whaticket-backend | 🔍 Buscando coincidencias parciales... +0|whaticket-backend | Total mappings: 98 +0|whaticket-backend | 🔍 Buscando LID para número: 5492478409220 +0|whaticket-backend | ✅ Número tiene LID asociado +0|whaticket-backend | LID: "28308196585718" +0|whaticket-backend | 🔍 obtenerNumeroReal - JID recibido: 5492478409220@s.whatsapp.net +0|whaticket-backend | ✅ Número tradicional extraído: 5492478409220 +0|whaticket-backend | ✅ RESULTADO (no LID @lid): +0|whaticket-backend | JID original: 5492478409220@s.whatsapp.net +0|whaticket-backend | Número obtenido: 5492478409220 +0|whaticket-backend | Es LID?: NO diff --git a/backend/logs_prueba_final.txt b/backend/logs_prueba_final.txt new file mode 100644 index 0000000..8d9639c --- /dev/null +++ b/backend/logs_prueba_final.txt @@ -0,0 +1,453 @@ +[TAILING] Tailing last 200 lines for [whaticket-backend] process (change the value with --lines option) +/home/whaticketapp/.pm2/logs/whaticket-backend-error-0.log last 200 lines: +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:26:38.724 -03:00', +0|whaticke | '2026-01-24 17:26:38.724 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | original: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:26:38.724 -03:00', +0|whaticke | '2026-01-24 17:26:38.724 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } +0|whaticke | ❌ ERROR CRÍTICO: No se encontró contacto existente a pesar del error de duplicado +0|whaticke | Número: 5492478610999 +0|whaticke | Company ID: 1 +0|whaticke | ❌ Error en handleMessage: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error +0|whaticke | at Query.formatError (/home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:324:18) +0|whaticke | at /home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:72:18 +0|whaticke | at tryCatcher (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/util.js:16:23) +0|whaticke | at Promise._settlePromiseFromHandler (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:547:31) +0|whaticke | at Promise._settlePromise (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:604:18) +0|whaticke | at Promise._settlePromise0 (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:649:10) +0|whaticke | at Promise._settlePromises (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:725:18) +0|whaticke | at _drainQueueStep (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:93:12) +0|whaticke | at _drainQueue (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:86:9) +0|whaticke | at Async._drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:102:5) +0|whaticke | at Async.drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:15:14) +0|whaticke | at process.processImmediate (node:internal/timers:485:21) +0|whaticke | at process.topLevelDomainCallback (node:domain:161:15) +0|whaticke | at process.callbackTrampoline (node:internal/async_hooks:128:24) { +0|whaticke | errors: [ +0|whaticke | ValidationErrorItem { +0|whaticke | message: 'number must be unique', +0|whaticke | type: 'unique violation', +0|whaticke | path: 'number', +0|whaticke | value: '5492478610999', +0|whaticke | origin: 'DB', +0|whaticke | instance: [Contact], +0|whaticke | validatorKey: 'not_unique', +0|whaticke | validatorName: null, +0|whaticke | validatorArgs: [] +0|whaticke | } +0|whaticke | ], +0|whaticke | fields: { number: '5492478610999' }, +0|whaticke | parent: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:26:38.724 -03:00', +0|whaticke | '2026-01-24 17:26:38.724 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | original: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:26:38.724 -03:00', +0|whaticke | '2026-01-24 17:26:38.724 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } + +/home/whaticketapp/.pm2/logs/whaticket-backend-out-0.log last 200 lines: +0|whaticke | Input: "80715924414582@lid" +0|whaticke | SessionId: "11" +0|whaticke | Clean input: "80715924414582" (14 dígitos) +0|whaticke | Tipo: Número normal +0|whaticke | +0|whaticke | 🔍 Buscando mapping exacto: lid-mapping-80715924414582_reverse +0|whaticke | ✅ Encontrado 1 mapping(s) exacto(s) +0|whaticke | whatsappId: 11 +0|whaticke | value: "5492478610999" +0|whaticke | 🎯 ÉXITO: 80715924414582 -> 5492478610999 +0|whaticke | 🎯 [BAILEYS_SESSIONS] CONVERSIÓN EXITOSA: +0|whaticke | Input original: 80715924414582@lid +0|whaticke | Número real: 5492478610999 +0|whaticke | JID real: 5492478610999@s.whatsapp.net +0|whaticke | Es LID? true +0|whaticke | ✅ FIN obtenerNumeroRealDeMensaje - BAILEYS_SESSIONS +0|whaticke | +0|whaticke | 📞 === ANTES DE verifyContact === +0|whaticke | msgContact recibido: +0|whaticke | id: 5492478610999@s.whatsapp.net +0|whaticke | name: secundaria1arrecifes +0|whaticke | number: 5492478610999 +0|whaticke | esLid: SÍ +0|whaticke | lid: 80715924414582@lid +0|whaticke | originalJid: 80715924414582@lid +0|whaticke | =============================== +0|whaticke | +0|whaticke | +0|whaticke | 🔍 === verifyContact INICIO === +0|whaticke | 📥 DATOS DE ENTRADA: +0|whaticke | ID: 5492478610999@s.whatsapp.net +0|whaticke | Nombre: secundaria1arrecifes +0|whaticke | Número recibido: 5492478610999 +0|whaticke | Es LID?: SÍ +0|whaticke | LID: 80715924414582@lid +0|whaticke | JID original: 80715924414582@lid +0|whaticke | 🖼️ Foto de perfil obtenida +0|whaticke | 👤 VERIFICANDO CONTACTO: +0|whaticke | ID recibido: 5492478610999@s.whatsapp.net +0|whaticke | Número en msgContact: 5492478610999 +0|whaticke | Es LID?: SÍ +0|whaticke | LID original: 80715924414582@lid +0|whaticke | JID original: 80715924414582@lid +0|whaticke | 🔍 Contacto tiene LID, verificando número real: 80715924414582@lid +0|whaticke | +0|whaticke | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticke | Input: "80715924414582@lid" +0|whaticke | SessionId: "11" +0|whaticke | Clean input: "80715924414582" (14 dígitos) +0|whaticke | Tipo: Número normal +0|whaticke | +0|whaticke | 🔍 Buscando mapping exacto: lid-mapping-80715924414582_reverse +0|whaticke | ✅ Encontrado 1 mapping(s) exacto(s) +0|whaticke | whatsappId: 11 +0|whaticke | value: "5492478610999" +0|whaticke | 🎯 ÉXITO: 80715924414582 -> 5492478610999 +0|whaticke | ✅ [BAILEYS_SESSIONS] LID convertido a número en verifyContact: 5492478610999 +0|whaticke | 📝 DATOS FINALES DEL CONTACTO: +0|whaticke | Nombre: secundaria1arrecifes +0|whaticke | Número: 5492478610999 +0|whaticke | LID guardado: 80715924414582@lid +0|whaticke | Company ID: 1 +0|whaticke | WhatsApp ID: 11 +0|whaticke | originaljid: 80715924414582@lid +0|whaticke | ⚠️ ERROR DE DUPLICADO DETECTADO +0|whaticke | Buscando contacto existente para: 5492478610999 +0|whaticke | Company ID: 1 +0|whaticke | [20:26:38.770] ERROR: Error handling whatsapp message: Err: SequelizeUniqueConstraintError: Validation error +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:26:46.446] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:26:46.447] INFO: Socket ID: +0|whaticke | [20:26:46.447] INFO: Handshake query: +0|whaticke | [20:26:46.447] INFO: Token param: +0|whaticke | [20:26:46.448] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:26:46.450] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:26:46.450] INFO: Socket ID: +0|whaticke | [20:26:46.451] INFO: Handshake query: +0|whaticke | [20:26:46.451] INFO: Token param: +0|whaticke | [20:26:46.452] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:26:46.454] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:26:46.454] INFO: Socket ID: +0|whaticke | [20:26:46.454] INFO: Handshake query: +0|whaticke | [20:26:46.455] INFO: Token param: +0|whaticke | [20:26:46.455] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:26:47.402] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:26:47.402] INFO: Socket ID: +0|whaticke | [20:26:47.403] INFO: Handshake query: +0|whaticke | [20:26:47.403] INFO: Token param: +0|whaticke | [20:26:47.404] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:26:48.360] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:26:48.361] INFO: Socket ID: +0|whaticke | [20:26:48.361] INFO: Handshake query: +0|whaticke | [20:26:48.361] INFO: Token param: +0|whaticke | [20:26:48.362] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:26:48.369] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:26:48.369] INFO: Socket ID: +0|whaticke | [20:26:48.370] INFO: Handshake query: +0|whaticke | [20:26:48.370] INFO: Token param: +0|whaticke | [20:26:48.371] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:26:49.687] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:26:49.687] INFO: Socket ID: +0|whaticke | [20:26:49.687] INFO: Handshake query: +0|whaticke | [20:26:49.687] INFO: Token param: +0|whaticke | [20:26:49.688] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:26:49.692] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:26:49.692] INFO: Socket ID: +0|whaticke | [20:26:49.692] INFO: Handshake query: +0|whaticke | [20:26:49.692] INFO: Token param: +0|whaticke | [20:26:49.693] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:26:50.468] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:26:50.468] INFO: Socket ID: +0|whaticke | [20:26:50.468] INFO: Handshake query: +0|whaticke | [20:26:50.469] INFO: Token param: +0|whaticke | [20:26:50.469] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:27:09.504] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:27:09.505] INFO: Socket ID: +0|whaticke | [20:27:09.505] INFO: Handshake query: +0|whaticke | [20:27:09.506] INFO: Token param: +0|whaticke | [20:27:09.506] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:27:09.516] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:27:09.517] INFO: Socket ID: +0|whaticke | [20:27:09.517] INFO: Handshake query: +0|whaticke | [20:27:09.517] INFO: Token param: +0|whaticke | [20:27:09.518] ERROR: Middleware authentication FAILED: +0|whaticke | [20:27:09.631] WARN: +0|whaticke | message: "Invalid token. We'll try to assign a new one on next request" +0|whaticke | statusCode: 403 +0|whaticke | [20:27:09.820] WARN: +0|whaticke | message: "Invalid token. We'll try to assign a new one on next request" +0|whaticke | statusCode: 403 +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:27:09.830] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:27:09.831] INFO: Socket ID: +0|whaticke | [20:27:09.831] INFO: Handshake query: +0|whaticke | [20:27:09.831] INFO: Token param: +0|whaticke | [20:27:09.831] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:27:09.863] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:27:09.863] INFO: Socket ID: +0|whaticke | [20:27:09.863] INFO: Handshake query: +0|whaticke | [20:27:09.863] INFO: Token param: +0|whaticke | [20:27:09.863] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:27:09.866] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:27:09.866] INFO: Socket ID: +0|whaticke | [20:27:09.866] INFO: Handshake query: +0|whaticke | [20:27:09.866] INFO: Token param: +0|whaticke | [20:27:09.866] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:27:09.869] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:27:09.869] INFO: Socket ID: +0|whaticke | [20:27:09.869] INFO: Handshake query: +0|whaticke | [20:27:09.869] INFO: Token param: +0|whaticke | [20:27:09.869] ERROR: Middleware authentication FAILED: +0|whaticke | [20:27:09.872] WARN: +0|whaticke | message: "Invalid token. We'll try to assign a new one on next request" +0|whaticke | statusCode: 403 +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:27:09.874] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:27:09.874] INFO: Socket ID: +0|whaticke | [20:27:09.874] INFO: Handshake query: +0|whaticke | [20:27:09.874] INFO: Token param: +0|whaticke | [20:27:09.875] ERROR: Middleware authentication FAILED: +0|whaticke | [20:27:09.877] WARN: +0|whaticke | message: "Invalid token. We'll try to assign a new one on next request" +0|whaticke | statusCode: 403 +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:27:09.879] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:27:09.879] INFO: Socket ID: +0|whaticke | [20:27:09.879] INFO: Handshake query: +0|whaticke | [20:27:09.879] INFO: Token param: +0|whaticke | [20:27:09.880] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:27:09.962] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:27:09.962] INFO: Socket ID: +0|whaticke | [20:27:09.963] INFO: Handshake query: +0|whaticke | [20:27:09.963] INFO: Token param: +0|whaticke | [20:27:09.963] ERROR: Middleware authentication FAILED: +0|whaticke | [20:27:10.587] INFO: Ticket 70 messages read +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [20:27:10.777] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [20:27:10.777] INFO: Socket ID: +0|whaticke | [20:27:10.777] INFO: Handshake query: +0|whaticke | [20:27:10.777] INFO: Token param: +0|whaticke | [20:27:10.777] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [20:27:10.778] INFO: === CONNECTION EVENT === +0|whaticke | [20:27:10.778] INFO: Socket ID: +0|whaticke | [20:27:10.778] INFO: Attached user ID: +0|whaticke | [20:27:11.060] INFO: Client Connected - User: + +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [20:27:55.435] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [20:27:55.436] INFO: Socket ID: +0|whaticket-backend | [20:27:55.436] INFO: Handshake query: +0|whaticket-backend | [20:27:55.436] INFO: Token param: +0|whaticket-backend | [20:27:55.436] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [20:27:56.387] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [20:27:56.388] INFO: Socket ID: +0|whaticket-backend | [20:27:56.388] INFO: Handshake query: +0|whaticket-backend | [20:27:56.388] INFO: Token param: +0|whaticket-backend | [20:27:56.389] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [20:27:56.391] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [20:27:56.392] INFO: Socket ID: +0|whaticket-backend | [20:27:56.392] INFO: Handshake query: +0|whaticket-backend | [20:27:56.392] INFO: Token param: +0|whaticket-backend | [20:27:56.392] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [20:27:56.395] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [20:27:56.395] INFO: Socket ID: +0|whaticket-backend | [20:27:56.396] INFO: Handshake query: +0|whaticket-backend | [20:27:56.396] INFO: Token param: +0|whaticket-backend | [20:27:56.396] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [20:27:56.444] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [20:27:56.444] INFO: Socket ID: +0|whaticket-backend | [20:27:56.445] INFO: Handshake query: +0|whaticket-backend | [20:27:56.445] INFO: Token param: +0|whaticket-backend | [20:27:56.445] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [20:27:56.450] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [20:27:56.450] INFO: Socket ID: +0|whaticket-backend | [20:27:56.450] INFO: Handshake query: +0|whaticket-backend | [20:27:56.450] INFO: Token param: +0|whaticket-backend | [20:27:56.451] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [20:27:56.453] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [20:27:56.453] INFO: Socket ID: +0|whaticket-backend | [20:27:56.453] INFO: Handshake query: +0|whaticket-backend | [20:27:56.453] INFO: Token param: +0|whaticket-backend | [20:27:56.453] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [20:27:56.664] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [20:27:56.664] INFO: Socket ID: +0|whaticket-backend | [20:27:56.664] INFO: Handshake query: +0|whaticket-backend | [20:27:56.664] INFO: Token param: +0|whaticket-backend | [20:27:56.665] ERROR: Middleware authentication FAILED: diff --git a/backend/package.json b/backend/package.json index 65a0021..4146a08 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,7 @@ { "name": "backend", "version": "1.0.0", + "type": "commonjs", "description": "", "main": "index.js", "scripts": { @@ -8,7 +9,7 @@ "watch": "tsc -w", "db:migrate": "npx sequelize db:migrate", "db:seed": "sequelize db:seed:all", - "start": "nodemon dist/server.js", + "start": "node dist/server.js", "dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts", "pretest": "NODE_ENV=test sequelize db:migrate && NODE_ENV=test sequelize db:seed:all", "test": "NODE_ENV=test jest", @@ -17,7 +18,6 @@ "author": "", "license": "MIT", "dependencies": { - "@adiwajshing/baileys": "github:adiwajshing/Baileys", "@adiwajshing/keyed-db": "^0.2.4", "@ffmpeg-installer/ffmpeg": "^1.1.0", "@google-cloud/dialogflow": "^4.7.0", @@ -26,6 +26,7 @@ "@thream/socketio-jwt": "^3.0.0", "@types/lodash": "^4.14.182", "@types/pino": "^6.3.4", + "@whiskeysockets/baileys": "6.6.0", "axios": "^0.26.1", "bcryptjs": "^2.4.3", "bull": "^4.8.2", @@ -44,15 +45,17 @@ "multer": "^1.4.2", "mustache": "^4.2.0", "mysql2": "^2.2.5", + "node-cache": "^5.1.2", + "node-fetch": "^2.7.0", "pg": "^8.4.1", "pino": "^6.9.0", - "pino-pretty": "^4.3.0", + "pino-pretty": "^10.0.0", "qrcode-terminal": "^0.12.0", "reflect-metadata": "^0.1.13", "sequelize": "^5.22.3", "sequelize-cli": "^5.5.1", "sequelize-typescript": "^1.1.0", - "socket.io": "^3.0.5", + "socket.io": "^3.1.2", "socketio-jwt-auth": "^0.2.1", "uuid": "^8.3.2", "yup": "^0.32.8" @@ -91,6 +94,7 @@ "prettier": "^2.1.2", "supertest": "^5.0.0", "ts-jest": "^26.4.1", + "ts-node": "^10.9.1", "ts-node-dev": "^1.1.8", "typescript": "^4.6.3" } diff --git a/backend/package.json.backup b/backend/package.json.backup new file mode 100644 index 0000000..ef36d4a --- /dev/null +++ b/backend/package.json.backup @@ -0,0 +1,98 @@ +{ + "name": "backend", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "db:migrate": "npx sequelize db:migrate", + "db:seed": "sequelize db:seed:all", + "start": "nodemon dist/server.js", + "dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts", + "pretest": "NODE_ENV=test sequelize db:migrate && NODE_ENV=test sequelize db:seed:all", + "test": "NODE_ENV=test jest", + "posttest": "NODE_ENV=test sequelize db:migrate:undo:all" + }, + "author": "", + "license": "MIT", + "dependencies": { + "@adiwajshing/keyed-db": "^0.2.4", + "@ffmpeg-installer/ffmpeg": "^1.1.0", + "@google-cloud/dialogflow": "^4.7.0", + "@hapi/boom": "^9.1.4", + "@sentry/node": "^5.29.2", + "@thream/socketio-jwt": "^3.0.0", + "@types/lodash": "^4.14.182", + "@types/pino": "^6.3.4", + "@whiskeysockets/baileys": "^7.0.0-rc.9", + "axios": "^0.26.1", + "bcryptjs": "^2.4.3", + "bull": "^4.8.2", + "cookie-parser": "^1.4.5", + "cors": "^2.8.5", + "date-fns": "^2.16.1", + "dialogflow-fulfillment": "^0.6.1", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "express-async-errors": "^3.1.1", + "file-type": "^17.1.1", + "form-data": "^4.0.0", + "http-graceful-shutdown": "^2.3.2", + "jsonwebtoken": "^8.5.1", + "moment": "^2.29.4", + "multer": "^1.4.2", + "mustache": "^4.2.0", + "mysql2": "^2.2.5", + "node-cache": "^5.1.2", + "pg": "^8.4.1", + "pino": "^6.9.0", + "pino-pretty": "^10.0.0", + "qrcode-terminal": "^0.12.0", + "reflect-metadata": "^0.1.13", + "sequelize": "^5.22.3", + "sequelize-cli": "^5.5.1", + "sequelize-typescript": "^1.1.0", + "socket.io": "^3.1.2", + "socketio-jwt-auth": "^0.2.1", + "uuid": "^8.3.2", + "yup": "^0.32.8" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.2", + "@types/bluebird": "^3.5.32", + "@types/cookie-parser": "^1.4.2", + "@types/cors": "^2.8.7", + "@types/dialogflow-fulfillment": "^0.6.1", + "@types/express": "^4.17.13", + "@types/factory-girl": "^5.0.2", + "@types/faker": "^5.1.3", + "@types/jest": "^26.0.15", + "@types/jsonwebtoken": "^8.5.0", + "@types/multer": "^1.4.4", + "@types/mustache": "^4.1.2", + "@types/node": "^14.11.8", + "@types/socketio-jwt-auth": "^0.0.2", + "@types/supertest": "^2.0.10", + "@types/uuid": "^8.3.3", + "@types/validator": "^13.1.0", + "@types/yup": "^0.29.8", + "@typescript-eslint/eslint-plugin": "^4.4.0", + "@typescript-eslint/parser": "^4.4.0", + "eslint": "^7.10.0", + "eslint-config-airbnb-base": "^14.2.0", + "eslint-config-prettier": "^6.12.0", + "eslint-import-resolver-typescript": "^2.3.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-prettier": "^3.1.4", + "factory-girl": "^5.0.4", + "faker": "^5.1.0", + "jest": "^26.6.0", + "nodemon": "^2.0.4", + "prettier": "^2.1.2", + "supertest": "^5.0.0", + "ts-jest": "^26.4.1", + "ts-node-dev": "^1.1.8", + "typescript": "^4.6.3" + } +} diff --git a/backend/package.json.backup.20260207_095144 b/backend/package.json.backup.20260207_095144 new file mode 100644 index 0000000..a9217cb --- /dev/null +++ b/backend/package.json.backup.20260207_095144 @@ -0,0 +1,101 @@ +{ + "name": "backend", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "db:migrate": "npx sequelize db:migrate", + "db:seed": "sequelize db:seed:all", + "start": "nodemon dist/server.js", + "dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts", + "pretest": "NODE_ENV=test sequelize db:migrate && NODE_ENV=test sequelize db:seed:all", + "test": "NODE_ENV=test jest", + "posttest": "NODE_ENV=test sequelize db:migrate:undo:all" + }, + "author": "", + "license": "MIT", + "dependencies": { + "@adiwajshing/baileys": "^5.0.0", + "@adiwajshing/keyed-db": "^0.2.4", + "@ffmpeg-installer/ffmpeg": "^1.1.0", + "@google-cloud/dialogflow": "^4.7.0", + "@hapi/boom": "^9.1.4", + "@sentry/node": "^5.29.2", + "@thream/socketio-jwt": "^3.0.0", + "@types/lodash": "^4.14.182", + "@types/pino": "^6.3.4", + "@whiskeysockets/baileys": "^6.4.2", + "axios": "^0.26.1", + "bcryptjs": "^2.4.3", + "bull": "^4.8.2", + "cookie-parser": "^1.4.5", + "cors": "^2.8.5", + "date-fns": "^2.16.1", + "dialogflow-fulfillment": "^0.6.1", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "express-async-errors": "^3.1.1", + "file-type": "^17.1.1", + "form-data": "^4.0.0", + "http-graceful-shutdown": "^2.3.2", + "jsonwebtoken": "^8.5.1", + "moment": "^2.29.4", + "multer": "^1.4.2", + "mustache": "^4.2.0", + "mysql2": "^2.2.5", + "node-cache": "^5.1.2", + "node-fetch": "^2.7.0", + "pg": "^8.4.1", + "pino": "^6.9.0", + "pino-pretty": "^10.0.0", + "qrcode-terminal": "^0.12.0", + "reflect-metadata": "^0.1.13", + "sequelize": "^5.22.3", + "sequelize-cli": "^5.5.1", + "sequelize-typescript": "^1.1.0", + "socket.io": "^3.1.2", + "socketio-jwt-auth": "^0.2.1", + "uuid": "^8.3.2", + "yup": "^0.32.8" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.2", + "@types/bluebird": "^3.5.32", + "@types/cookie-parser": "^1.4.2", + "@types/cors": "^2.8.7", + "@types/dialogflow-fulfillment": "^0.6.1", + "@types/express": "^4.17.13", + "@types/factory-girl": "^5.0.2", + "@types/faker": "^5.1.3", + "@types/jest": "^26.0.15", + "@types/jsonwebtoken": "^8.5.0", + "@types/multer": "^1.4.4", + "@types/mustache": "^4.1.2", + "@types/node": "^14.11.8", + "@types/socketio-jwt-auth": "^0.0.2", + "@types/supertest": "^2.0.10", + "@types/uuid": "^8.3.3", + "@types/validator": "^13.1.0", + "@types/yup": "^0.29.8", + "@typescript-eslint/eslint-plugin": "^4.4.0", + "@typescript-eslint/parser": "^4.4.0", + "eslint": "^7.10.0", + "eslint-config-airbnb-base": "^14.2.0", + "eslint-config-prettier": "^6.12.0", + "eslint-import-resolver-typescript": "^2.3.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-prettier": "^3.1.4", + "factory-girl": "^5.0.4", + "faker": "^5.1.0", + "jest": "^26.6.0", + "nodemon": "^2.0.4", + "prettier": "^2.1.2", + "supertest": "^5.0.0", + "ts-jest": "^26.4.1", + "ts-node": "^10.9.2", + "ts-node-dev": "^1.1.8", + "typescript": "^4.6.3" + } +} diff --git a/backend/patch_message_listener.js b/backend/patch_message_listener.js new file mode 100644 index 0000000..6fe9247 --- /dev/null +++ b/backend/patch_message_listener.js @@ -0,0 +1,57 @@ +const fs = require('fs'); +const path = require('path'); + +const filePath = path.join(process.cwd(), 'dist/services/WbotServices/wbotMessageListener.js'); +console.log('Buscando archivo en:', filePath); + +if (!fs.existsSync(filePath)) { + console.log('❌ Archivo no encontrado'); + process.exit(1); +} + +let content = fs.readFileSync(filePath, 'utf8'); +const lines = content.split('\n'); +let found = false; + +for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('.chats.get(')) { + console.log(`✅ Encontrado en línea ${i+1}: ${lines[i].trim()}`); + + // Parchear la línea + const original = lines[i]; + const patched = original.replace( + /const count = (wbot\.store\.chats\.get\(.*?\));/, + 'const count = wbot.store && wbot.store.chats ? wbot.store.chats.get($2) : undefined;' + ); + + if (patched !== original) { + lines[i] = patched; + console.log('✅ Línea parcheada:', lines[i].trim()); + } else { + // Intentar otro patrón + const patched2 = original.replace( + /\.chats\.get\(/, + ' && $1.chats ? $1.chats.get(' + ); + if (patched2 !== original) { + lines[i] = patched2; + console.log('✅ Línea parcheada (patrón 2):', lines[i].trim()); + } + } + found = true; + break; + } +} + +if (found) { + fs.writeFileSync(filePath, lines.join('\n')); + console.log('✅ Archivo parcheado exitosamente'); +} else { + console.log('❌ No se encontró la línea con .chats.get()'); + // Buscar cualquier referencia a chats + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('chats')) { + console.log(`Línea ${i+1}: ${lines[i].trim()}`); + } + } +} diff --git a/backend/probarEnvioLID.js b/backend/probarEnvioLID.js new file mode 100644 index 0000000..7d930a3 --- /dev/null +++ b/backend/probarEnvioLID.js @@ -0,0 +1,48 @@ +const { default: makeWASocket } = require('@whiskeysockets/baileys'); +const { Sequelize } = require('sequelize'); + +async function probarEnvioLID() { + console.log('🧪 PROBANDO ENVÍO USANDO MAPPING LID'); + + try { + // 1. Conectar a BD para obtener el mapping + const sequelize = new Sequelize('unkbot', 'unkbot', 'TuPasswordSeguro1', { + host: 'localhost', + dialect: 'postgres', + logging: false + }); + + // 2. Buscar mapping del LID + const resultado = await sequelize.query( + `SELECT value::text as numero_real FROM "BaileysSessions" + WHERE name = 'lid-mapping-28308196585718_reverse' AND "whatsappId" = 10`, + { type: Sequelize.QueryTypes.SELECT } + ); + + if (resultado.length === 0) { + console.log('❌ No se encontró mapping para LID 28308196585718'); + return; + } + + const numeroReal = JSON.parse(resultado[0].numero_real); + console.log('✅ Mapping encontrado:'); + console.log(' LID: 28308196585718'); + console.log(' Número real:', numeroReal); + console.log(' JID para enviar:', `${numeroReal}@s.whatsapp.net`); + + // 3. Cargar sesión WhatsApp (necesitaríamos las creds) + console.log('\n⚠️ Para probar envío real necesitamos:'); + console.log(' a) Cargar sesión WhatsApp desde BD'); + console.log(' b) Conectar socket'); + console.log(' c) Enviar mensaje de prueba'); + + console.log('\n🎯 CONCLUSIÓN: El mapping existe y deberíamos usarlo en el código.'); + + await sequelize.close(); + + } catch (error) { + console.error('❌ Error:', error.message); + } +} + +probarEnvioLID().catch(console.error); diff --git a/backend/src/app.ts b/backend/src/app.ts index 92d287c..94838a4 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -20,8 +20,8 @@ const app = express(); app.use( cors({ credentials: true, - origin: process.env.FRONTEND_URL - }) + origin: [process.env.FRONTEND_URL || "http://localhost:3000", "https://app.sistemasarrecifes.com.ar", "https://api.sistemasarrecifes.com.ar"], + }) ); app.use(express.json({ diff --git a/backend/src/controllers/ContactController.ts b/backend/src/controllers/ContactController.ts index 87d040b..2cc67eb 100644 --- a/backend/src/controllers/ContactController.ts +++ b/backend/src/controllers/ContactController.ts @@ -25,7 +25,7 @@ type IndexGetContactQuery = { number: string; }; -interface ExtraInfo { +interface Extrainfo { name: string; value: string; } @@ -33,7 +33,7 @@ interface ContactData { name: string; number: string; email?: string; - extraInfo?: ExtraInfo[]; + extrainfo?: Extrainfo[]; } export const index = async (req: Request, res: Response): Promise => { @@ -83,13 +83,13 @@ export const store = async (req: Request, res: Response): Promise => { const profilePicUrl = await GetProfilePicUrl(validNumber); - const { name, extraInfo, email } = newContact; + const { name, extrainfo, email } = newContact; const contact = await CreateContactService({ name, number: validNumber, email, - extraInfo, + extrainfo, profilePicUrl }); diff --git a/backend/src/controllers/MessageController.ts b/backend/src/controllers/MessageController.ts index d41c714..402f7ed 100644 --- a/backend/src/controllers/MessageController.ts +++ b/backend/src/controllers/MessageController.ts @@ -12,6 +12,7 @@ import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage"; import sendFaceMedia from "../services/FacebookServices/sendFacebookMessageMedia"; import sendFaceMessage from "../services/FacebookServices/sendFacebookMessage"; +import WebhookSender from "../utils/webhookSender"; // NUEVA IMPORTACIÓN type IndexQuery = { pageNumber: string; @@ -70,6 +71,23 @@ export const store = async (req: Request, res: Response): Promise => { } } + // ENVIAR WEBHOOK A N8N DESPUÉS DE ENVIAR MENSAJE + try { + // Obtener el último mensaje para enviar en el webhook + const messages = await Message.findAll({ + where: { ticketId: ticket.id }, + order: [['createdAt', 'DESC']], + limit: 1 + }); + + if (messages.length > 0) { + await WebhookSender.sendNewMessageWebhook(messages[0], ticket); + } + } catch (error) { + console.error('Error enviando webhook para nuevo mensaje:', error); + // No fallar el proceso principal si el webhook falla + } + return res.send(); }; diff --git a/backend/src/controllers/MessageController.ts.backup b/backend/src/controllers/MessageController.ts.backup new file mode 100644 index 0000000..d41c714 --- /dev/null +++ b/backend/src/controllers/MessageController.ts.backup @@ -0,0 +1,91 @@ +import { Request, Response } from "express"; + +import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead"; +import { getIO } from "../libs/socket"; +import Message from "../models/Message"; + +import ListMessagesService from "../services/MessageServices/ListMessagesService"; +import ShowTicketService from "../services/TicketServices/ShowTicketService"; +import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage"; +import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia"; +import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage"; + +import sendFaceMedia from "../services/FacebookServices/sendFacebookMessageMedia"; +import sendFaceMessage from "../services/FacebookServices/sendFacebookMessage"; + +type IndexQuery = { + pageNumber: string; +}; + +type MessageData = { + body: string; + fromMe: boolean; + read: boolean; + quotedMsg?: Message; +}; + +export const index = async (req: Request, res: Response): Promise => { + const { ticketId } = req.params; + const { pageNumber } = req.query as IndexQuery; + + const { count, messages, ticket, hasMore } = await ListMessagesService({ + pageNumber, + ticketId + }); + + SetTicketMessagesAsRead(ticket); + + return res.json({ count, messages, ticket, hasMore }); +}; + +export const store = async (req: Request, res: Response): Promise => { + const { ticketId } = req.params; + const { body, quotedMsg }: MessageData = req.body; + const medias = req.files as Express.Multer.File[]; + + const ticket = await ShowTicketService(ticketId); + + SetTicketMessagesAsRead(ticket); + + if (medias) { + await Promise.all( + medias.map(async (media: Express.Multer.File) => { + if (ticket.channel === "whatsapp") { + await SendWhatsAppMedia({ media, ticket }); + } + + if (ticket.channel === "facebook" || ticket.channel === "instagram") { + await sendFaceMedia({ media, ticket }); + } + }) + ); + } else { + if (ticket.channel === "whatsapp") { + await SendWhatsAppMessage({ body, ticket, quotedMsg }); + } + + if (ticket.channel === "facebook" || ticket.channel === "instagram") { + console.log("facebook"); + await sendFaceMessage({ body, ticket, quotedMsg }); + } + } + + return res.send(); +}; + +export const remove = async ( + req: Request, + res: Response +): Promise => { + const { messageId } = req.params; + + const message = await DeleteWhatsAppMessage(messageId); + + const io = getIO(); + io.to(message.ticketId.toString()).emit("appMessage", { + action: "update", + message + }); + + return res.send(); +}; diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index c38b789..710907f 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -8,6 +8,7 @@ import ShowTicketService from "../services/TicketServices/ShowTicketService"; import UpdateTicketService from "../services/TicketServices/UpdateTicketService"; import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage"; import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService"; +import WebhookSender from "../utils/webhookSender"; // NUEVA IMPORTACIÓN import Ticket from "../models/Ticket"; @@ -83,10 +84,21 @@ export const store = async (req: Request, res: Response): Promise => { }); const io = getIO(); - io.to(ticket.status).emit("ticket", { - action: "update", - ticket - }); + // send status to the specific queue channel + io.to(ticket.status) + .to(`queue-${ticket.queueId}-${ticket.status}`) + .emit("ticket", { + action: "update", + ticket + }); + + // ENVIAR WEBHOOK A N8N PARA NUEVO TICKET + try { + await WebhookSender.sendTicketUpdateWebhook(ticket); + } catch (error) { + console.error('Error enviando webhook para nuevo ticket:', error); + // No fallar el proceso principal si el webhook falla + } return res.status(200).json(ticket); }; @@ -108,6 +120,10 @@ export const update = async ( const { ticketId } = req.params; const ticketData: TicketData = req.body; + // Obtener el ticket actual para comparar estados + const oldTicket = await Ticket.findByPk(ticketId); + const oldStatus = oldTicket ? oldTicket.status : undefined; + const { ticket } = await UpdateTicketService({ ticketData, ticketId @@ -126,6 +142,17 @@ export const update = async ( } } + // ENVIAR WEBHOOK A N8N PARA ACTUALIZACIÓN DE TICKET + try { + // Solo enviar webhook si el estado cambió + if (oldStatus && oldStatus !== ticket.status) { + await WebhookSender.sendTicketUpdateWebhook(ticket, oldStatus); + } + } catch (error) { + console.error('Error enviando webhook para actualización de ticket:', error); + // No fallar el proceso principal si el webhook falla + } + return res.status(200).json(ticket); }; @@ -138,13 +165,24 @@ export const remove = async ( const ticket = await DeleteTicketService(ticketId); const io = getIO(); + // send delete message to queues of ticket's current status io.to(ticket.status) .to(ticketId) .to("notification") + .to(`queue-${ticket.queueId}-${ticket.status}`) + .to(`queue-${ticket.queueId}-notification`) .emit("ticket", { action: "delete", ticketId: +ticketId }); + // ENVIAR WEBHOOK A N8N PARA ELIMINACIÓN DE TICKET + try { + await WebhookSender.sendTicketUpdateWebhook(ticket); + } catch (error) { + console.error('Error enviando webhook para eliminación de ticket:', error); + // No fallar el proceso principal si el webhook falla + } + return res.status(200).json({ message: "ticket deleted" }); }; diff --git a/backend/src/controllers/TicketController.ts.backup b/backend/src/controllers/TicketController.ts.backup new file mode 100644 index 0000000..3f0b9fa --- /dev/null +++ b/backend/src/controllers/TicketController.ts.backup @@ -0,0 +1,156 @@ +import { Request, Response } from "express"; +import { getIO } from "../libs/socket"; + +import CreateTicketService from "../services/TicketServices/CreateTicketService"; +import DeleteTicketService from "../services/TicketServices/DeleteTicketService"; +import ListTicketsService from "../services/TicketServices/ListTicketsService"; +import ShowTicketService from "../services/TicketServices/ShowTicketService"; +import UpdateTicketService from "../services/TicketServices/UpdateTicketService"; +import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage"; +import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService"; + +import Ticket from "../models/Ticket"; + +type IndexQuery = { + searchParam: string; + pageNumber: string; + status: string; + date: string; + updatedAt?: string; + showAll: string; + withUnreadMessages: string; + queueIds: string; + tags: string; +}; + +interface TicketData { + contactId: number; + status: string; + queueId: number; + userId: number; +} + +export const index = async (req: Request, res: Response): Promise => { + const { + pageNumber, + status, + date, + updatedAt, + searchParam, + showAll, + queueIds: queueIdsStringified, + tags: tagIdsStringified, + withUnreadMessages + } = req.query as IndexQuery; + + const userId = req.user.id; + + let queueIds: number[] = []; + let tagsIds: number[] = []; + + if (queueIdsStringified) { + queueIds = JSON.parse(queueIdsStringified); + } + + if (tagIdsStringified) { + tagsIds = JSON.parse(tagIdsStringified); + } + + const { tickets, count, hasMore } = await ListTicketsService({ + searchParam, + tags: tagsIds, + pageNumber, + status, + date, + updatedAt, + showAll, + userId, + queueIds, + withUnreadMessages + }); + + return res.status(200).json({ tickets, count, hasMore }); +}; + +export const store = async (req: Request, res: Response): Promise => { + const { contactId, status, userId, queueId }: TicketData = req.body; + + const ticket = await CreateTicketService({ + contactId, + status, + userId, + queueId + }); + + const io = getIO(); + // send status to the specific queue channel + io.to(ticket.status) + .to(`queue-${ticket.queueId}-${ticket.status}`) + .emit("ticket", { + action: "update", + ticket + }); + + return res.status(200).json(ticket); +}; + +export const show = async (req: Request, res: Response): Promise => { + const { ticketId } = req.params; + + const ticket = await ShowTicketService(ticketId); + + const contact = ticket; + + return res.status(200).json(contact); +}; + +export const update = async ( + req: Request, + res: Response +): Promise => { + const { ticketId } = req.params; + const ticketData: TicketData = req.body; + + const { ticket } = await UpdateTicketService({ + ticketData, + ticketId + }); + + if (ticket.status === "closed") { + const whatsapp = await ShowWhatsAppService(ticket.whatsappId); + + const { farewellMessage } = whatsapp; + + if (farewellMessage) { + await SendWhatsAppMessage({ + body: farewellMessage, + ticket + }); + } + } + + return res.status(200).json(ticket); +}; + +export const remove = async ( + req: Request, + res: Response +): Promise => { + const { ticketId } = req.params; + + const ticket = await DeleteTicketService(ticketId); + + const io = getIO(); + // send delete message to queues of ticket's current status + io.to(ticket.status) + .to(ticketId) + .to("notification") + .to(`queue-${ticket.queueId}-${ticket.status}`) + .to(`queue-${ticket.queueId}-notification`) + .emit("ticket", { + action: "delete", + ticketId: +ticketId + }); + + return res.status(200).json({ message: "ticket deleted" }); +}; diff --git a/backend/src/helpers/GetRealNumberFromLid.ts b/backend/src/helpers/GetRealNumberFromLid.ts new file mode 100644 index 0000000..6e5237f --- /dev/null +++ b/backend/src/helpers/GetRealNumberFromLid.ts @@ -0,0 +1,174 @@ +import BaileysSessions from "../models/BaileysSessions"; +import { Op } from 'sequelize'; + +interface MappingResult { + realNumber: string; + isLid: boolean; + original: string; + lidNumber?: string; + foundInBaileysSessions: boolean; +} + +function extractStringValue(value: any): string | null { + try { + if (value === null || value === undefined) return null; + + if (typeof value === 'string') { + return value.replace(/^"|"$/g, '').trim(); + } + + if (typeof value === 'object') { + const stringValues = Object.values(value).filter(v => typeof v === 'string'); + if (stringValues.length > 0) { + return (stringValues[0] as string).replace(/^"|"$/g, '').trim(); + } + + const str = JSON.stringify(value); + const match = str.match(/"([^"\\]*(?:\\.[^"\\]*)*)"/); + if (match) return match[1].replace(/\\"/g, '"').trim(); + return str.replace(/^"|"$/g, '').trim(); + } + + return String(value).replace(/^"|"$/g, '').trim(); + } catch (error) { + console.error('[extractStringValue] Error:', error); + return null; + } +} + +const GetRealNumberFromLid = async ( + sessionId: string, + lidOrNumber: string +): Promise => { + console.log(`\n🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA`); + console.log(` Input: "${lidOrNumber}"`); + console.log(` SessionId: "${sessionId}"`); + + try { + let cleanInput = lidOrNumber; + if (lidOrNumber.includes('@')) cleanInput = lidOrNumber.split('@')[0]; + + console.log(` Clean input: "${cleanInput}" (${cleanInput.length} dígitos)`); + + const isNormalNumber = cleanInput.length <= 15 && /^\d+$/.test(cleanInput); + console.log(` Tipo: ${isNormalNumber ? 'Número normal' : 'Posible LID'}`); + + // 🎯 BUSCAR MAPPING EXACTO + const exactReverseKey = `lid-mapping-${cleanInput}_reverse`; + console.log(`\n🔍 Buscando mapping exacto: ${exactReverseKey}`); + + const exactMappings = await BaileysSessions.findAll({ + where: { name: exactReverseKey }, + order: [['whatsappId', 'DESC'], ['updatedAt', 'DESC']] + }); + + if (exactMappings.length > 0) { + console.log(`✅ Encontrado ${exactMappings.length} mapping(s) exacto(s)`); + const mapping = exactMappings[0]; + const realNumber = extractStringValue(mapping.value); + + if (realNumber) { + console.log(` whatsappId: ${mapping.whatsappId}`); + console.log(` value: "${realNumber}"`); + console.log(`🎯 ÉXITO: ${cleanInput} -> ${realNumber}`); + return { + realNumber, + isLid: true, + original: lidOrNumber, + lidNumber: cleanInput, + foundInBaileysSessions: true + }; + } + console.log(` ⚠️ No se pudo extraer el valor`); + } else { + console.log(` ⚠️ No se encontró mapping exacto`); + } + + // 🎯 BUSCAR COINCIDENCIAS PARCIALES + console.log(`\n🔍 Buscando coincidencias parciales...`); + const allReverseMappings = await BaileysSessions.findAll({ + where: { name: { [Op.like]: 'lid-mapping-%_reverse' } }, + order: [['whatsappId', 'DESC'], ['updatedAt', 'DESC']] + }); + + console.log(` Total mappings: ${allReverseMappings.length}`); + + for (const mapping of allReverseMappings) { + if (!mapping.name) continue; + const lidFromKey = mapping.name.replace('lid-mapping-', '').replace('_reverse', ''); + const minLength = Math.min(lidFromKey.length, cleanInput.length); + + if (minLength >= 6) { + const inputStart = cleanInput.substring(0, minLength); + const lidStart = lidFromKey.substring(0, minLength); + + if (inputStart === lidStart) { + console.log(`✅ Coincidencia primeros ${minLength} dígitos`); + console.log(` LID BD: ${lidFromKey} (${lidFromKey.length})`); + console.log(` Input: ${cleanInput} (${cleanInput.length})`); + + const realNumber = extractStringValue(mapping.value); + if (realNumber) { + console.log(` whatsappId: ${mapping.whatsappId}`); + console.log(` Número: "${realNumber}"`); + console.log(`🎯 CONVERTIDO: ${cleanInput} -> ${realNumber}`); + return { + realNumber, + isLid: true, + original: lidOrNumber, + lidNumber: lidFromKey, + foundInBaileysSessions: true + }; + } + console.log(` ⚠️ No se pudo extraer número de: ${mapping.value}`); + } + } + } + + // 🎯 BUSCAR SI EL INPUT ES NÚMERO CON LID ASOCIADO + if (isNormalNumber) { + console.log(`\n🔍 Buscando LID para número: ${cleanInput}`); + const directMapping = await BaileysSessions.findOne({ + where: { name: `lid-mapping-${cleanInput}` } + }); + + if (directMapping) { + console.log(`✅ Número tiene LID asociado`); + const lidNumber = extractStringValue(directMapping.value); + if (lidNumber) { + console.log(` LID: "${lidNumber}"`); + return { + realNumber: cleanInput, + isLid: false, + original: lidOrNumber, + lidNumber, + foundInBaileysSessions: true + }; + } + console.log(` ⚠️ No se pudo extraer LID de: ${directMapping.value}`); + } + console.log(` ℹ️ No se encontró LID para número ${cleanInput}`); + } + + // NO SE ENCONTRÓ + console.log(`\n⚠️ No se encontró mapping para: ${cleanInput}`); + console.log(` Devolviendo input original`); + return { + realNumber: cleanInput, + isLid: cleanInput.length > 12, + original: lidOrNumber, + foundInBaileysSessions: false + }; + + } catch (error) { + console.error(`❌ [GetRealNumberFromLid] ERROR:`, error); + return { + realNumber: lidOrNumber, + isLid: false, + original: lidOrNumber, + foundInBaileysSessions: false + }; + } +}; + +export default GetRealNumberFromLid; diff --git a/backend/src/helpers/GetRealNumberFromLid.ts.bak b/backend/src/helpers/GetRealNumberFromLid.ts.bak new file mode 100644 index 0000000..7f91f67 --- /dev/null +++ b/backend/src/helpers/GetRealNumberFromLid.ts.bak @@ -0,0 +1,221 @@ +import BaileysSessions from "../models/BaileysSessions"; +import { Op } from 'sequelize'; + +interface MappingResult { + realNumber: string; + isLid: boolean; + original: string; + lidNumber?: string; +} + +// Función helper para extraer valor del JSON +function extractValueFromJson(jsonValue: any): string | null { + try { + if (!jsonValue) return null; + + if (typeof jsonValue === 'string') { + // Si es string con comillas, removerlas + return jsonValue.replace(/"/g, ''); + } + + if (typeof jsonValue === 'object') { + // Si es objeto, tomar el primer valor string + for (const value of Object.values(jsonValue)) { + if (typeof value === 'string') { + return value.replace(/"/g, ''); + } + } + } + + return null; + } catch (error) { + console.error(`[extractValueFromJson] Error:`, error); + return null; + } +} + +const GetRealNumberFromLid = async ( + sessionId: string, + lidOrNumber: string +): Promise => { + try { + console.log(`[LID Helper] Buscando número real para: ${lidOrNumber}, sessionId: ${sessionId}`); + + // Limpiar input + let cleanInput = lidOrNumber; + if (lidOrNumber.includes('@')) { + cleanInput = lidOrNumber.split('@')[0]; + } + + // Si es número normal (hasta 15 dígitos) + if (cleanInput.length <= 15 && /^\d+$/.test(cleanInput)) { + return { + realNumber: cleanInput, + isLid: false, + original: lidOrNumber + }; + } + + // Convertir sessionId a número (whatsappId es integer) + const whatsappId = parseInt(sessionId) || 1; + + console.log(`[LID Helper] Buscando en BaileysSessions whatsappId=${whatsappId}`); + + // 🎯 ESTRATEGIA 1: Buscar exactamente por name + const reverseKey = `lid-mapping-${cleanInput}_reverse`; + console.log(`[LID Helper] Buscando key exacta: ${reverseKey}`); + + const reverseSession = await BaileysSessions.findOne({ + where: { + whatsappId, + name: reverseKey + } + }); + + if (reverseSession && reverseSession.value) { + const realNumber = extractValueFromJson(reverseSession.value); + if (realNumber) { + console.log(`[LID Helper] ✅ Mapping exacto encontrado: ${cleanInput} -> ${realNumber}`); + return { + realNumber, + isLid: true, + original: lidOrNumber, + lidNumber: cleanInput + }; + } + } + + // 🎯 ESTRATEGIA 2: Buscar todos los mappings reverse y encontrar coincidencias parciales + console.log(`[LID Helper] Búsqueda exacta falló, buscando coincidencias parciales...`); + + const allMappings = await BaileysSessions.findAll({ + where: { + whatsappId, + name: { + [Op.like]: 'lid-mapping-%_reverse' + } + } + }); + + console.log(`[LID Helper] Encontrados ${allMappings.length} mappings reverse`); + + for (const mapping of allMappings) { + if (!mapping.name) continue; + + // Extraer el LID del nombre (ej: lid-mapping-129888535236789_reverse) + const lidFromKey = mapping.name.replace('lid-mapping-', '').replace('_reverse', ''); + + // 🚨 CLAVE: Verificar coincidencia parcial + // Caso 1: Nuestro input (1298885352) está contenido en el LID real (129888535236789) + // Caso 2: El LID real está contenido en nuestro input + // Caso 3: Coinciden los primeros dígitos (común con LIDs truncados) + if (lidFromKey.includes(cleanInput) || cleanInput.includes(lidFromKey)) { + const realNumber = extractValueFromJson(mapping.value); + if (realNumber) { + console.log(`[LID Helper] ✅ Coincidencia parcial encontrada:`); + console.log(` Input: ${cleanInput}`); + console.log(` LID en BD: ${lidFromKey}`); + console.log(` Número real: ${realNumber}`); + return { + realNumber, + isLid: true, + original: lidOrNumber, + lidNumber: lidFromKey + }; + } + } + + // También verificar si los primeros dígitos coinciden (para LIDs truncados) + const minLength = Math.min(lidFromKey.length, cleanInput.length); + const firstDigitsMatch = lidFromKey.substring(0, minLength) === cleanInput.substring(0, minLength); + + if (firstDigitsMatch && minLength >= 8) { // Al menos 8 dígitos coinciden + const realNumber = extractValueFromJson(mapping.value); + if (realNumber) { + console.log(`[LID Helper] ✅ Coincidencia por primeros dígitos:`); + console.log(` Input: ${cleanInput} (${cleanInput.length} dígitos)`); + console.log(` LID en BD: ${lidFromKey} (${lidFromKey.length} dígitos)`); + console.log(` Coinciden primeros ${minLength} dígitos`); + console.log(` Número real: ${realNumber}`); + return { + realNumber, + isLid: true, + original: lidOrNumber, + lidNumber: lidFromKey + }; + } + } + } + + // 🎯 ESTRATEGIA 3: Buscar por el número real (por si el input ya es número) + const directKey = `lid-mapping-${cleanInput}`; + const directSession = await BaileysSessions.findOne({ + where: { + whatsappId, + name: directKey + } + }); + + if (directSession && directSession.value) { + console.log(`[LID Helper] ℹ️ Input es número real, tiene LID asociado`); + const lidNumber = extractValueFromJson(directSession.value); + return { + realNumber: cleanInput, + isLid: false, + original: lidOrNumber, + lidNumber + }; + } + + // 🎯 ESTRATEGIA 4: Buscar en mappings directos (sin _reverse) + const allDirectMappings = await BaileysSessions.findAll({ + where: { + whatsappId, + name: { + [Op.like]: 'lid-mapping-%', + [Op.notLike]: '%_reverse' + } + } + }); + + for (const mapping of allDirectMappings) { + if (!mapping.name) continue; + + // Extraer el número del nombre (ej: lid-mapping-5492478461419) + const numberFromKey = mapping.name.replace('lid-mapping-', ''); + + if (numberFromKey === cleanInput || cleanInput.includes(numberFromKey) || numberFromKey.includes(cleanInput)) { + const lidNumber = extractValueFromJson(mapping.value); + if (lidNumber) { + console.log(`[LID Helper] ✅ Encontrado a través de número real:`); + console.log(` Número: ${numberFromKey}`); + console.log(` LID asociado: ${lidNumber}`); + return { + realNumber: cleanInput, + isLid: false, + original: lidOrNumber, + lidNumber + }; + } + } + } + + // No se encontró mapping + console.log(`[LID Helper] ⚠️ No se encontró mapping para: ${cleanInput}`); + return { + realNumber: cleanInput, + isLid: cleanInput.length > 12, + original: lidOrNumber + }; + + } catch (error) { + console.error(`[LID Helper] ❌ Error:`, error); + return { + realNumber: lidOrNumber, + isLid: false, + original: lidOrNumber + }; + } +}; + +export default GetRealNumberFromLid; diff --git a/backend/src/helpers/GetTicketWbot.ts b/backend/src/helpers/GetTicketWbot.ts index c9ed8fd..b83e77b 100644 --- a/backend/src/helpers/GetTicketWbot.ts +++ b/backend/src/helpers/GetTicketWbot.ts @@ -1,4 +1,4 @@ -import { WASocket } from "@adiwajshing/baileys"; +import { WASocket } from "@whiskeysockets/baileys"; import { getWbot } from "../libs/wbot"; import GetDefaultWhatsApp from "./GetDefaultWhatsApp"; import Ticket from "../models/Ticket"; diff --git a/backend/src/helpers/GetWbotMessage.ts b/backend/src/helpers/GetWbotMessage.ts index fbbe29e..e5f7413 100644 --- a/backend/src/helpers/GetWbotMessage.ts +++ b/backend/src/helpers/GetWbotMessage.ts @@ -1,4 +1,4 @@ -import { proto } from "@adiwajshing/baileys"; +import { proto } from "@whiskeysockets/baileys"; import Ticket from "../models/Ticket"; import AppError from "../errors/AppError"; import GetMessageService from "../services/MessageServices/GetMessagesService"; diff --git a/backend/src/helpers/GetWhatsappWbot.ts b/backend/src/helpers/GetWhatsappWbot.ts index 7228df6..e60ac6f 100644 --- a/backend/src/helpers/GetWhatsappWbot.ts +++ b/backend/src/helpers/GetWhatsappWbot.ts @@ -1,15 +1,26 @@ -import { WASocket } from "@adiwajshing/baileys"; +import { WASocket } from "@whiskeysockets/baileys"; import { Store } from "../libs/store"; import { getWbot } from "../libs/wbot"; import Whatsapp from "../models/Whatsapp"; -type Session = WASocket & { +export type Session = WASocket & { id?: number; store?: Store; }; + const GetWhatsappWbot = async (whatsapp: Whatsapp): Promise => { - const wbot = getWbot(whatsapp.id); - return wbot; + try { + const wbot = await getWbot(whatsapp.id); + + if (!wbot) { + throw new Error(`WhatsApp bot not found for ID: ${whatsapp.id}`); + } + + return wbot; + } catch (error) { + console.error(`Error getting WhatsApp bot for ID ${whatsapp.id}:`, error); + throw error; + } }; -export default GetWhatsappWbot; +export default GetWhatsappWbot; \ No newline at end of file diff --git a/backend/src/helpers/SendMessage.ts b/backend/src/helpers/SendMessage.ts index ac3dcc9..641e4de 100644 --- a/backend/src/helpers/SendMessage.ts +++ b/backend/src/helpers/SendMessage.ts @@ -1,9 +1,10 @@ -import Whatsapp from "../models/Whatsapp"; -import GetWhatsappWbot from "./GetWhatsappWbot"; -import SendWhatsAppMedia, { processAudio, processAudioFile } from "../services/WbotServices/SendWhatsAppMedia"; import mime from "mime-types"; import fs from "fs"; -import { AnyMessageContent } from "@adiwajshing/baileys"; +import { AnyMessageContent } from "@whiskeysockets/baileys"; +import Whatsapp from "../models/Whatsapp"; +import GetWhatsappWbot from "./GetWhatsappWbot"; +import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia"; +import { processAudio, processAudioFile } from "../services/WbotServices/SendWhatsAppMedia"; export type MessageData = { number: number | string; @@ -17,84 +18,97 @@ export const SendMessage = async ( ): Promise => { try { const wbot = await GetWhatsappWbot(whatsapp); + + if (!wbot) { + throw new Error("WhatsApp bot is not initialized"); + } + const jid = `${messageData.number}@s.whatsapp.net`; let message: any; const body = `\u200e${messageData.body}`; - console.log("envio de mensagem"); + + console.log("Enviando mensaje a:", jid); + if (messageData.mediaPath) { + // Verificar si el archivo existe + if (!fs.existsSync(messageData.mediaPath)) { + throw new Error(`Media file not found: ${messageData.mediaPath}`); + } + const mimetype = mime.lookup(messageData.mediaPath) || "application/octet-stream"; const media = { path: messageData.mediaPath, - mimetype: mime.lookup(messageData.mediaPath) - } as Express.Multer.File; + mimetype, + originalname: messageData.mediaPath.split("/").pop() || "file" + }; - console.log(media) + console.log("Media detected:", media); const pathMedia = messageData.mediaPath; const typeMessage = media.mimetype.split("/")[0]; let options: AnyMessageContent; - if (typeMessage === "video") { - options = { - video: fs.readFileSync(pathMedia), - caption: body, - fileName: media.originalname - // gifPlayback: true - }; - } else if (typeMessage === "audio") { - const typeAudio = media.originalname.includes("audio-record-site"); - if (typeAudio) { - const convert = await processAudio(media.path); + switch (typeMessage) { + case "video": options = { - audio: fs.readFileSync(convert), + video: fs.readFileSync(pathMedia), + caption: body, + fileName: media.originalname + }; + break; + + case "audio": + const typeAudio = media.originalname.includes("audio-record-site"); + let audioPath: string; + + if (typeAudio) { + audioPath = await processAudio(media.path); + } else { + audioPath = await processAudioFile(media.path); + } + + options = { + audio: fs.readFileSync(audioPath), mimetype: typeAudio ? "audio/mp4" : media.mimetype, - ptt: true + ptt: typeAudio // push-to-talk solo para grabaciones + }; + break; + + case "document": + case "application": + options = { + document: fs.readFileSync(pathMedia), + caption: body, + fileName: media.originalname, + mimetype: media.mimetype }; - } else { - const convert = await processAudioFile(media.path); + break; + + default: + // Para imágenes y otros tipos options = { - audio: fs.readFileSync(convert), - mimetype: typeAudio ? "audio/mp4" : media.mimetype + image: fs.readFileSync(pathMedia), + caption: body }; - } - } else if (typeMessage === "document") { - options = { - document: fs.readFileSync(pathMedia), - caption: body, - fileName: media.originalname, - mimetype: media.mimetype - }; - } else if (typeMessage === "application") { - options = { - document: fs.readFileSync(pathMedia), - caption: body, - fileName: media.originalname, - mimetype: media.mimetype - }; - } else { - options = { - image: fs.readFileSync(pathMedia), - caption: body - }; } - message = await wbot.sendMessage( - jid, - { - ...options - } - ); + message = await wbot.sendMessage(jid, options); + console.log("Media message sent:", message.key.id); - console.log(message); } else { - console.log(body); - message = await wbot.sendMessage(jid, { - text: body - }); + // Mensaje de texto simple + console.log("Text message body:", body); + message = await wbot.sendMessage(jid, { text: body }); } return message; + } catch (err: any) { - console.log(err) - throw new Error(err); + console.error("Error in SendMessage:", err); + // Mejor manejo del error + if (err instanceof Error) { + throw new Error(`Failed to send message: ${err.message}`); + } else { + throw new Error("Failed to send message: Unknown error"); + } } -}; +}; \ No newline at end of file diff --git a/backend/src/helpers/SendMessage.ts.backup b/backend/src/helpers/SendMessage.ts.backup new file mode 100644 index 0000000..b7d12e7 --- /dev/null +++ b/backend/src/helpers/SendMessage.ts.backup @@ -0,0 +1,116 @@ +import mime from "mime-types"; +import fs from "fs"; +import { AnyMessageContent } from "@whiskeysockets/baileys"; +import Whatsapp from "../models/Whatsapp"; +import GetWhatsappWbot from "./GetWhatsappWbot"; +import SendWhatsAppMedia, { + processAudio, + processAudioFile +} from "../services/WbotServices/SendWhatsAppMedia"; + +export type MessageData = { + number: number | string; + body: string; + mediaPath?: string; +}; + +export const SendMessage = async ( + whatsapp: Whatsapp, + messageData: MessageData +): Promise => { + try { + const wbot = await GetWhatsappWbot(whatsapp); + + if (!wbot) { + throw new Error("WhatsApp bot is not initialized"); + } + + const jid = `${messageData.number}@s.whatsapp.net`; + let message: any; + const body = `\u200e${messageData.body}`; + + console.log("Enviando mensaje a:", jid); + + if (messageData.mediaPath) { + // Verificar si el archivo existe + if (!fs.existsSync(messageData.mediaPath)) { + throw new Error(`Media file not found: ${messageData.mediaPath}`); + } + + const mimetype = mime.lookup(messageData.mediaPath) || "application/octet-stream"; + const media = { + path: messageData.mediaPath, + mimetype, + originalname: messageData.mediaPath.split("/").pop() || "file" + }; + + console.log("Media detected:", media); + const pathMedia = messageData.mediaPath; + const typeMessage = media.mimetype.split("/")[0]; + let options: AnyMessageContent; + + switch (typeMessage) { + case "video": + options = { + video: fs.readFileSync(pathMedia), + caption: body, + fileName: media.originalname + }; + break; + + case "audio": + const typeAudio = media.originalname.includes("audio-record-site"); + let audioPath: string; + + if (typeAudio) { + audioPath = await processAudio(media.path); + } else { + audioPath = await processAudioFile(media.path); + } + + options = { + audio: fs.readFileSync(audioPath), + mimetype: typeAudio ? "audio/mp4" : media.mimetype, + ptt: typeAudio // push-to-talk solo para grabaciones + }; + break; + + case "document": + case "application": + options = { + document: fs.readFileSync(pathMedia), + caption: body, + fileName: media.originalname, + mimetype: media.mimetype + }; + break; + + default: + // Para imágenes y otros tipos + options = { + image: fs.readFileSync(pathMedia), + caption: body + }; + } + + message = await wbot.sendMessage(jid, options); + console.log("Media message sent:", message.key.id); + + } else { + // Mensaje de texto simple + console.log("Text message body:", body); + message = await wbot.sendMessage(jid, { text: body }); + } + + return message; + + } catch (err: any) { + console.error("Error in SendMessage:", err); + // Mejor manejo del error + if (err instanceof Error) { + throw new Error(`Failed to send message: ${err.message}`); + } else { + throw new Error("Failed to send message: Unknown error"); + } + } +}; \ No newline at end of file diff --git a/backend/src/helpers/SetTicketMessagesAsRead.ts b/backend/src/helpers/SetTicketMessagesAsRead.ts index d770bc1..a97a17e 100644 --- a/backend/src/helpers/SetTicketMessagesAsRead.ts +++ b/backend/src/helpers/SetTicketMessagesAsRead.ts @@ -1,4 +1,4 @@ -import {} from "@adiwajshing/baileys"; +import {} from "@whiskeysockets/baileys"; import { getIO } from "../libs/socket"; import Message from "../models/Message"; import Ticket from "../models/Ticket"; @@ -32,10 +32,13 @@ const SetTicketMessagesAsRead = async (ticket: Ticket): Promise => { } const io = getIO(); - io.to(ticket.status).to("notification").emit("ticket", { - action: "updateUnread", - ticketId: ticket.id - }); + io.to(ticket.status) + .to("notification") + .to(`queue-${ticket.queueId}-notification`) + .emit("ticket", { + action: "updateUnread", + ticketId: ticket.id + }); }; export default SetTicketMessagesAsRead; diff --git a/backend/src/helpers/authState.ts b/backend/src/helpers/authState.ts index e66821f..5da5fb7 100644 --- a/backend/src/helpers/authState.ts +++ b/backend/src/helpers/authState.ts @@ -2,21 +2,22 @@ import type { AuthenticationCreds, AuthenticationState, SignalDataTypeMap -} from "@adiwajshing/baileys"; -import { BufferJSON, initAuthCreds, proto } from "@adiwajshing/baileys"; +} from "@whiskeysockets/baileys"; +import { BufferJSON, initAuthCreds, proto } from "@whiskeysockets/baileys"; import * as Sentry from "@sentry/node"; import Whatsapp from "../models/Whatsapp"; const KEY_MAP: { [T in keyof SignalDataTypeMap]: string } = { - "pre-key": "preKeys", - session: "sessions", - "sender-key": "senderKeys", - "app-state-sync-key": "appStateSyncKeys", - "app-state-sync-version": "appStateVersions", - "sender-key-memory": "senderKeyMemory" -}; + 'pre-key': 'preKeys', + session: 'sessions', + 'sender-key': 'senderKeys', + 'sender-key-memory': 'senderKeyMemory', + 'app-state-sync-key': 'appStateSyncKeys', + 'app-state-sync-version': 'appStateVersions', + }; + const authState = async ( whatsapp: Whatsapp ): Promise<{ state: AuthenticationState; saveState: () => void }> => { diff --git a/backend/src/helpers/useMultiFileAuthState.ts b/backend/src/helpers/useMultiFileAuthState.ts index 45eeb84..039a230 100644 --- a/backend/src/helpers/useMultiFileAuthState.ts +++ b/backend/src/helpers/useMultiFileAuthState.ts @@ -5,7 +5,7 @@ import { SignalDataTypeMap, initAuthCreds, BufferJSON -} from "@adiwajshing/baileys"; +} from "@whiskeysockets/baileys"; import Whatsapp from "../models/Whatsapp"; import BaileysSessions from "../models/BaileysSessions"; diff --git a/backend/src/libs/makeInMemoryStore.js b/backend/src/libs/makeInMemoryStore.js new file mode 100644 index 0000000..7298cb8 --- /dev/null +++ b/backend/src/libs/makeInMemoryStore.js @@ -0,0 +1,157 @@ +// Implementación compatible con baileys para Whaticket +module.exports = function makeInMemoryStore(options = {}) { + console.log('🔧 makeInMemoryStore() llamado, options:', Object.keys(options)); + const logger = options.logger || { + debug: () => {}, + trace: () => {}, + info: () => {} + }; + + // Almacenamiento en memoria + const messages = new Map(); // { "jid:id": message } + const contacts = new Map(); + const chats = new Map(); + + const store = { + // Map de chats (accesible como wbot.store.chats) + chats, + // Map de mensajes (accesible como wbot.store.messages) + messages, + // Map de contactos (accesible como wbot.store.contacts) + contacts, + + // Método para vincular con eventos de baileys + bind(ev) { + console.log('🔧 store.bind() llamado'); + if (ev && typeof ev.on === 'function') { + // Escuchar nuevos mensajes + ev.on('messages.upsert', ({ messages: newMessages }) => { + if (newMessages && Array.isArray(newMessages)) { + newMessages.forEach(msg => { + if (msg.key && msg.key.remoteJid && msg.key.id) { + const key = `${msg.key.remoteJid}:${msg.key.id}`; + messages.set(key, msg); + } + }); + } + }); + + // Escuchar actualizaciones de contactos + ev.on('contacts.update', (contactsUpdate) => { + if (contactsUpdate && Array.isArray(contactsUpdate)) { + contactsUpdate.forEach(contact => { + if (contact.id) { + contacts.set(contact.id, contact); + } + }); + } + }); + + // Escuchar cuando se establecen chats (inicialización) + ev.on('chats.set', ({ chats: newChats }) => { + if (newChats && Array.isArray(newChats)) { + newChats.forEach(chat => { + if (chat.id) { + chats.set(chat.id, chat); + } + }); + } + }); + + // Escuchar actualizaciones de chats + ev.on('chats.update', (chatsUpdate) => { + if (chatsUpdate && Array.isArray(chatsUpdate)) { + chatsUpdate.forEach(chatUpdate => { + if (chatUpdate.id) { + // Obtener chat existente o crear nuevo + const existingChat = chats.get(chatUpdate.id) || {}; + // Fusionar actualizaciones manteniendo propiedades existentes + const updatedChat = { + unreadCount: 0, // Valor por defecto + ...existingChat, + ...chatUpdate + }; + chats.set(chatUpdate.id, updatedChat); + console.log('🔧 Chat actualizado:', chatUpdate.id, 'unreadCount:', updatedChat.unreadCount); + } + }); + } + }); + + // Log para debug + ev.on('connection.update', (update) => { + logger.debug('connection.update:', update.connection); + }); + } + logger.debug('store bound to ev'); + return store; + }, + + // Método para cargar un mensaje específico (requerido por wbot.ts) + async loadMessage(remoteJid, id) { + const key = `${remoteJid}:${id}`; + const msg = messages.get(key); + + if (msg) { + return msg.message || msg; + } + return undefined; + }, + + // Método para escribir/guardar un mensaje + write(message) { + if (message && message.key && message.key.remoteJid && message.key.id) { + const key = `${message.key.remoteJid}:${message.key.id}`; + messages.set(key, message); + } + }, + + // Método para leer todos los mensajes de un JID + readMessages(remoteJid) { + const result = []; + for (const [key, message] of messages.entries()) { + if (key.startsWith(remoteJid + ':')) { + result.push(message); + } + } + return result; + }, + + // Método para obtener un mensaje por key (alternativa) + getMessage(key) { + if (key && key.remoteJid && key.id) { + const mapKey = `${key.remoteJid}:${key.id}`; + const msg = messages.get(mapKey); + return msg ? msg.message || msg : undefined; + } + return undefined; + }, + + // Método para limpiar el store + clear() { + messages.clear(); + contacts.clear(); + chats.clear(); + }, + + // Para compatibilidad + toJSON() { + return { + messages: Array.from(messages.entries()).reduce((obj, [key, val]) => { + obj[key] = val; + return obj; + }, {}), + contacts: Array.from(contacts.entries()).reduce((obj, [key, val]) => { + obj[key] = val; + return obj; + }, {}), + chats: Array.from(chats.entries()).reduce((obj, [key, val]) => { + obj[key] = val; + return obj; + }, {}) + }; + } + }; + + return store; +}; diff --git a/backend/src/libs/makeInMemoryStore.js.backup b/backend/src/libs/makeInMemoryStore.js.backup new file mode 100644 index 0000000..0b14b8a --- /dev/null +++ b/backend/src/libs/makeInMemoryStore.js.backup @@ -0,0 +1,116 @@ +// Implementación compatible con baileys para Whaticket +module.exports = function makeInMemoryStore(options = {}) { + const logger = options.logger || { + debug: () => {}, + trace: () => {}, + info: () => {} + }; + + // Almacenamiento en memoria + const messages = new Map(); // { "jid:id": message } + const contacts = new Map(); + const chats = new Map(); + + const store = { + // Método para vincular con eventos de baileys + bind(ev) { + if (ev && typeof ev.on === 'function') { + // Escuchar nuevos mensajes + ev.on('messages.upsert', ({ messages: newMessages }) => { + if (newMessages && Array.isArray(newMessages)) { + newMessages.forEach(msg => { + if (msg.key && msg.key.remoteJid && msg.key.id) { + const key = `${msg.key.remoteJid}:${msg.key.id}`; + messages.set(key, msg); + } + }); + } + }); + + // Escuchar actualizaciones de contactos + ev.on('contacts.update', (contactsUpdate) => { + if (contactsUpdate && Array.isArray(contactsUpdate)) { + contactsUpdate.forEach(contact => { + if (contact.id) { + contacts.set(contact.id, contact); + } + }); + } + }); + + // Escuchar actualizaciones de chats + ev.on('chats.update', (chatsUpdate) => { + if (chatsUpdate && Array.isArray(chatsUpdate)) { + chatsUpdate.forEach(chat => { + if (chat.id) { + chats.set(chat.id, chat); + } + }); + } + }); + } + logger.debug('store bound to ev'); + return store; + }, + + // Método para cargar un mensaje específico (requerido por wbot.ts) + async loadMessage(remoteJid, id) { + const key = `${remoteJid}:${id}`; + const msg = messages.get(key); + + if (msg) { + // Retornar el mensaje o su propiedad .message + return msg.message || msg; + } + + return undefined; + }, + + // Método para escribir/guardar un mensaje + write(message) { + if (message && message.key && message.key.remoteJid && message.key.id) { + const key = `${message.key.remoteJid}:${message.key.id}`; + messages.set(key, message); + } + }, + + // Método para leer todos los mensajes de un JID + readMessages(remoteJid) { + const result = []; + for (const [key, message] of messages.entries()) { + if (key.startsWith(remoteJid + ':')) { + result.push(message); + } + } + return result; + }, + + // Método para obtener un mensaje por key (alternativa) + getMessage(key) { + if (key && key.remoteJid && key.id) { + const mapKey = `${key.remoteJid}:${key.id}`; + const msg = messages.get(mapKey); + return msg ? msg.message || msg : undefined; + } + return undefined; + }, + + // Método para limpiar el store + clear() { + messages.clear(); + contacts.clear(); + chats.clear(); + }, + + // Para compatibilidad + toJSON() { + return { + messages: Object.fromEntries(messages), + contacts: Object.fromEntries(contacts), + chats: Object.fromEntries(chats) + }; + } + }; + + return store; +}; diff --git a/backend/src/libs/makeInMemoryStore.ts b/backend/src/libs/makeInMemoryStore.ts new file mode 100644 index 0000000..5424e7a --- /dev/null +++ b/backend/src/libs/makeInMemoryStore.ts @@ -0,0 +1,82 @@ +// Implementación TypeScript para makeInMemoryStore +export default function makeInMemoryStore(options?: any) { + console.log('🔧 makeInMemoryStore() llamado'); + const logger = options?.logger || { + debug: () => {}, + trace: () => {}, + info: () => {} + }; + + const messages = new Map(); + const contacts = new Map(); + const chats = new Map(); + + const store = { + bind(ev: any) { + console.log('🔧 store.bind() llamado, eventos disponibles:', Object.keys(ev)); + console.log('🔧 store.bind() llamado'); + if (ev && typeof ev.on === 'function') { + ev.on('messages.upsert', ({ messages: newMessages }: any) => { + if (newMessages && Array.isArray(newMessages)) { + newMessages.forEach((msg: any) => { + if (msg.key && msg.key.remoteJid && msg.key.id) { + const key = `${msg.key.remoteJid}:${msg.key.id}`; + messages.set(key, msg); + } + }); + } + }); + } + logger.debug('store bound'); + return store; + }, + + async loadMessage(remoteJid: string, id: string) { + const key = `${remoteJid}:${id}`; + const msg = messages.get(key); + return msg ? msg.message || msg : undefined; + }, + + write(message: any) { + if (message && message.key && message.key.remoteJid && message.key.id) { + const key = `${message.key.remoteJid}:${message.key.id}`; + messages.set(key, message); + } + }, + + readMessages(remoteJid: string) { + const result: any[] = []; + for (const [key, message] of messages.entries()) { + if (key.startsWith(remoteJid + ':')) { + result.push(message); + } + } + return result; + }, + + getMessage(key: any) { + if (key && key.remoteJid && key.id) { + const mapKey = `${key.remoteJid}:${key.id}`; + const msg = messages.get(mapKey); + return msg ? msg.message || msg : undefined; + } + return undefined; + }, + + clear() { + messages.clear(); + contacts.clear(); + chats.clear(); + }, + + toJSON() { + return { + messages: Array.from(messages.entries()).reduce((obj, [key, val]) => { obj[key] = val; return obj; }, {}), + contacts: Array.from(contacts.entries()).reduce((obj, [key, val]) => { obj[key] = val; return obj; }, {}), + chats: Array.from(chats.entries()).reduce((obj, [key, val]) => { obj[key] = val; return obj; }, {}) + }; + } + }; + + return store; +} diff --git a/backend/src/libs/makeInMemoryStore.ts.backup b/backend/src/libs/makeInMemoryStore.ts.backup new file mode 100644 index 0000000..c9fc2a4 --- /dev/null +++ b/backend/src/libs/makeInMemoryStore.ts.backup @@ -0,0 +1,79 @@ +// Implementación TypeScript para makeInMemoryStore +export default function makeInMemoryStore(options?: any) { + const logger = options?.logger || { + debug: () => {}, + trace: () => {}, + info: () => {} + }; + + const messages = new Map(); + const contacts = new Map(); + const chats = new Map(); + + const store = { + bind(ev: any) { + if (ev && typeof ev.on === 'function') { + ev.on('messages.upsert', ({ messages: newMessages }: any) => { + if (newMessages && Array.isArray(newMessages)) { + newMessages.forEach((msg: any) => { + if (msg.key && msg.key.remoteJid && msg.key.id) { + const key = `${msg.key.remoteJid}:${msg.key.id}`; + messages.set(key, msg); + } + }); + } + }); + } + logger.debug('store bound'); + return store; + }, + + async loadMessage(remoteJid: string, id: string) { + const key = `${remoteJid}:${id}`; + const msg = messages.get(key); + return msg ? msg.message || msg : undefined; + }, + + write(message: any) { + if (message && message.key && message.key.remoteJid && message.key.id) { + const key = `${message.key.remoteJid}:${message.key.id}`; + messages.set(key, message); + } + }, + + readMessages(remoteJid: string) { + const result: any[] = []; + for (const [key, message] of messages.entries()) { + if (key.startsWith(remoteJid + ':')) { + result.push(message); + } + } + return result; + }, + + getMessage(key: any) { + if (key && key.remoteJid && key.id) { + const mapKey = `${key.remoteJid}:${key.id}`; + const msg = messages.get(mapKey); + return msg ? msg.message || msg : undefined; + } + return undefined; + }, + + clear() { + messages.clear(); + contacts.clear(); + chats.clear(); + }, + + toJSON() { + return { + messages: Array.from(messages.entries()).reduce((obj, [key, val]) => { obj[key] = val; return obj; }, {}), + contacts: Array.from(contacts.entries()).reduce((obj, [key, val]) => { obj[key] = val; return obj; }, {}), + chats: Array.from(chats.entries()).reduce((obj, [key, val]) => { obj[key] = val; return obj; }, {}) + }; + } + }; + + return store; +} diff --git a/backend/src/libs/socket.ts b/backend/src/libs/socket.ts index 7169e23..2d5df9a 100644 --- a/backend/src/libs/socket.ts +++ b/backend/src/libs/socket.ts @@ -1,38 +1,164 @@ import { Server as SocketIO } from "socket.io"; import { Server } from "http"; +import { verify } from "jsonwebtoken"; import AppError from "../errors/AppError"; import { logger } from "../utils/logger"; +import authConfig from "../config/auth"; +import User from "../models/User"; +import Queue from "../models/Queue"; +import Ticket from "../models/Ticket"; let io: SocketIO; export const initIO = (httpServer: Server): SocketIO => { + logger.info("=== INITIO CALLED - WITH MIDDLEWARE ==="); + io = new SocketIO(httpServer, { cors: { - origin: process.env.FRONTEND_URL + origin: [process.env.FRONTEND_URL, "https://api.sistemasarrecifes.com.ar"], + credentials: true + }, + allowEIO3: true, + transports: ["polling", "websocket"], + pingTimeout: 60000, + pingInterval: 25000 + }); + + // ========== MIDDLEWARE (se ejecuta en CADA conexión) ========== + io.use(async (socket, next) => { + console.log("🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED!"); + logger.info("=== MIDDLEWARE EXECUTED ==="); + logger.info("Socket ID:", socket.id); + logger.info("Handshake query:", socket.handshake.query); + + // Obtener token + const tokenParam = socket.handshake.query?.token; + logger.info("Token param:", tokenParam); + + if (!tokenParam) { + logger.error("No token in middleware"); + return next(new Error("Authentication error")); + } + + // Convertir a string + let tokenString: string = ""; + if (Array.isArray(tokenParam)) { + tokenString = tokenParam[0]; + } else if (typeof tokenParam === 'string') { + tokenString = tokenParam; + } + + // Quitar comillas si viene como JSON string + if (tokenString.startsWith('"') && tokenString.endsWith('"')) { + try { + tokenString = JSON.parse(tokenString); + } catch (e) { + // Ignorar + } + } + + try { + const tokenData = verify(tokenString, authConfig.secret); + + // Extraer user ID + let userId: string = ""; + if (typeof tokenData === 'string') { + try { + const parsed = JSON.parse(tokenData); + userId = parsed.id; + } catch { + return next(new Error("Invalid token format")); + } + } else { + userId = (tokenData as any).id; + } + + if (!userId) { + return next(new Error("No user ID in token")); + } + + // Adjuntar user ID al socket para uso posterior + (socket as any).userId = userId; + logger.info("Middleware SUCCESS - User ID:", userId); + + return next(); + + } catch (error) { + logger.error("Middleware authentication FAILED:", error); + return next(new Error("Authentication error")); } }); - io.on("connection", socket => { - logger.info("Client Connected"); + // ========== EVENT LISTENER (debería ejecutarse después del middleware) ========== + io.on("connection", async socket => { + console.log("🟢🟢🟢 CONNECTION EVENT FINALLY FIRED!"); + logger.info("=== CONNECTION EVENT ==="); + logger.info("Socket ID:", socket.id); + logger.info("Attached user ID:", (socket as any).userId); + + const userId = (socket as any).userId; + if (!userId) { + logger.error("No user ID attached to socket"); + socket.disconnect(); + return; + } + + const user = await User.findByPk(userId, { include: [Queue] }); + if (!user) { + logger.error("User not found:", userId); + socket.disconnect(); + return; + } + + logger.info("Client Connected - User:", user.name); + + // ... (mantén el resto del código original: joinChatBox, joinNotification, etc.) socket.on("joinChatBox", (ticketId: string) => { - logger.info("A client joined a ticket channel"); - socket.join(ticketId); + if (ticketId === "undefined") return; + Ticket.findByPk(ticketId).then( + ticket => { + if (ticket && (ticket?.userId === user.id || user.profile === "admin")) { + logger.debug(`User ${user.id} joined ticket ${ticketId} channel`); + socket.join(ticketId); + } else { + logger.info(`Invalid attempt to join ticket ${ticketId} by user ${user.id}`); + } + }, + error => logger.error(error, `Error fetching ticket ${ticketId}`) + ); }); socket.on("joinNotification", () => { - logger.info("A client joined notification channel"); - socket.join("notification"); + if (user.profile === "admin") { + logger.debug(`Admin ${user.id} joined notification channel.`); + socket.join("notification"); + } else { + user.queues.forEach(queue => { + logger.debug(`User ${user.id} joined queue ${queue.id} channel.`); + socket.join(`queue-${queue.id}-notification`); + }); + } }); socket.on("joinTickets", (status: string) => { - logger.info(`A client joined to ${status} tickets channel.`); - socket.join(status); + if (user.profile === "admin") { + logger.debug(`Admin ${user.id} joined ${status} tickets channel.`); + socket.join(`${status}`); + } else { + user.queues.forEach(queue => { + logger.debug(`User ${user.id} joined queue ${queue.id} ${status} tickets channel.`); + socket.join(`queue-${queue.id}-${status}`); + }); + } }); socket.on("disconnect", () => { - logger.info("Client disconnected"); + logger.info("Client disconnected:", socket.id); }); + + socket.emit("ready"); }); + return io; }; diff --git a/backend/src/libs/socket.ts.backup b/backend/src/libs/socket.ts.backup new file mode 100644 index 0000000..4596c9f --- /dev/null +++ b/backend/src/libs/socket.ts.backup @@ -0,0 +1,176 @@ +import { Server as SocketIO } from "socket.io"; +import { Server } from "http"; +import { verify } from "jsonwebtoken"; +import AppError from "../errors/AppError"; +import { logger } from "../utils/logger"; +import authConfig from "../config/auth"; +import User from "../models/User"; +import Queue from "../models/Queue"; +import Ticket from "../models/Ticket"; + +let io: SocketIO; + +export const initIO = (httpServer: Server): SocketIO => { + logger.info("=== INITIO CALLED ==="); + logger.info("httpServer exists?", !!httpServer); + + // Si ya existe una instancia io, limpiarla + if (io) { + logger.warn("IO instance already exists! Closing previous instance..."); + io.close(); + io = null; + } + + io = new SocketIO(httpServer, { + cors: { + origin: [process.env.FRONTEND_URL, "https://api.sistemasarrecifes.com.ar"], + credentials: true + }, + allowEIO3: true, + transports: ["polling", "websocket"], + pingTimeout: 60000, + pingInterval: 25000 + }); + + logger.info("=== BEFORE io.on('connection') ==="); + logger.info("IO instance created successfully?", !!io); + + io.on("connection", async socket => { + logger.info("=== INSIDE CONNECTION HANDLER ==="); + logger.info("=== SOCKET.IO CONNECTION ATTEMPT ==="); + logger.info("Socket ID:", socket.id); + logger.info("Handshake query:", socket.handshake.query); + logger.info("Handshake auth:", socket.handshake.auth); + logger.info("Handshake headers:", socket.handshake.headers); + + // Debug detallado del query + logger.info("All query params:", JSON.stringify(socket.handshake.query)); + + // Obtener token de múltiples fuentes posibles + let token = socket.handshake.query?.token; + logger.info("Token from 'token' param:", token); + logger.info("Type of token:", typeof token); + + // Si no está en 'token', buscar en 'auth_token' (por si acaso) + if (!token) { + token = socket.handshake.query?.auth_token; + logger.info("Token from 'auth_token' param:", token); + } + + // Si viene como string JSON entre comillas, parsearlo + if (token && typeof token === 'string') { + logger.info("Token length:", token.length); + logger.info("First 50 chars of token:", token.substring(0, 50)); + + // El frontend usa JSON.parse(token) - puede estar enviando token como string JSON + if (token.startsWith('"') && token.endsWith('"')) { + try { + const parsedToken = JSON.parse(token); + logger.info("Token after JSON.parse:", parsedToken); + token = parsedToken; + } catch (e) { + logger.warn("Failed to JSON.parse token:", e.message); + } + } + } + + logger.info("Final token to verify:", token ? "PRESENT (length: " + token.length + ")" : "MISSING"); + + if (!token) { + logger.error("No token provided in handshake"); + socket.disconnect(); + return io; + } + + let tokenData = null; + try { + tokenData = verify(token, authConfig.secret); + logger.debug(tokenData, "io-onConnection: tokenData"); + logger.info("Token verification SUCCESS for user ID:", tokenData.id); + } catch (error) { + logger.error(error, "Error decoding token"); + logger.error("Token that failed:", token.substring(0, 100) + "..."); + socket.disconnect(); + return io; + } + + const userId = tokenData.id; + + let user: User = null; + if (userId && userId !== "undefined" && userId !== "null") { + user = await User.findByPk(userId, { include: [Queue] }); + } + + logger.info("Client Connected - User ID:", userId); + socket.on("joinChatBox", (ticketId: string) => { + if (ticketId === "undefined") { + return; + } + Ticket.findByPk(ticketId).then( + ticket => { + // only admin and the current user of the ticket + // can join the message channel of it. + if ( + ticket && + (ticket?.userId === user.id || user.profile === "admin") + ) { + logger.debug(`User ${user.id} joined ticket ${ticketId} channel`); + socket.join(ticketId); + } else { + logger.info( + `Invalid attempt to join chanel of ticket ${ticketId} by user ${user.id}` + ); + } + }, + error => { + logger.error(error, `Error fetching ticket ${ticketId}`); + } + ); + }); + + socket.on("joinNotification", () => { + if (user.profile === "admin") { + // admin can join all notifications + logger.debug(`Admin ${user.id} joined the notification channel.`); + socket.join("notification"); + } else { + // normal users join notifications of the queues they participate + user.queues.forEach(queue => { + logger.debug(`User ${user.id} joined queue ${queue.id} channel.`); + socket.join(`queue-${queue.id}-notification`); + }); + } + }); + + socket.on("joinTickets", (status: string) => { + if (user.profile === "admin") { + // only admin can join the notifications of a particular status + logger.debug(`Admin ${user.id} joined ${status} tickets channel.`); + socket.join(`${status}`); + } else { + // normal users can only receive messages of the queues they participate + user.queues.forEach(queue => { + logger.debug( + `User ${user.id} joined queue ${queue.id} ${status} tickets channel.` + ); + socket.join(`queue-${queue.id}-${status}`); + }); + } + }); + + socket.on("disconnect", () => { + logger.info("Client disconnected"); + }); + + socket.emit("ready"); + }); + + return io; +}; + +export const getIO = (): SocketIO => { + if (!io) { + throw new AppError("Socket IO not initialized"); + } + return io; +}; diff --git a/backend/src/libs/store.ts b/backend/src/libs/store.ts index 503c923..0d497cd 100644 --- a/backend/src/libs/store.ts +++ b/backend/src/libs/store.ts @@ -8,7 +8,7 @@ import { proto, WAMessageCursor, WASocket -} from "@adiwajshing/baileys"; +} from "@whiskeysockets/baileys"; import KeyedDB from "@adiwajshing/keyed-db"; export interface Store { diff --git a/backend/src/libs/wbot.ts b/backend/src/libs/wbot.ts index eea7186..28a686b 100644 --- a/backend/src/libs/wbot.ts +++ b/backend/src/libs/wbot.ts @@ -2,13 +2,12 @@ import makeWASocket, { AuthenticationState, DisconnectReason, fetchLatestBaileysVersion, - makeInMemoryStore, - MessageRetryMap, WASocket -} from "@adiwajshing/baileys"; +} from "@whiskeysockets/baileys"; import { Boom } from "@hapi/boom"; -import MAIN_LOGGER from "@adiwajshing/baileys/lib/Utils/logger"; +import MAIN_LOGGER from "@whiskeysockets/baileys/lib/Utils/logger"; +import NodeCache from "node-cache"; import Whatsapp from "../models/Whatsapp"; import { logger } from "../utils/logger"; import AppError from "../errors/AppError"; @@ -19,7 +18,7 @@ import DeleteBaileysService from "../services/BaileysServices/DeleteBaileysServi import { useMultiFileAuthState } from "../helpers/useMultiFileAuthState"; import BaileysSessions from "../models/BaileysSessions"; -const msgRetryCounterMap: MessageRetryMap = {}; +const msgRetryCounterCache = new NodeCache(); const loggerBaileys = MAIN_LOGGER.child({}); loggerBaileys.level = "silent"; @@ -50,8 +49,8 @@ export const removeWbot = async ( const sessionIndex = sessions.findIndex(s => s.id === whatsappId); if (sessionIndex !== -1) { if (isLogout) { - sessions[sessionIndex].logout(); - sessions[sessionIndex].ws.close(); + sessions[sessionIndex].logout?.(); + sessions[sessionIndex].ws?.close(); } sessions.splice(sessionIndex, 1); @@ -62,186 +61,206 @@ export const removeWbot = async ( }; export const initWbot = async (whatsapp: Whatsapp): Promise => { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { try { - (async () => { - const io = getIO(); - - const whatsappUpdate = await Whatsapp.findOne({ - where: { id: whatsapp.id } - }); - - if (!whatsappUpdate) return; - - const { id, name, isMultidevice } = whatsappUpdate; - const { isLatest, version } = await fetchLatestBaileysVersion(); - - logger.info(`using WA v${version.join(".")}, isLatest: ${isLatest}`); - logger.info(`isMultidevice: ${isMultidevice}`); - logger.info(`Starting session ${name}`); - let retriesQrCode = 0; - - let wsocket: Session = null; - const store = makeInMemoryStore({ - logger: loggerBaileys - }); - - const { state, saveCreds } = await useMultiFileAuthState(whatsapp); - - wsocket = makeWASocket({ - logger: loggerBaileys, - printQRInTerminal: false, - auth: state as AuthenticationState, - version, - msgRetryCounterMap, - getMessage: async key => { - if (store) { - const msg = await store.loadMessage(key.remoteJid!, key.id!); - return msg?.message || undefined; - } + const io = getIO(); + + const whatsappUpdate = await Whatsapp.findOne({ + where: { id: whatsapp.id } + }); + + if (!whatsappUpdate) { + reject(new Error("WhatsApp not found")); + return; + } + + const { id, name, isMultidevice } = whatsappUpdate; + const { isLatest, version } = await fetchLatestBaileysVersion(); + + logger.info(`using WA v${version.join(".")}, isLatest: ${isLatest}`); + logger.info(`isMultidevice: ${isMultidevice}`); + logger.info(`Starting session ${name}`); + + let retriesQrCode = 0; + let wsocket: Session | null = null; + + // Importación dinámica para evitar problemas de carga circular + // const makeInMemoryStore = require("./libs/makeInMemoryStore"); + const makeInMemoryStore = require("./makeInMemoryStore").default; + const store = makeInMemoryStore({ + logger: loggerBaileys + }); + + // CORRECCIÓN CLAVE: Definir state y saveCreds en el scope correcto + const { state: authState, saveCreds } = await useMultiFileAuthState(whatsapp); + + if (!authState) { + throw new Error("Failed to initialize authentication state"); + } + + wsocket = makeWASocket({ + logger: loggerBaileys, + printQRInTerminal: false, + auth: authState as AuthenticationState, + version, + msgRetryCounterCache, + getMessage: async key => { + if (store) { + const msg = await store.loadMessage(key.remoteJid!, key.id!); + return msg?.message || undefined; } - }); - - wsocket.ev.on( - "connection.update", - async ({ connection, lastDisconnect, qr }) => { - logger.info( - `Socket ${name} Connection Update ${connection || ""} ${ - lastDisconnect || "" - }` - ); + return undefined; + } + }); - const disconect = (lastDisconnect?.error as Boom)?.output - ?.statusCode; - - if (connection === "close") { - if (disconect === 403) { - await whatsapp.update({ - status: "PENDING", - session: "", - number: "" - }); - await DeleteBaileysService(whatsapp.id); - - await BaileysSessions.destroy({ - where: { - whatsappId: whatsapp.id - } - }); - - io.emit("whatsappSession", { - action: "update", - session: whatsapp - }); - removeWbot(id, false); - } + if (!wsocket) { + throw new Error("Failed to create WhatsApp socket"); + } - if (disconect !== DisconnectReason.loggedOut) { - removeWbot(id, false); - setTimeout(() => StartWhatsAppSession(whatsapp), 2000); - } + wsocket.ev.on( + "connection.update", + async ({ connection, lastDisconnect, qr }) => { + logger.info( + `Socket ${name} Connection Update ${connection || ""} ${ + lastDisconnect || "" + }` + ); - if (disconect === DisconnectReason.loggedOut) { - await whatsapp.update({ - status: "PENDING", - session: "", - number: "" - }); - await DeleteBaileysService(whatsapp.id); - - await BaileysSessions.destroy({ - where: { - whatsappId: whatsapp.id - } - }); - - io.emit("whatsappSession", { - action: "update", - session: whatsapp - }); - removeWbot(id, false); - setTimeout(() => StartWhatsAppSession(whatsapp), 2000); - } + const disconnectStatus = (lastDisconnect?.error as Boom)?.output + ?.statusCode; + + if (connection === "close") { + if (disconnectStatus === 403) { + await whatsapp.update({ + status: "PENDING", + session: "", + number: "" + }); + await DeleteBaileysService(whatsapp.id); + + await BaileysSessions.destroy({ + where: { + whatsappId: whatsapp.id + } + }); + + io.emit("whatsappSession", { + action: "update", + session: whatsapp + }); + await removeWbot(id, false); + } + + if (disconnectStatus !== DisconnectReason.loggedOut) { + await removeWbot(id, false); + setTimeout(() => StartWhatsAppSession(whatsapp), 2000); } - if (connection === "open") { + if (disconnectStatus === DisconnectReason.loggedOut) { await whatsapp.update({ - status: "CONNECTED", - qrcode: "", - retries: 0 + status: "PENDING", + session: "", + number: "" + }); + await DeleteBaileysService(whatsapp.id); + + await BaileysSessions.destroy({ + where: { + whatsappId: whatsapp.id + } }); io.emit("whatsappSession", { action: "update", session: whatsapp }); + await removeWbot(id, false); + setTimeout(() => StartWhatsAppSession(whatsapp), 2000); + } + } - const sessionIndex = sessions.findIndex( - s => s.id === whatsapp.id - ); - if (sessionIndex === -1) { - wsocket.id = whatsapp.id; - sessions.push(wsocket); - } + if (connection === "open") { + await whatsapp.update({ + status: "CONNECTED", + qrcode: "", + retries: 0 + }); + + io.emit("whatsappSession", { + action: "update", + session: whatsapp + }); - resolve(wsocket); + const sessionIndex = sessions.findIndex( + s => s.id === whatsapp.id + ); + if (sessionIndex === -1) { + wsocket!.id = whatsapp.id; + sessions.push(wsocket!); } - if (qr !== undefined) { - if (retriesQrCodeMap.get(id) && retriesQrCodeMap.get(id) >= 3) { - await whatsappUpdate.update({ - status: "DISCONNECTED", - qrcode: "" - }); - await DeleteBaileysService(whatsappUpdate.id); - await BaileysSessions.destroy({ - where: { - whatsappId: whatsapp.id - } - }); - io.emit("whatsappSession", { - action: "update", - session: whatsappUpdate - }); - wsocket.ev.removeAllListeners("connection.update"); - wsocket.ws.close(); - wsocket = null; - // retriesQrCode = 0; - retriesQrCodeMap.delete(id); - } else { - logger.info(`Session QRCode Generate ${name}`); - retriesQrCodeMap.set(id, (retriesQrCode += 1)); - - await whatsapp.update({ - qrcode: qr, - status: "qrcode", - retries: 0 - }); - const sessionIndex = sessions.findIndex( - s => s.id === whatsapp.id - ); - - if (sessionIndex === -1) { - wsocket.id = whatsapp.id; - sessions.push(wsocket); + resolve(wsocket!); + } + + if (qr !== undefined) { + const currentRetries = retriesQrCodeMap.get(id) || 0; + + if (currentRetries >= 3) { + await whatsappUpdate.update({ + status: "DISCONNECTED", + qrcode: "" + }); + await DeleteBaileysService(whatsappUpdate.id); + await BaileysSessions.destroy({ + where: { + whatsappId: whatsapp.id } + }); + + io.emit("whatsappSession", { + action: "update", + session: whatsappUpdate + }); + + wsocket!.ev.removeAllListeners("connection.update"); + wsocket!.ws?.close(); + wsocket = null; + retriesQrCodeMap.delete(id); + } else { + logger.info(`Session QRCode Generate ${name}`); + retriesQrCodeMap.set(id, currentRetries + 1); + + await whatsapp.update({ + qrcode: qr, + status: "qrcode", + retries: currentRetries + 1 + }); + + const sessionIndex = sessions.findIndex( + s => s.id === whatsapp.id + ); - io.emit("whatsappSession", { - action: "update", - session: whatsapp - }); + if (sessionIndex === -1) { + wsocket!.id = whatsapp.id; + sessions.push(wsocket!); } + + io.emit("whatsappSession", { + action: "update", + session: whatsapp + }); } } - ); + } + ); + + wsocket.ev.on("creds.update", saveCreds); - wsocket.ev.on("creds.update", saveCreds); + wsocket.store = store; + store.bind(wsocket.ev); - wsocket.store = store; - store.bind(wsocket.ev); - })(); } catch (error) { - console.log(error); + console.error("Error in initWbot:", error); reject(error); } }); diff --git a/backend/src/libs/wbot.ts.backup b/backend/src/libs/wbot.ts.backup new file mode 100644 index 0000000..aa9b4a7 --- /dev/null +++ b/backend/src/libs/wbot.ts.backup @@ -0,0 +1,267 @@ +import makeWASocket, { + AuthenticationState, + DisconnectReason, + fetchLatestBaileysVersion, + WASocket +} from "@whiskeysockets/baileys"; + +import { Boom } from "@hapi/boom"; +import MAIN_LOGGER from "@whiskeysockets/baileys/lib/Utils/logger"; +import NodeCache from "node-cache"; +import Whatsapp from "../models/Whatsapp"; +import { logger } from "../utils/logger"; +import AppError from "../errors/AppError"; +import { getIO } from "./socket"; +import { Store } from "./store"; +import { StartWhatsAppSession } from "../services/WbotServices/StartWhatsAppSession"; +import DeleteBaileysService from "../services/BaileysServices/DeleteBaileysService"; +import { useMultiFileAuthState } from "../helpers/useMultiFileAuthState"; +import BaileysSessions from "../models/BaileysSessions"; + +const msgRetryCounterCache = new NodeCache(); + +const loggerBaileys = MAIN_LOGGER.child({}); +loggerBaileys.level = "silent"; + +type Session = WASocket & { + id?: number; + store?: Store; +}; + +const sessions: Session[] = []; + +const retriesQrCodeMap = new Map(); + +export const getWbot = (whatsappId: number): Session => { + const sessionIndex = sessions.findIndex(s => s.id === whatsappId); + + if (sessionIndex === -1) { + throw new AppError("ERR_WAPP_NOT_INITIALIZED"); + } + return sessions[sessionIndex]; +}; + +export const removeWbot = async ( + whatsappId: number, + isLogout = true +): Promise => { + try { + const sessionIndex = sessions.findIndex(s => s.id === whatsappId); + if (sessionIndex !== -1) { + if (isLogout) { + sessions[sessionIndex].logout?.(); + sessions[sessionIndex].ws?.close(); + } + + sessions.splice(sessionIndex, 1); + } + } catch (err) { + logger.error(err); + } +}; + +export const initWbot = async (whatsapp: Whatsapp): Promise => { + return new Promise(async (resolve, reject) => { + try { + const io = getIO(); + + const whatsappUpdate = await Whatsapp.findOne({ + where: { id: whatsapp.id } + }); + + if (!whatsappUpdate) { + reject(new Error("WhatsApp not found")); + return; + } + + const { id, name, isMultidevice } = whatsappUpdate; + const { isLatest, version } = await fetchLatestBaileysVersion(); + + logger.info(`using WA v${version.join(".")}, isLatest: ${isLatest}`); + logger.info(`isMultidevice: ${isMultidevice}`); + logger.info(`Starting session ${name}`); + + let retriesQrCode = 0; + let wsocket: Session | null = null; + + // Importación dinámica para evitar problemas de carga circular + // const makeInMemoryStore = require("./libs/makeInMemoryStore"); + import { makeInMemoryStore } from "@whiskeysockets/baileys"; + const store = makeInMemoryStore({ + logger: loggerBaileys + }); + + // CORRECCIÓN CLAVE: Definir state y saveCreds en el scope correcto + const { state: authState, saveCreds } = await useMultiFileAuthState(whatsapp); + + if (!authState) { + throw new Error("Failed to initialize authentication state"); + } + + wsocket = makeWASocket({ + logger: loggerBaileys, + printQRInTerminal: false, + auth: authState as AuthenticationState, + version, + msgRetryCounterCache, + getMessage: async key => { + if (store) { + const msg = await store.loadMessage(key.remoteJid!, key.id!); + return msg?.message || undefined; + } + return undefined; + } + }); + + if (!wsocket) { + throw new Error("Failed to create WhatsApp socket"); + } + + wsocket.ev.on( + "connection.update", + async ({ connection, lastDisconnect, qr }) => { + logger.info( + `Socket ${name} Connection Update ${connection || ""} ${ + lastDisconnect || "" + }` + ); + + const disconnectStatus = (lastDisconnect?.error as Boom)?.output + ?.statusCode; + + if (connection === "close") { + if (disconnectStatus === 403) { + await whatsapp.update({ + status: "PENDING", + session: "", + number: "" + }); + await DeleteBaileysService(whatsapp.id); + + await BaileysSessions.destroy({ + where: { + whatsappId: whatsapp.id + } + }); + + io.emit("whatsappSession", { + action: "update", + session: whatsapp + }); + await removeWbot(id, false); + } + + if (disconnectStatus !== DisconnectReason.loggedOut) { + await removeWbot(id, false); + setTimeout(() => StartWhatsAppSession(whatsapp), 2000); + } + + if (disconnectStatus === DisconnectReason.loggedOut) { + await whatsapp.update({ + status: "PENDING", + session: "", + number: "" + }); + await DeleteBaileysService(whatsapp.id); + + await BaileysSessions.destroy({ + where: { + whatsappId: whatsapp.id + } + }); + + io.emit("whatsappSession", { + action: "update", + session: whatsapp + }); + await removeWbot(id, false); + setTimeout(() => StartWhatsAppSession(whatsapp), 2000); + } + } + + if (connection === "open") { + await whatsapp.update({ + status: "CONNECTED", + qrcode: "", + retries: 0 + }); + + io.emit("whatsappSession", { + action: "update", + session: whatsapp + }); + + const sessionIndex = sessions.findIndex( + s => s.id === whatsapp.id + ); + if (sessionIndex === -1) { + wsocket!.id = whatsapp.id; + sessions.push(wsocket!); + } + + resolve(wsocket!); + } + + if (qr !== undefined) { + const currentRetries = retriesQrCodeMap.get(id) || 0; + + if (currentRetries >= 3) { + await whatsappUpdate.update({ + status: "DISCONNECTED", + qrcode: "" + }); + await DeleteBaileysService(whatsappUpdate.id); + await BaileysSessions.destroy({ + where: { + whatsappId: whatsapp.id + } + }); + + io.emit("whatsappSession", { + action: "update", + session: whatsappUpdate + }); + + wsocket!.ev.removeAllListeners("connection.update"); + wsocket!.ws?.close(); + wsocket = null; + retriesQrCodeMap.delete(id); + } else { + logger.info(`Session QRCode Generate ${name}`); + retriesQrCodeMap.set(id, currentRetries + 1); + + await whatsapp.update({ + qrcode: qr, + status: "qrcode", + retries: currentRetries + 1 + }); + + const sessionIndex = sessions.findIndex( + s => s.id === whatsapp.id + ); + + if (sessionIndex === -1) { + wsocket!.id = whatsapp.id; + sessions.push(wsocket!); + } + + io.emit("whatsappSession", { + action: "update", + session: whatsapp + }); + } + } + } + ); + + wsocket.ev.on("creds.update", saveCreds); + + wsocket.store = store; + store.bind(wsocket.ev); + + } catch (error) { + console.error("Error in initWbot:", error); + reject(error); + } + }); +}; diff --git a/backend/src/models/Chatbot.ts b/backend/src/models/Chatbot.ts index beee5ea..0c7a2a3 100644 --- a/backend/src/models/Chatbot.ts +++ b/backend/src/models/Chatbot.ts @@ -9,49 +9,60 @@ import { AllowNull, ForeignKey, BelongsTo, - HasMany + HasMany, + DataType, + Default } from "sequelize-typescript"; import Queue from "./Queue"; -@Table -class Chatbot extends Model { +@Table({ + tableName: "Chatbots", + timestamps: true +}) +class Chatbot extends Model { @PrimaryKey @AutoIncrement - @Column - id: number; + @Column(DataType.INTEGER) + id!: number; @AllowNull(false) - @Column - name: string; + @Column(DataType.STRING) + name!: string; - @Column - greetingMessage: string; + @Column(DataType.TEXT) + greetingMessage!: string; + + @Column(DataType.JSONB) + options!: any; + + @Default(false) + @Column(DataType.BOOLEAN) + isAgent!: boolean; @ForeignKey(() => Queue) - @Column - queueId: number; + @Column(DataType.INTEGER) + queueId!: number; @BelongsTo(() => Queue) - queue: Queue; + queue!: Queue; @ForeignKey(() => Chatbot) - @Column - chatbotId: number; - - @Column - isAgent: boolean; + @Column(DataType.INTEGER) + chatbotId!: number; @BelongsTo(() => Chatbot) - mainChatbot: Chatbot; + parentChatbot!: Chatbot; @HasMany(() => Chatbot) - options: Chatbot[]; + childrenChatbots!: Chatbot[]; @CreatedAt - createdAt: Date; + @Column(DataType.DATE) + createdAt!: Date; @UpdatedAt - updatedAt: Date; + @Column(DataType.DATE) + updatedAt!: Date; } export default Chatbot; diff --git a/backend/src/models/Chatbot.ts.backup b/backend/src/models/Chatbot.ts.backup new file mode 100644 index 0000000..beee5ea --- /dev/null +++ b/backend/src/models/Chatbot.ts.backup @@ -0,0 +1,57 @@ +import { + Table, + Column, + CreatedAt, + UpdatedAt, + Model, + PrimaryKey, + AutoIncrement, + AllowNull, + ForeignKey, + BelongsTo, + HasMany +} from "sequelize-typescript"; +import Queue from "./Queue"; + +@Table +class Chatbot extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @AllowNull(false) + @Column + name: string; + + @Column + greetingMessage: string; + + @ForeignKey(() => Queue) + @Column + queueId: number; + + @BelongsTo(() => Queue) + queue: Queue; + + @ForeignKey(() => Chatbot) + @Column + chatbotId: number; + + @Column + isAgent: boolean; + + @BelongsTo(() => Chatbot) + mainChatbot: Chatbot; + + @HasMany(() => Chatbot) + options: Chatbot[]; + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; +} + +export default Chatbot; diff --git a/backend/src/models/Contact.ts b/backend/src/models/Contact.ts index ac30958..264541f 100644 --- a/backend/src/models/Contact.ts +++ b/backend/src/models/Contact.ts @@ -1,66 +1,103 @@ import { Table, Column, - CreatedAt, - UpdatedAt, Model, + DataType, PrimaryKey, AutoIncrement, AllowNull, - Unique, Default, - HasMany + ForeignKey, + BelongsTo, + CreatedAt, + UpdatedAt } from "sequelize-typescript"; -import ContactCustomField from "./ContactCustomField"; -import Schedule from "./Schedule"; -import Ticket from "./Ticket"; +//import Company from "./Company"; +import Whatsapp from "./Whatsapp"; -@Table -class Contact extends Model { +@Table({ + tableName: "Contacts", + timestamps: true +}) +class Contact extends Model { @PrimaryKey @AutoIncrement - @Column - id: number; - - @Column - name: string; + @Column(DataType.INTEGER) + id!: number; @AllowNull(false) - @Unique - @Column - number: string; + @Column(DataType.STRING) + name!: string; @AllowNull(false) + @Column(DataType.STRING) + number!: string; // Puede ser número real o placeholder para LIDs (ej: LID_2830819658) + + // Getter para compatibilidad con 'extraInfo' (con I mayúscula) + get extraInfo(): any { + return this.extrainfo; + } + + set extraInfo(value: any) { + this.extrainfo = value; + } + @Default("") - @Column - email: string; + @Column(DataType.STRING) + email!: string; - @Column - profilePicUrl: string; + @Column(DataType.TEXT) + profilePicUrl!: string; @Default(false) - @Column - isGroup: boolean; + @Column(DataType.BOOLEAN) + isGroup!: boolean; - @CreatedAt - createdAt: Date; + @Column(DataType.JSONB) + extrainfo!: any; + +// @ForeignKey(() => Company) +// @Column(DataType.INTEGER) +// companyId!: number; + +// @BelongsTo(() => Company) +// company!: Company; + @Column(DataType.INTEGER) + companyId!: number; - @UpdatedAt - updatedAt: Date; - @HasMany(() => Ticket) - tickets: Ticket[]; + @ForeignKey(() => Whatsapp) + @Column(DataType.INTEGER) + whatsappId!: number; - @HasMany(() => ContactCustomField) - extraInfo: ContactCustomField[]; + @BelongsTo(() => Whatsapp) + whatsapp!: Whatsapp; - @HasMany(() => Schedule, { - onUpdate: "CASCADE", - onDelete: "CASCADE", - hooks: true - }) - schedules: Schedule[]; + @Column(DataType.STRING) + lid!: string; // Guarda el LID original completo (ej: 28308196585718@lid) + @Column(DataType.STRING) + originaljid!: string; + + @Column(DataType.BOOLEAN) + active!: boolean; + + @Column(DataType.BOOLEAN) + disableBot!: boolean; + + @Column(DataType.STRING) + messengerId!: string; + + @Column(DataType.STRING) + instagramId!: string; + + @CreatedAt + @Column(DataType.DATE) + createdAt!: Date; + + @UpdatedAt + @Column(DataType.DATE) + updatedAt!: Date; } export default Contact; diff --git a/backend/src/models/Contact.ts.backup b/backend/src/models/Contact.ts.backup new file mode 100644 index 0000000..915f70e --- /dev/null +++ b/backend/src/models/Contact.ts.backup @@ -0,0 +1,66 @@ +// En models/Contact.ts +import { + Model, + // ... otros imports +} from "sequelize"; + +class Contact extends Model { + public id!: number; + public name!: string; + public number!: string; + public email?: string; + public profilePicUrl?: string; + public isGroup!: boolean; + public extraInfo?: JSON; + public companyId?: number; // NUEVO + public lid?: string; // NUEVO + public originalJid?: string; // NUEVO + public whatsappId?: number; // NUEVO + + // ... resto del modelo +} + +Contact.init( + { + // ... campos existentes + name: DataTypes.STRING, + number: DataTypes.STRING, + email: DataTypes.STRING, + profilePicUrl: DataTypes.TEXT, + isGroup: DataTypes.BOOLEAN, + extraInfo: DataTypes.JSON, + + // NUEVOS CAMPOS + companyId: { + type: DataTypes.INTEGER, + references: { + model: "Companies", + key: "id" + }, + onUpdate: "CASCADE", + onDelete: "SET NULL" + }, + lid: { + type: DataTypes.STRING, + allowNull: true + }, + originalJid: { + type: DataTypes.STRING, + allowNull: true + }, + whatsappId: { + type: DataTypes.INTEGER, + references: { + model: "Whatsapps", + key: "id" + }, + onUpdate: "CASCADE", + onDelete: "SET NULL" + } + }, + { + // ... opciones del modelo + } +); + +export default Contact; \ No newline at end of file diff --git a/backend/src/sendWork.ts b/backend/src/sendWork.ts index b9c074a..8e8653c 100644 --- a/backend/src/sendWork.ts +++ b/backend/src/sendWork.ts @@ -1,4 +1,4 @@ -import { WASocket } from "@adiwajshing/baileys"; +import { WASocket } from "@whiskeysockets/baileys"; import Queue from "bull"; import moment from "moment"; import { getWbot } from "./libs/wbot"; diff --git a/backend/src/services/BaileysServices/CreateOrUpdateBaileysService.ts b/backend/src/services/BaileysServices/CreateOrUpdateBaileysService.ts index eee1063..97cbf55 100644 --- a/backend/src/services/BaileysServices/CreateOrUpdateBaileysService.ts +++ b/backend/src/services/BaileysServices/CreateOrUpdateBaileysService.ts @@ -1,4 +1,4 @@ -import { Chat, Contact } from "@adiwajshing/baileys"; +import { Chat, Contact } from "@whiskeysockets/baileys"; import Baileys from "../../models/Baileys"; interface Request { diff --git a/backend/src/services/ContactServices/CreateContactService.ts b/backend/src/services/ContactServices/CreateContactService.ts index 331d175..76371e3 100644 --- a/backend/src/services/ContactServices/CreateContactService.ts +++ b/backend/src/services/ContactServices/CreateContactService.ts @@ -1,7 +1,7 @@ import AppError from "../../errors/AppError"; import Contact from "../../models/Contact"; -interface ExtraInfo { +interface Extrainfo { name: string; value: string; } @@ -11,14 +11,14 @@ interface Request { number: string; email?: string; profilePicUrl?: string; - extraInfo?: ExtraInfo[]; + extrainfo?: any[]; } const CreateContactService = async ({ name, number, email = "", - extraInfo = [] + extrainfo = [] }: Request): Promise => { const numberExists = await Contact.findOne({ where: { number } @@ -33,10 +33,10 @@ const CreateContactService = async ({ name, number, email, - extraInfo + extrainfo }, { - include: ["extraInfo"] + } ); diff --git a/backend/src/services/ContactServices/CreateOrUpdateContactService.ts b/backend/src/services/ContactServices/CreateOrUpdateContactService.ts index 83ced19..c161584 100644 --- a/backend/src/services/ContactServices/CreateOrUpdateContactService.ts +++ b/backend/src/services/ContactServices/CreateOrUpdateContactService.ts @@ -1,7 +1,7 @@ import { getIO } from "../../libs/socket"; import Contact from "../../models/Contact"; -interface ExtraInfo { +interface Extrainfo { name: string; value: string; } @@ -12,58 +12,106 @@ interface Request { isGroup: boolean; email?: string; profilePicUrl?: string; - extraInfo?: ExtraInfo[]; + extrainfo?: Extrainfo[]; + companyId?: number; + lid?: string | null; + originalJid?: string | null; + whatsappId?: number; } +// REEMPLAZA desde la línea donde se declara 'let contact' hasta el final + const CreateOrUpdateContactService = async ({ name, number: rawNumber, profilePicUrl, isGroup, email = "", - extraInfo = [] + extrainfo = [], + companyId, + lid = null, + originalJid = null, + whatsappId }: Request): Promise => { + // ========== LOGGING DETALLADO ========== + console.log('🔍 CreateOrUpdateContactService - DATOS RECIBIDOS:'); + console.log(' name:', name); + console.log(' rawNumber:', rawNumber); + console.log(' isGroup:', isGroup); + console.log(' companyId:', companyId); + console.log(' lid:', lid); + console.log(' originalJid:', originalJid); + console.log(' whatsappId:', whatsappId); + // ========== FIN LOGGING ========== + + const number = isGroup ? rawNumber : rawNumber.replace(/[^0-9]/g, ""); const io = getIO(); - let contact: Contact | null; + + // 1. Buscar contacto existente + const whereClause: any = { number }; + if (companyId) { + whereClause.companyId = companyId; + } - contact = await Contact.findOne({ where: { number } }); + let contact = await Contact.findOne({ where: whereClause }); + // 2. Si existe, actualizarlo if (contact) { try { - await contact.update({ profilePicUrl }); + const updateData: any = { + name, + profilePicUrl: profilePicUrl || contact.profilePicUrl + }; + + if (email) updateData.email = email; + if (extrainfo && extrainfo.length > 0) updateData.extrainfo = extrainfo; + if (lid !== undefined) updateData.lid = lid; + if (originalJid !== undefined) updateData.originalJid = originalJid; + if (whatsappId !== undefined) updateData.whatsappId = whatsappId; + + await contact.update(updateData); io.emit("contact", { action: "update", contact }); + + return contact; // <-- Retornar aquí si se actualizó + } catch (error) { - console.log(error); + console.error('Error actualizando contacto:', error); + throw error; // <-- Importante: propagar el error } } - if (!contact) { - try { - contact = await Contact.create({ - name, - number, - profilePicUrl, - email, - isGroup, - extraInfo - }); + // 3. Si no existe, crearlo + try { + contact = await Contact.create({ + name, + number, + profilePicUrl, + email, + isGroup, + extrainfo, + companyId, + lid, + originalJid, + whatsappId + }); - io.emit("contact", { - action: "create", - contact - }); - } catch (error) { - console.log(error); - } - } + io.emit("contact", { + action: "create", + contact + }); - return contact; + return contact; // <-- Retornar el contacto creado + + } catch (error) { + console.error('Error creando contacto:', error); + throw error; // <-- Propagar el error + } }; export default CreateOrUpdateContactService; diff --git a/backend/src/services/ContactServices/CreateOrUpdateContactService.ts.backup b/backend/src/services/ContactServices/CreateOrUpdateContactService.ts.backup new file mode 100644 index 0000000..83ced19 --- /dev/null +++ b/backend/src/services/ContactServices/CreateOrUpdateContactService.ts.backup @@ -0,0 +1,69 @@ +import { getIO } from "../../libs/socket"; +import Contact from "../../models/Contact"; + +interface ExtraInfo { + name: string; + value: string; +} + +interface Request { + name: string; + number: string; + isGroup: boolean; + email?: string; + profilePicUrl?: string; + extraInfo?: ExtraInfo[]; +} + +const CreateOrUpdateContactService = async ({ + name, + number: rawNumber, + profilePicUrl, + isGroup, + email = "", + extraInfo = [] +}: Request): Promise => { + const number = isGroup ? rawNumber : rawNumber.replace(/[^0-9]/g, ""); + + const io = getIO(); + let contact: Contact | null; + + contact = await Contact.findOne({ where: { number } }); + + if (contact) { + try { + await contact.update({ profilePicUrl }); + + io.emit("contact", { + action: "update", + contact + }); + } catch (error) { + console.log(error); + } + } + + if (!contact) { + try { + contact = await Contact.create({ + name, + number, + profilePicUrl, + email, + isGroup, + extraInfo + }); + + io.emit("contact", { + action: "create", + contact + }); + } catch (error) { + console.log(error); + } + } + + return contact; +}; + +export default CreateOrUpdateContactService; diff --git a/backend/src/services/ContactServices/GetContactService.ts b/backend/src/services/ContactServices/GetContactService.ts index 3865bc5..d64591e 100644 --- a/backend/src/services/ContactServices/GetContactService.ts +++ b/backend/src/services/ContactServices/GetContactService.ts @@ -2,7 +2,7 @@ import AppError from "../../errors/AppError"; import Contact from "../../models/Contact"; import CreateContactService from "./CreateContactService"; -interface ExtraInfo { +interface Extrainfo { name: string; value: string; } @@ -12,7 +12,7 @@ interface Request { number: string; email?: string; profilePicUrl?: string; - extraInfo?: ExtraInfo[]; + extraInfo?: Extrainfo[]; } const GetContactService = async ({ name, number }: Request): Promise => { diff --git a/backend/src/services/ContactServices/ShowContactService.ts b/backend/src/services/ContactServices/ShowContactService.ts index 4b215c4..64db8ef 100644 --- a/backend/src/services/ContactServices/ShowContactService.ts +++ b/backend/src/services/ContactServices/ShowContactService.ts @@ -2,7 +2,7 @@ import Contact from "../../models/Contact"; import AppError from "../../errors/AppError"; const ShowContactService = async (id: string | number): Promise => { - const contact = await Contact.findByPk(id, { include: ["extraInfo"] }); + const contact = await Contact.findByPk(id, {}); if (!contact) { throw new AppError("ERR_NO_CONTACT_FOUND", 404); diff --git a/backend/src/services/ContactServices/UpdateContactService.ts b/backend/src/services/ContactServices/UpdateContactService.ts index 8211766..4223f13 100644 --- a/backend/src/services/ContactServices/UpdateContactService.ts +++ b/backend/src/services/ContactServices/UpdateContactService.ts @@ -2,7 +2,7 @@ import AppError from "../../errors/AppError"; import Contact from "../../models/Contact"; import ContactCustomField from "../../models/ContactCustomField"; -interface ExtraInfo { +interface Extrainfo { id?: number; name: string; value: string; @@ -11,7 +11,7 @@ interface ContactData { email?: string; number?: string; name?: string; - extraInfo?: ExtraInfo[]; + extrainfo?: Extrainfo[]; } interface Request { @@ -23,28 +23,28 @@ const UpdateContactService = async ({ contactData, contactId }: Request): Promise => { - const { email, name, number, extraInfo } = contactData; + const { email, name, number, extrainfo } = contactData; const contact = await Contact.findOne({ where: { id: contactId }, attributes: ["id", "name", "number", "email", "profilePicUrl"], - include: ["extraInfo"] + }); if (!contact) { throw new AppError("ERR_NO_CONTACT_FOUND", 404); } - if (extraInfo) { + if (extrainfo) { await Promise.all( - extraInfo.map(async info => { + extrainfo.map(async info => { await ContactCustomField.upsert({ ...info, contactId: contact.id }); }) ); await Promise.all( - contact.extraInfo.map(async oldInfo => { - const stillExists = extraInfo.findIndex(info => info.id === oldInfo.id); + contact.extrainfo.map(async oldInfo => { + const stillExists = extrainfo.findIndex(info => info.id === oldInfo.id); if (stillExists === -1) { await ContactCustomField.destroy({ where: { id: oldInfo.id } }); @@ -61,7 +61,7 @@ const UpdateContactService = async ({ await contact.reload({ attributes: ["id", "name", "number", "email", "profilePicUrl"], - include: ["extraInfo"] + }); return contact; diff --git a/backend/src/services/MessageServices/CreateMessageService.ts b/backend/src/services/MessageServices/CreateMessageService.ts index 17f9edf..d2152a1 100644 --- a/backend/src/services/MessageServices/CreateMessageService.ts +++ b/backend/src/services/MessageServices/CreateMessageService.ts @@ -54,6 +54,9 @@ const CreateMessageService = async ({ io.to(message.ticketId.toString()) .to(message.ticket.status) .to("notification") + // send message to specific queues + .to(`queue-${message.ticket.queueId}-${message.ticket.status}`) + .to(`queue-${message.ticket.queueId}-notification`) .emit("appMessage", { action: "create", message, diff --git a/backend/src/services/TicketServices/ShowTicketService.ts b/backend/src/services/TicketServices/ShowTicketService.ts index d82dea8..fa69e8e 100644 --- a/backend/src/services/TicketServices/ShowTicketService.ts +++ b/backend/src/services/TicketServices/ShowTicketService.ts @@ -12,8 +12,7 @@ const ShowTicketService = async (id: string | number): Promise => { { model: Contact, as: "contact", - attributes: ["id", "name", "number", "profilePicUrl"], - include: ["extraInfo"] + attributes: ["id", "name", "number", "profilePicUrl", "extrainfo"] }, { model: User, diff --git a/backend/src/services/TicketServices/UpdateTicketService.ts b/backend/src/services/TicketServices/UpdateTicketService.ts index b689a37..e638be0 100644 --- a/backend/src/services/TicketServices/UpdateTicketService.ts +++ b/backend/src/services/TicketServices/UpdateTicketService.ts @@ -59,7 +59,7 @@ const UpdateTicketService = async ({ const io = getIO(); if (ticket.status !== oldStatus || ticket.user?.id !== oldUserId) { - io.to(oldStatus).emit("ticket", { + io.to(oldStatus).to(`queue-${ticket.queueId}-${oldStatus}`).emit("ticket", { action: "delete", ticketId: ticket.id }); @@ -68,6 +68,9 @@ const UpdateTicketService = async ({ io.to(ticket.status) .to("notification") .to(ticketId.toString()) + // send queue specific messages + .to(`queue-${ticket.queueId}-${ticket.status}`) + .to(`queue-${ticket.queueId}-notification`) .emit("ticket", { action: "update", ticket diff --git a/backend/src/services/WbotServices/ChatBotListener.ts b/backend/src/services/WbotServices/ChatBotListener.ts index 7df5fcb..275219a 100644 --- a/backend/src/services/WbotServices/ChatBotListener.ts +++ b/backend/src/services/WbotServices/ChatBotListener.ts @@ -1,8 +1,9 @@ -import { proto, WASocket } from "@adiwajshing/baileys"; +import { proto, WASocket } from "@whiskeysockets/baileys"; import Contact from "../../models/Contact"; import Ticket from "../../models/Ticket"; import { Store } from "../../libs/store"; -import { getBodyMessage, verifyMessage } from "./wbotMessageListener"; +import { getBodyMessage } from "./wbotMessageListener"; +import { verifyMessage } from "./wbotMessageListener"; import ShowDialogChatBotsServices from "../DialogChatBotsServices/ShowDialogChatBotsServices"; import ShowQueueService from "../QueueService/ShowQueueService"; import ShowChatBotServices from "../ChatBotServices/ShowChatBotServices"; diff --git a/backend/src/services/WbotServices/CheckIsValidContact.ts b/backend/src/services/WbotServices/CheckIsValidContact.ts index f85543f..106c2ef 100644 --- a/backend/src/services/WbotServices/CheckIsValidContact.ts +++ b/backend/src/services/WbotServices/CheckIsValidContact.ts @@ -1,4 +1,4 @@ -import { WASocket } from "@adiwajshing/baileys"; +import { WASocket } from "@whiskeysockets/baileys"; import AppError from "../../errors/AppError"; import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; import { getWbot } from "../../libs/wbot"; diff --git a/backend/src/services/WbotServices/DeleteWhatsAppMessage.ts b/backend/src/services/WbotServices/DeleteWhatsAppMessage.ts index 1a2cde0..6fea8b7 100644 --- a/backend/src/services/WbotServices/DeleteWhatsAppMessage.ts +++ b/backend/src/services/WbotServices/DeleteWhatsAppMessage.ts @@ -1,4 +1,4 @@ -import { WASocket } from "@adiwajshing/baileys"; +import { WASocket } from "@whiskeysockets/baileys"; import AppError from "../../errors/AppError"; import GetTicketWbot from "../../helpers/GetTicketWbot"; import GetWbotMessage from "../../helpers/GetWbotMessage"; diff --git a/backend/src/services/WbotServices/NormalizadorMejorado.ts b/backend/src/services/WbotServices/NormalizadorMejorado.ts new file mode 100644 index 0000000..c9a27d4 --- /dev/null +++ b/backend/src/services/WbotServices/NormalizadorMejorado.ts @@ -0,0 +1,115 @@ +// src/utils/NormalizadorMejorado.ts +import { WASocket } from "@whiskeysockets/baileys"; +// import { logger } from "../utils/logger"; +const logger = console; + +export class NormalizadorMejorado { + + /** + * Normaliza un JID para envío, convirtiendo LIDs a números reales + */ + async normalizarParaEnvio(sock: WASocket, jidDestino: string, companyId: number): Promise { + try { + console.log('🔄 Normalizando JID para envío:', jidDestino); + + // Si ya es un número tradicional con @s.whatsapp.net, retornar tal cual + if (jidDestino.includes('@s.whatsapp.net')) { + console.log('✅ JID ya normalizado (tradicional):', jidDestino); + return jidDestino; + } + + // Si es un LID (contiene : o . y no tiene @s.whatsapp.net) + if (jidDestino.includes(':') || (jidDestino.includes('.') && !jidDestino.includes('@s.whatsapp.net'))) { + console.log('🔍 Detectado LID, buscando número real...'); + + // Intentar usar onWhatsApp para obtener el número real + try { + const results = await sock.onWhatsApp(jidDestino); + + if (results && results.length > 0 && results[0]?.exists) { + const numeroReal = results[0].jid; + console.log('✅ LID convertido a número real:', jidDestino, '->', numeroReal); + return numeroReal; + } + } catch (error) { + console.error('❌ Error usando onWhatsApp:', error); + } + + // Si onWhatsApp falla, intentar extraer el número del LID + // Los LIDs suelen tener formato: 5492478409220:0@lid o similar + const match = jidDestino.match(/^(\d+)[:\-]/); + if (match && match[1]) { + const posibleNumero = match[1]; + console.log('🔧 Extrayendo número de LID:', jidDestino, '->', posibleNumero); + + // Verificar si este número existe en WhatsApp + try { + const results = await sock.onWhatsApp(posibleNumero + '@s.whatsapp.net'); + if (results && results.length > 0 && results[0]?.exists) { + const jidCompleto = results[0].jid; + console.log('✅ Número extraído válido:', jidCompleto); + return jidCompleto; + } + } catch (error) { + console.error('❌ Error verificando número extraído:', error); + } + + // Si no podemos verificar, al menos formatear como número tradicional + return posibleNumero + '@s.whatsapp.net'; + } + } + + // Si no es LID pero no tiene @s.whatsapp.net, agregarlo + if (!jidDestino.includes('@')) { + const jidNormalizado = jidDestino + '@s.whatsapp.net'; + console.log('🔧 Agregando dominio a JID:', jidDestino, '->', jidNormalizado); + return jidNormalizado; + } + + // Si llegamos aquí, retornar el JID original + console.log('⚠️ No se pudo normalizar, usando original:', jidDestino); + return jidDestino; + + } catch (error) { + console.error('❌ Error en normalizador:', error); + return jidDestino; + } + } + + /** + * Obtiene el número real de un JID (LID o tradicional) + */ + async obtenerNumeroReal(sock: WASocket, jid: string): Promise { + try { + // Si ya es un número tradicional + if (jid.includes('@s.whatsapp.net')) { + return jid.split('@')[0]; + } + + // Si es un LID + if (jid.includes(':') || (jid.includes('.') && !jid.includes('@s.whatsapp.net'))) { + try { + const results = await sock.onWhatsApp(jid); + if (results && results.length > 0 && results[0]?.exists) { + return results[0].jid.split('@')[0]; + } + } catch (error) { + console.error('Error en obtenerNumeroReal:', error); + } + + // Fallback: extraer número del LID + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + return match[1]; + } + } + + // Fallback final + return jid.replace(/\D/g, ''); + + } catch (error) { + console.error('Error en obtenerNumeroReal:', error); + return jid.replace(/\D/g, ''); + } + } +} \ No newline at end of file diff --git a/backend/src/services/WbotServices/SendWhatsAppMedia.ts b/backend/src/services/WbotServices/SendWhatsAppMedia.ts index ade760a..85d198c 100644 --- a/backend/src/services/WbotServices/SendWhatsAppMedia.ts +++ b/backend/src/services/WbotServices/SendWhatsAppMedia.ts @@ -1,4 +1,4 @@ -import { WAMessage, AnyMessageContent } from "@adiwajshing/baileys"; +import { WAMessage, AnyMessageContent } from "@whiskeysockets/baileys"; import fs from "fs"; import { exec } from "child_process"; diff --git a/backend/src/services/WbotServices/SendWhatsAppMessage.ts b/backend/src/services/WbotServices/SendWhatsAppMessage.ts index 08c2dab..a4eae61 100644 --- a/backend/src/services/WbotServices/SendWhatsAppMessage.ts +++ b/backend/src/services/WbotServices/SendWhatsAppMessage.ts @@ -1,9 +1,9 @@ -import { WAMessage } from "@adiwajshing/baileys"; +import { WAMessage } from "@whiskeysockets/baileys"; import AppError from "../../errors/AppError"; import GetTicketWbot from "../../helpers/GetTicketWbot"; +import GetRealNumberFromLid from "../../helpers/GetRealNumberFromLid"; import Message from "../../models/Message"; import Ticket from "../../models/Ticket"; - import formatBody from "../../helpers/Mustache"; interface Request { @@ -19,28 +19,95 @@ const SendWhatsAppMessage = async ({ }: Request): Promise => { let options = {}; const wbot = await GetTicketWbot(ticket); - const number = `${ticket.contact.number}@${ - ticket.isGroup ? "g.us" : "s.whatsapp.net" - }`; + + // OBTENER NÚMERO REAL SI ES LID + let contactNumber = ticket.contact.number; + const whatsappId = ticket.whatsappId?.toString() || "default"; // Convertir a string + + console.log(`[SendWhatsAppMessage] Enviando mensaje a ticket: ${ticket.id}`); + console.log(`[SendWhatsAppMessage] Número original: ${contactNumber}`); + console.log(`[SendWhatsAppMessage] WhatsApp ID: ${whatsappId}`); + + // Usar helper para obtener número real si es LID + const mappingResult = await GetRealNumberFromLid(whatsappId, contactNumber); + + console.log(`[SendWhatsAppMessage] Resultado mapping:`); + console.log(` - Número real: ${mappingResult.realNumber}`); + console.log(` - Es LID: ${mappingResult.isLid}`); + console.log(` - Original: ${mappingResult.original}`); + + // Siempre usar el número real para construir el JID + const targetNumber = mappingResult.realNumber; + + // Determinar el dominio basado en si es grupo + let domain = "s.whatsapp.net"; + if (ticket.isGroup) { + domain = "g.us"; + } + + const number = `${targetNumber}@${domain}`; + + console.log(`[SendWhatsAppMessage] Enviando a: ${number}`); + if (quotedMsg) { const chatMessages = await Message.findOne({ where: { id: quotedMsg.id } }); + + // ========== AGREGAR ESTOS LOGS ========== + console.log('🔍 DEBUG QUOTED MESSAGE INICIO'); + console.log(' quotedMsg recibido del frontend:', JSON.stringify(quotedMsg)); + console.log(' chatMessages encontrado en BD:', chatMessages ? `SÍ (ID: ${chatMessages.id})` : 'NO'); + + if (chatMessages && chatMessages.dataJson) { + + // ========== AGREGAR ESTOS LOGS ========== + console.log(' ✅ chatMessages tiene dataJson'); + console.log(' Tipo de dataJson:', typeof chatMessages.dataJson); + // LOG CRÍTICO: Ver dataJson antes de procesar + //console.log(' dataJson crudo (primeros 500 chars):', + console.log(' dataJson crudo:', chatMessages.dataJson); + const msgFound = JSON.parse(JSON.stringify(chatMessages.dataJson)); - const msgFound = JSON.parse(JSON.stringify(chatMessages.dataJson)); + // LOGS PARA VERIFICAR msgFound + console.log(' msgFound parseado:', typeof msgFound); + console.log(' ¿msgFound.key existe?:', msgFound && 'key' in msgFound); + console.log(' ¿msgFound.message existe?:', msgFound && 'message' in msgFound); + if (msgFound && msgFound.key) { + console.log(' msgFound.key.remoteJid:', msgFound.key?.remoteJid); + console.log(' msgFound.key.fromMe:', msgFound.key?.fromMe); + console.log(' msgFound.key.id:', msgFound.key?.id); + } else { + console.log(' ❌ msgFound.key NO EXISTE o es null'); + } - options = { - quoted: { - key: msgFound.key, - message: { - extendedTextMessage: msgFound.message.extendedTextMessage + options = { + quoted: { + key: msgFound.key, + message: msgFound.message } - } - }; + }; + console.log(' Objeto quoted construido:', options); + + } else { + // LOG SI FALLA ALGUNA CONDICIÓN + if (!chatMessages) { + console.log(' ❌ No se encontró el mensaje en la BD con id:', quotedMsg.id); + } else if (!chatMessages.dataJson) { + console.log(' ❌ El mensaje no tiene dataJson. Mensaje info:', { + id: chatMessages.id, + body: chatMessages.body?.substring(0, 100), + fromMe: chatMessages.fromMe, + mediaType: chatMessages.mediaType, + createdAt: chatMessages.createdAt + }); + } + } + } - + console.log('📤 Opciones finales para wbot.sendMessage:', JSON.stringify(options)); try { const sentMessage = await wbot.sendMessage( number, @@ -51,11 +118,55 @@ const SendWhatsAppMessage = async ({ ...options } ); - await ticket.update({ lastMessage: formatBody(body, ticket.contact) }); + + // Actualizar última mensaje + await ticket.update({ + lastMessage: formatBody(body, ticket.contact), + lastMessageAt: new Date() + }); + + console.log(`[SendWhatsAppMessage] ✅ Mensaje enviado exitosamente a: ${number}`); + return sentMessage; - } catch (err) { + + } catch (err: any) { + console.error(`[SendWhatsAppMessage] ❌ Error enviando mensaje:`, err); + + // Si el error es específico de LID, podríamos intentar con número normal + if (err.message && err.message.includes("lid")) { + console.log(`[SendWhatsAppMessage] 🔄 Intentando fallback con número normal...`); + + const fallbackNumber = `${targetNumber}@s.whatsapp.net`; + + try { + const sentMessage = await wbot.sendMessage( + fallbackNumber, + { + text: formatBody(body, ticket.contact) + }, + { + ...options + } + ); + + await ticket.update({ + lastMessage: formatBody(body, ticket.contact), + lastMessageAt: new Date() + }); + + console.log(`[SendWhatsAppMessage] ✅ Mensaje enviado (fallback) a: ${fallbackNumber}`); + return sentMessage; + + } catch (fallbackErr) { + console.error(`[SendWhatsAppMessage] ❌ Error en fallback:`, fallbackErr); + throw new AppError("ERR_SENDING_WAPP_MSG"); + } + } + throw new AppError("ERR_SENDING_WAPP_MSG"); } }; export default SendWhatsAppMessage; + + diff --git a/backend/src/services/WbotServices/SendWhatsAppMessage.ts.backup b/backend/src/services/WbotServices/SendWhatsAppMessage.ts.backup new file mode 100644 index 0000000..a7a0b0b --- /dev/null +++ b/backend/src/services/WbotServices/SendWhatsAppMessage.ts.backup @@ -0,0 +1,172 @@ +import { WAMessage } from "@whiskeysockets/baileys"; +import AppError from "../../errors/AppError"; +import GetTicketWbot from "../../helpers/GetTicketWbot"; +import GetRealNumberFromLid from "../../helpers/GetRealNumberFromLid"; +import Message from "../../models/Message"; +import Ticket from "../../models/Ticket"; +import formatBody from "../../helpers/Mustache"; + +interface Request { + body: string; + ticket: Ticket; + quotedMsg?: Message; +} + +const SendWhatsAppMessage = async ({ + body, + ticket, + quotedMsg +}: Request): Promise => { + let options = {}; + const wbot = await GetTicketWbot(ticket); + + // OBTENER NÚMERO REAL SI ES LID + let contactNumber = ticket.contact.number; + const whatsappId = ticket.whatsappId?.toString() || "default"; // Convertir a string + + console.log(`[SendWhatsAppMessage] Enviando mensaje a ticket: ${ticket.id}`); + console.log(`[SendWhatsAppMessage] Número original: ${contactNumber}`); + console.log(`[SendWhatsAppMessage] WhatsApp ID: ${whatsappId}`); + + // Usar helper para obtener número real si es LID + const mappingResult = await GetRealNumberFromLid(whatsappId, contactNumber); + + console.log(`[SendWhatsAppMessage] Resultado mapping:`); + console.log(` - Número real: ${mappingResult.realNumber}`); + console.log(` - Es LID: ${mappingResult.isLid}`); + console.log(` - Original: ${mappingResult.original}`); + + // Siempre usar el número real para construir el JID + const targetNumber = mappingResult.realNumber; + + // Determinar el dominio basado en si es grupo + let domain = "s.whatsapp.net"; + if (ticket.isGroup) { + domain = "g.us"; + } + + const number = `${targetNumber}@${domain}`; + + console.log(`[SendWhatsAppMessage] Enviando a: ${number}`); + + if (quotedMsg) { + const chatMessages = await Message.findOne({ + where: { + id: quotedMsg.id + } + }); + + // ========== AGREGAR ESTOS LOGS ========== + console.log('🔍 DEBUG QUOTED MESSAGE INICIO'); + console.log(' quotedMsg recibido del frontend:', JSON.stringify(quotedMsg)); + console.log(' chatMessages encontrado en BD:', chatMessages ? `SÍ (ID: ${chatMessages.id})` : 'NO'); + + if (chatMessages && chatMessages.dataJson) { + + // ========== AGREGAR ESTOS LOGS ========== + console.log(' ✅ chatMessages tiene dataJson'); + console.log(' Tipo de dataJson:', typeof chatMessages.dataJson); + // LOG CRÍTICO: Ver dataJson antes de procesar + console.log(' dataJson crudo (primeros 500 chars):', + + const msgFound = JSON.parse(JSON.stringify(chatMessages.dataJson)); + + // LOGS PARA VERIFICAR msgFound + console.log(' msgFound parseado:', typeof msgFound); + console.log(' ¿msgFound.key existe?:', msgFound && 'key' in msgFound); + console.log(' ¿msgFound.message existe?:', msgFound && 'message' in msgFound); + if (msgFound && msgFound.key) { + console.log(' msgFound.key.remoteJid:', msgFound.key?.remoteJid); + console.log(' msgFound.key.fromMe:', msgFound.key?.fromMe); + console.log(' msgFound.key.id:', msgFound.key?.id); + } else { + console.log(' ❌ msgFound.key NO EXISTE o es null'); + } + + options = { + quoted: { + key: msgFound.key, + message: msgFound.message + } + }; + console.log(' Objeto quoted construido:', options); + + } else { + // LOG SI FALLA ALGUNA CONDICIÓN + if (!chatMessages) { + console.log(' ❌ No se encontró el mensaje en la BD con id:', quotedMsg.id); + } else if (!chatMessages.dataJson) { + console.log(' ❌ El mensaje no tiene dataJson. Mensaje info:', { + id: chatMessages.id, + body: chatMessages.body?.substring(0, 100), + fromMe: chatMessages.fromMe, + mediaType: chatMessages.mediaType, + createdAt: chatMessages.createdAt + }); + } + } + } + } + console.log('📤 Opciones finales para wbot.sendMessage:', JSON.stringify(options)); + try { + const sentMessage = await wbot.sendMessage( + number, + { + text: formatBody(body, ticket.contact) + }, + { + ...options + } + ); + + // Actualizar última mensaje + await ticket.update({ + lastMessage: formatBody(body, ticket.contact), + lastMessageAt: new Date() + }); + + console.log(`[SendWhatsAppMessage] ✅ Mensaje enviado exitosamente a: ${number}`); + + return sentMessage; + + } catch (err: any) { + console.error(`[SendWhatsAppMessage] ❌ Error enviando mensaje:`, err); + + // Si el error es específico de LID, podríamos intentar con número normal + if (err.message && err.message.includes("lid")) { + console.log(`[SendWhatsAppMessage] 🔄 Intentando fallback con número normal...`); + + const fallbackNumber = `${targetNumber}@s.whatsapp.net`; + + try { + const sentMessage = await wbot.sendMessage( + fallbackNumber, + { + text: formatBody(body, ticket.contact) + }, + { + ...options + } + ); + + await ticket.update({ + lastMessage: formatBody(body, ticket.contact), + lastMessageAt: new Date() + }); + + console.log(`[SendWhatsAppMessage] ✅ Mensaje enviado (fallback) a: ${fallbackNumber}`); + return sentMessage; + + } catch (fallbackErr) { + console.error(`[SendWhatsAppMessage] ❌ Error en fallback:`, fallbackErr); + throw new AppError("ERR_SENDING_WAPP_MSG"); + } + } + + throw new AppError("ERR_SENDING_WAPP_MSG"); + } +}; + +export default SendWhatsAppMessage; + + diff --git a/backend/src/services/WbotServices/SendWhatsAppMessage.ts.bak b/backend/src/services/WbotServices/SendWhatsAppMessage.ts.bak new file mode 100644 index 0000000..c0d17fa --- /dev/null +++ b/backend/src/services/WbotServices/SendWhatsAppMessage.ts.bak @@ -0,0 +1,61 @@ +import { WAMessage } from "@whiskeysockets/baileys"; +import AppError from "../../errors/AppError"; +import GetTicketWbot from "../../helpers/GetTicketWbot"; +import Message from "../../models/Message"; +import Ticket from "../../models/Ticket"; + +import formatBody from "../../helpers/Mustache"; + +interface Request { + body: string; + ticket: Ticket; + quotedMsg?: Message; +} + +const SendWhatsAppMessage = async ({ + body, + ticket, + quotedMsg +}: Request): Promise => { + let options = {}; + const wbot = await GetTicketWbot(ticket); + const number = `${ticket.contact.number}@${ + ticket.isGroup ? "g.us" : "s.whatsapp.net" + }`; + if (quotedMsg) { + const chatMessages = await Message.findOne({ + where: { + id: quotedMsg.id + } + }); + + const msgFound = JSON.parse(JSON.stringify(chatMessages.dataJson)); + + options = { + quoted: { + key: msgFound.key, + message: { + extendedTextMessage: msgFound.message.extendedTextMessage + } + } + }; + } + + try { + const sentMessage = await wbot.sendMessage( + number, + { + text: formatBody(body, ticket.contact) + }, + { + ...options + } + ); + await ticket.update({ lastMessage: formatBody(body, ticket.contact) }); + return sentMessage; + } catch (err) { + throw new AppError("ERR_SENDING_WAPP_MSG"); + } +}; + +export default SendWhatsAppMessage; diff --git a/backend/src/services/WbotServices/SendWhatsAppMessage.ts.current b/backend/src/services/WbotServices/SendWhatsAppMessage.ts.current new file mode 100644 index 0000000..b49e662 --- /dev/null +++ b/backend/src/services/WbotServices/SendWhatsAppMessage.ts.current @@ -0,0 +1,172 @@ +import { WAMessage } from "@whiskeysockets/baileys"; +import AppError from "../../errors/AppError"; +import GetTicketWbot from "../../helpers/GetTicketWbot"; +import GetRealNumberFromLid from "../../helpers/GetRealNumberFromLid"; +import Message from "../../models/Message"; +import Ticket from "../../models/Ticket"; +import formatBody from "../../helpers/Mustache"; + +interface Request { + body: string; + ticket: Ticket; + quotedMsg?: Message; +} + +const SendWhatsAppMessage = async ({ + body, + ticket, + quotedMsg +}: Request): Promise => { + let options = {}; + const wbot = await GetTicketWbot(ticket); + + // OBTENER NÚMERO REAL SI ES LID + let contactNumber = ticket.contact.number; + const whatsappId = ticket.whatsappId?.toString() || "default"; // Convertir a string + + console.log(`[SendWhatsAppMessage] Enviando mensaje a ticket: ${ticket.id}`); + console.log(`[SendWhatsAppMessage] Número original: ${contactNumber}`); + console.log(`[SendWhatsAppMessage] WhatsApp ID: ${whatsappId}`); + + // Usar helper para obtener número real si es LID + const mappingResult = await GetRealNumberFromLid(whatsappId, contactNumber); + + console.log(`[SendWhatsAppMessage] Resultado mapping:`); + console.log(` - Número real: ${mappingResult.realNumber}`); + console.log(` - Es LID: ${mappingResult.isLid}`); + console.log(` - Original: ${mappingResult.original}`); + + // Siempre usar el número real para construir el JID + const targetNumber = mappingResult.realNumber; + + // Determinar el dominio basado en si es grupo + let domain = "s.whatsapp.net"; + if (ticket.isGroup) { + domain = "g.us"; + } + + const number = `${targetNumber}@${domain}`; + + console.log(`[SendWhatsAppMessage] Enviando a: ${number}`); + + if (quotedMsg) { + const chatMessages = await Message.findOne({ + where: { + id: quotedMsg.id + } + }); + + // ========== AGREGAR ESTOS LOGS ========== + console.log('🔍 DEBUG QUOTED MESSAGE INICIO'); + console.log(' quotedMsg recibido del frontend:', JSON.stringify(quotedMsg)); + console.log(' chatMessages encontrado en BD:', chatMessages ? `SÍ (ID: ${chatMessages.id})` : 'NO'); + + if (chatMessages && chatMessages.dataJson) { + + // ========== AGREGAR ESTOS LOGS ========== + console.log(' ✅ chatMessages tiene dataJson'); + console.log(' Tipo de dataJson:', typeof chatMessages.dataJson); + // LOG CRÍTICO: Ver dataJson antes de procesar + //console.log(' dataJson crudo (primeros 500 chars):', + console.log(' dataJson crudo:', chatMessages.dataJson); + const msgFound = JSON.parse(JSON.stringify(chatMessages.dataJson)); + + // LOGS PARA VERIFICAR msgFound + console.log(' msgFound parseado:', typeof msgFound); + console.log(' ¿msgFound.key existe?:', msgFound && 'key' in msgFound); + console.log(' ¿msgFound.message existe?:', msgFound && 'message' in msgFound); + if (msgFound && msgFound.key) { + console.log(' msgFound.key.remoteJid:', msgFound.key?.remoteJid); + console.log(' msgFound.key.fromMe:', msgFound.key?.fromMe); + console.log(' msgFound.key.id:', msgFound.key?.id); + } else { + console.log(' ❌ msgFound.key NO EXISTE o es null'); + } + + options = { + quoted: { + key: msgFound.key, + message: msgFound.message + } + }; + console.log(' Objeto quoted construido:', options); + + } else { + // LOG SI FALLA ALGUNA CONDICIÓN + if (!chatMessages) { + console.log(' ❌ No se encontró el mensaje en la BD con id:', quotedMsg.id); + } else if (!chatMessages.dataJson) { + console.log(' ❌ El mensaje no tiene dataJson. Mensaje info:', { + id: chatMessages.id, + body: chatMessages.body?.substring(0, 100), + fromMe: chatMessages.fromMe, + mediaType: chatMessages.mediaType, + createdAt: chatMessages.createdAt + }); + } + } + } + } + console.log('📤 Opciones finales para wbot.sendMessage:', JSON.stringify(options)); + try { + const sentMessage = await wbot.sendMessage( + number, + { + text: formatBody(body, ticket.contact) + }, + { + ...options + } + ); + + // Actualizar última mensaje + await ticket.update({ + lastMessage: formatBody(body, ticket.contact), + lastMessageAt: new Date() + }); + + console.log(`[SendWhatsAppMessage] ✅ Mensaje enviado exitosamente a: ${number}`); + + return sentMessage; + + } catch (err: any) { + console.error(`[SendWhatsAppMessage] ❌ Error enviando mensaje:`, err); + + // Si el error es específico de LID, podríamos intentar con número normal + if (err.message && err.message.includes("lid")) { + console.log(`[SendWhatsAppMessage] 🔄 Intentando fallback con número normal...`); + + const fallbackNumber = `${targetNumber}@s.whatsapp.net`; + + try { + const sentMessage = await wbot.sendMessage( + fallbackNumber, + { + text: formatBody(body, ticket.contact) + }, + { + ...options + } + ); + + await ticket.update({ + lastMessage: formatBody(body, ticket.contact), + lastMessageAt: new Date() + }); + + console.log(`[SendWhatsAppMessage] ✅ Mensaje enviado (fallback) a: ${fallbackNumber}`); + return sentMessage; + + } catch (fallbackErr) { + console.error(`[SendWhatsAppMessage] ❌ Error en fallback:`, fallbackErr); + throw new AppError("ERR_SENDING_WAPP_MSG"); + } + } + + throw new AppError("ERR_SENDING_WAPP_MSG"); + } +}; + +export default SendWhatsAppMessage; + + diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 207d0d7..dc83b13 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -4,6 +4,7 @@ import { writeFile } from "fs"; import * as Sentry from "@sentry/node"; import { + downloadMediaMessage, downloadContentFromMessage, jidNormalizedUser, MediaType, @@ -14,8 +15,9 @@ import { WASocket, getContentType, extractMessageContent, - WAMessageStubType -} from "@adiwajshing/baileys"; + WAMessageStubType, + AnyMessageContent +} from "@whiskeysockets/baileys"; import Contact from "../../models/Contact"; import Ticket from "../../models/Ticket"; @@ -34,6 +36,9 @@ import { debounce } from "../../helpers/Debounce"; import UpdateTicketService from "../TicketServices/UpdateTicketService"; import { sayChatbot } from "./ChatBotListener"; import hourExpedient from "./hourExpedient"; +import { NormalizadorMejorado } from "../../utils/NormalizadorMejorado"; // IMPORTAR EL NUEVO NORMALIZADOR +const DEBUG_MEDIA = false; // Cambia a false en producción + type Session = WASocket & { id?: number; @@ -45,311 +50,375 @@ interface ImessageUpsert { type: MessageUpsertType; } +// ========== INTERFACES ACTUALIZADAS ========== interface IMe { name: string; id: string; + lid?: string; + esLid?: boolean; + esLidValido?: boolean; // NUEVO: indica si el LID pudo convertirse a número real + number?: string; + originalJid?: string; } const writeFileAsync = promisify(writeFile); -const getTypeMessage = (msg: proto.IWebMessageInfo): string => { - return getContentType(msg.message); -}; - -const getBodyButton = (msg: proto.IWebMessageInfo): string => { - if (msg.key.fromMe && msg?.message?.buttonsMessage?.contentText) { - let bodyMessage = `*${msg?.message?.buttonsMessage?.contentText}*`; - // eslint-disable-next-line no-restricted-syntax - for (const buton of msg.message?.buttonsMessage?.buttons) { - bodyMessage += `\n\n${buton.buttonText?.displayText}`; - } - return bodyMessage; +// Instancia global del normalizador +const normalizador = new NormalizadorMejorado(); + +// ========== FUNCIÓN MEJORADA PARA OBTENER NÚMERO REAL ========== + +async function obtenerNumeroRealDeMensaje(wbot: Session, message: proto.IWebMessageInfo): Promise<{ + numeroReal: string; + jidReal: string; + esLid: boolean; + esLidValido: boolean; + lidOriginal?: string; +}> { + let jid = message.key.remoteJid || ''; + + if (message.key.participant) { + jid = message.key.participant; } - - if (msg.key.fromMe && msg?.message?.listMessage) { - let bodyMessage = `*${msg?.message?.listMessage?.description}*`; - // eslint-disable-next-line no-restricted-syntax - for (const buton of msg.message?.listMessage?.sections) { - // eslint-disable-next-line no-restricted-syntax - for (const rows of buton.rows) { - bodyMessage += `\n\n${rows.title}`; + + if (!jid) { + return { + numeroReal: '', + jidReal: '', + esLid: false, + esLidValido: false + }; + } + + console.log('🔍 ANALIZANDO JID:', jid); + console.log(' Tipo:', jid.includes('@lid') ? 'LID' : jid.includes('@s.whatsapp.net') ? 'Tradicional' : 'Desconocido'); + + // DETECCIÓN MEJORADA DE LIDs + const esLid = jid.includes('@lid') || jid.includes(':') || (jid.includes('.') && !jid.includes('@s.whatsapp.net')); + + if (esLid && jid.includes('@lid')) { + console.log('✅ DETECTADO LID CON @lid:', jid); + + try { + // Intentar obtener número real con onWhatsApp + const results = await wbot.onWhatsApp(jid); + + if (results && results.length > 0 && results[0]?.exists) { + // ¡ÉXITO! Tenemos el número real + const numeroReal = results[0].jid.split('@')[0]; + const jidReal = results[0].jid; + + console.log('🎯 LID CONVERTIDO A NÚMERO REAL:'); + console.log(' LID original:', jid); + console.log(' Número real:', numeroReal); + console.log(' JID real:', jidReal); + + return { + numeroReal, + jidReal, + esLid: true, + esLidValido: true, + lidOriginal: jid + }; + } else { + // onWhatsApp falló o el LID no existe + console.log('⚠️ LID NO PUDO CONVERTIRSE:', jid); + console.log(' onWhatsApp no devolvió resultados válidos'); + + // Extraer la parte numérica del LID para placeholder + console.log('🔍🔍🔍 DEBUG LID FALLBACK 🔍🔍🔍'); +console.log('JID completo:', jid); +console.log('Parte antes de @:', jid.split('@')[0]); +console.log('Solo números:', jid.split('@')[0].replace(/\D/g, '')); +console.log('lidSinSuffix:', jid.split('@')[0].replace(/\D/g, '')); +console.log('placeholder:', `LID_${jid.split('@')[0].replace(/\D/g, '').substring(0, 10)}`); +console.log('🔍🔍🔍 FIN DEBUG 🔍🔍🔍'); + + const lidSinSuffix = jid.split('@')[0]; + const placeholder = `LID_${lidSinSuffix.substring(0, 10)}`; // Limitar longitud + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; } + } catch (error) { + console.error('❌ Error en onWhatsApp para LID:', error); + + // Fallback: usar placeholder seguro + const lidSinSuffix = jid.split('@')[0].replace(/\D/g, ''); + const placeholder = lidSinSuffix ? `LID_${lidSinSuffix.substring(0, 10)}` : 'LID_DESCONOCIDO'; + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; } - - return bodyMessage; } - if (msg.key.fromMe && msg?.message?.viewOnceMessage?.message?.listMessage) { - let bodyMessage = `*${msg?.message?.viewOnceMessage?.message?.listMessage?.description}*`; - // eslint-disable-next-line no-restricted-syntax - for (const buton of msg?.message?.viewOnceMessage?.message?.listMessage - ?.sections) { - // eslint-disable-next-line no-restricted-syntax - for (const rows of buton.rows) { - bodyMessage += `\n\n${rows.title}`; + + // Si NO es LID con @lid, usar el normalizador existente + try { + const numeroReal = await normalizador.obtenerNumeroReal(wbot, jid); + + console.log('✅ RESULTADO (no LID @lid):'); + console.log(' JID original:', jid); + console.log(' Número obtenido:', numeroReal); + console.log(' Es LID?:', esLid ? 'SÍ' : 'NO'); + + return { + numeroReal, + jidReal: numeroReal + '@s.whatsapp.net', + esLid, + esLidValido: false, + lidOriginal: esLid ? jid : undefined + }; + + } catch (error) { + console.error('❌ Error obteniendo número real:', error); + + // Fallback + let numeroFallback = jid.split('@')[0]; + if (jid.includes(':')) { + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + numeroFallback = match[1]; } } - - return bodyMessage; + + return { + numeroReal: numeroFallback, + jidReal: numeroFallback + '@s.whatsapp.net', + esLid: false, + esLidValido: false + }; } - if ( - msg.key.fromMe && - msg?.message?.viewOnceMessage?.message?.buttonsMessage - ) { - let bodyMessage = `*${msg?.message?.viewOnceMessage?.message?.buttonsMessage?.contentText}*`; - // eslint-disable-next-line no-restricted-syntax - for (const buton of msg?.message?.viewOnceMessage?.message?.buttonsMessage - ?.buttons) { - bodyMessage += `\n\n${buton.buttonText?.displayText}`; - } +} +// ========== FIN FUNCIÓN MEJORADA ========== - return bodyMessage; - } +const getTypeMessage = (msg: proto.IWebMessageInfo): string => { + return getContentType(msg.message); }; -const msgLocation = (image, latitude, longitude) => { - if (image) { - const b64 = Buffer.from(image).toString("base64"); - - const data = `data:image/png;base64, ${b64} | https://maps.google.com/maps?q=${latitude}%2C${longitude}&z=17&hl=pt-BR|${latitude}, ${longitude} `; - return data; +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +const getBodyButton = (message: proto.IWebMessageInfo): string => { + if (message.message?.templateMessage?.hydratedFourRowTemplate) { + return ( + message.message.templateMessage.hydratedFourRowTemplate.hydratedContentText || + "" + ); } -}; - -export const getBodyMessage = (msg: proto.IWebMessageInfo): string | null => { - try { - const type = getTypeMessage(msg); - - const types = { - conversation: msg.message.conversation, - imageMessage: msg.message.imageMessage?.caption, - videoMessage: msg.message.videoMessage?.caption, - extendedTextMessage: msg.message.extendedTextMessage?.text, - buttonsResponseMessage: - msg.message.buttonsResponseMessage?.selectedDisplayText, - listResponseMessage: - msg.message.listResponseMessage?.title || - msg.message.listResponseMessage?.singleSelectReply?.selectedRowId, - templateButtonReplyMessage: - msg.message?.templateButtonReplyMessage?.selectedId, - messageContextInfo: - msg.message.buttonsResponseMessage?.selectedButtonId || - msg.message.listResponseMessage?.title, - buttonsMessage: - getBodyButton(msg) || msg.message.listResponseMessage?.title, - stickerMessage: "sticker", - contactMessage: msg.message?.contactMessage?.vcard, - contactsArrayMessage: "varios contatos", - // locationMessage: `Latitude: ${msg.message.locationMessage?.degreesLatitude} - Longitude: ${msg.message.locationMessage?.degreesLongitude}`, - locationMessage: msgLocation( - msg.message?.locationMessage?.jpegThumbnail, - msg.message?.locationMessage?.degreesLatitude, - msg.message?.locationMessage?.degreesLongitude - ), - liveLocationMessage: `Latitude: ${msg.message.liveLocationMessage?.degreesLatitude} - Longitude: ${msg.message.liveLocationMessage?.degreesLongitude}`, - documentMessage: msg.message.documentMessage?.title, - audioMessage: "Áudio", - listMessage: getBodyButton(msg) || msg.message.listResponseMessage?.title, - viewOnceMessage: getBodyButton(msg), - reactionMessage: msg.message.reactionMessage?.text || "reaction" - }; - - const objKey = Object.keys(types).find(key => key === type); - - if (!objKey) { - logger.warn(`#### Nao achou o type 152: ${type} -${JSON.stringify(msg)}`); - Sentry.setExtra("Mensagem", { BodyMsg: msg.message, msg, type }); - Sentry.captureException( - new Error("Novo Tipo de Mensagem em getTypeMessage") - ); - } - return types[type]; - } catch (error) { - Sentry.setExtra("Error getTypeMessage", { msg, BodyMsg: msg.message }); - Sentry.captureException(error); - console.log(error); + if (message.message?.templateMessage?.hydratedTemplate) { + return ( + message.message.templateMessage.hydratedTemplate.hydratedContentText || + "" + ); } + return ""; }; -export const getQuotedMessage = (msg: proto.IWebMessageInfo) => { - const body = extractMessageContent(msg.message)[ - Object.keys(msg?.message).values().next().value - ]; - - if (!body?.contextInfo?.quotedMessage) return; - const quoted = extractMessageContent( - body?.contextInfo?.quotedMessage[ - Object.keys(body?.contextInfo?.quotedMessage).values().next().value - ] - ); - return quoted; +const msgLocation = (message: proto.IWebMessageInfo): proto.Message.ILocationMessage | null => { + return message.message?.locationMessage || null; }; -export const getQuotedMessageId = (msg: proto.IWebMessageInfo) => { - const body = extractMessageContent(msg.message)[ - Object.keys(msg?.message).values().next().value - ]; - const reaction = msg?.message?.reactionMessage - ? msg?.message?.reactionMessage?.key?.id - : ""; - - return reaction || body?.contextInfo?.stanzaId; +const getBodyMessage = (msg: proto.IWebMessageInfo): string => { + const extract = extractMessageContent(msg.message); + + if (extract) { + if (extract.conversation) { + return extract.conversation; + } + if (extract.extendedTextMessage?.text) { + return extract.extendedTextMessage.text; + } + if (extract.imageMessage?.caption) { + return extract.imageMessage.caption; + } + if (extract.videoMessage?.caption) { + return extract.videoMessage.caption; + } + if (extract.documentMessage?.caption) { + return extract.documentMessage.caption; + } + } + + const bodyButton = getBodyButton(msg); + if (bodyButton) return bodyButton; + + return ""; }; +// ========== FIN FUNCIONES FALTANTES ========== const getMeSocket = (wbot: Session): IMe => { return { id: jidNormalizedUser((wbot as WASocket).user.id), - name: (wbot as WASocket).user.name + name: (wbot as WASocket).user.name || "" }; }; -const getSenderMessage = ( - msg: proto.IWebMessageInfo, - wbot: Session -): string => { +const getSenderMessage = (msg: proto.IWebMessageInfo, wbot: Session): string => { const me = getMeSocket(wbot); if (msg.key.fromMe) return me.id; - const senderId = - msg.participant || msg.key.participant || msg.key.remoteJid || undefined; - - return senderId && jidNormalizedUser(senderId); + const senderId = msg.participant || msg.key.participant || msg.key.remoteJid || undefined; + return senderId ? jidNormalizedUser(senderId) : ""; }; -const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session) => { - const isGroup = msg.key.remoteJid.includes("g.us"); - const rawNumber = msg.key.remoteJid.replace(/\D/g, ""); - return isGroup - ? { - id: getSenderMessage(msg, wbot), - name: msg.pushName - } - : { - id: msg.key.remoteJid, - name: msg.key.fromMe ? rawNumber : msg.pushName - }; +// ========== FUNCIÓN COMPLETAMENTE REESCRITA PARA MANEJAR LIDs ========== +const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session): Promise => { + const isGroup = msg.key.remoteJid?.includes("g.us") || false; + + if (isGroup) { + return { + id: getSenderMessage(msg, wbot), + name: msg.pushName || "" + }; + } + + // Para mensajes individuales, usar nuestro normalizador + try { + const { numeroReal, jidReal, esLid, lidOriginal } = await obtenerNumeroRealDeMensaje(wbot, msg); + + if (!numeroReal) { + throw new Error('No se pudo obtener número real'); + } + + return { + id: jidReal, + name: msg.key.fromMe ? numeroReal : msg.pushName || numeroReal, + number: numeroReal, + lid: lidOriginal, + esLid, + originalJid: msg.key.remoteJid || '' // Guardar el JID original + }; + + } catch (error) { + console.error('❌ Error en getContactMessage:', error); + + // Fallback extremo + const rawNumber = msg.key.remoteJid?.replace(/\D/g, "") || ""; + return { + id: msg.key.remoteJid || "", + name: msg.key.fromMe ? rawNumber : msg.pushName || rawNumber, + number: rawNumber + }; + } }; +// ========== FIN FUNCIÓN REESCRITA ========== +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +// ========== FUNCIÓN downloadMedia REESCRITA Y SEGURA ========== +// ========== FUNCIÓN downloadMedia MEJORADA ========== const downloadMedia = async (msg: proto.IWebMessageInfo) => { + let buffer; + try { + const logger: any = { + level: 'error', + debug: () => {}, + info: () => {}, + warn: () => {}, + error: console.error, + trace: () => {}, + child: () => logger + }; + + buffer = await downloadMediaMessage( + msg, + 'buffer', + {}, + { + logger, + reuploadRequest: async (msgToReupload: proto.IWebMessageInfo) => { + return msgToReupload; + } + } + ); + } catch (err: any) { + console.error('Erro ao baixar mídia:', err.message || err); + return null; + } + + if (!buffer) return null; + + let filename = msg.message?.documentMessage?.fileName || ""; + const mineType = msg.message?.imageMessage || msg.message?.audioMessage || msg.message?.videoMessage || msg.message?.stickerMessage || msg.message?.documentMessage || - msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; - - // eslint-disable-next-line no-nested-ternary - const messageType = msg.message?.documentMessage - ? "document" - : mineType.mimetype.split("/")[0].replace("application", "document") - ? (mineType.mimetype - .split("/")[0] - .replace("application", "document") as MediaType) - : (mineType.mimetype.split("/")[0] as MediaType); - - let stream; - let contDownload = 0; + msg.message?.documentWithCaptionMessage?.message?.documentMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.videoMessage; - while (contDownload < 10 && !stream) { - try { - // eslint-disable-next-line no-await-in-loop - stream = await downloadContentFromMessage( - msg.message.audioMessage || - msg.message.videoMessage || - msg.message.documentMessage || - msg.message.imageMessage || - msg.message.stickerMessage || - msg.message.extendedTextMessage?.contextInfo.quotedMessage - .imageMessage || - msg.message?.buttonsMessage?.imageMessage || - msg.message?.templateMessage?.fourRowTemplate?.imageMessage || - msg.message?.templateMessage?.hydratedTemplate?.imageMessage || - msg.message?.templateMessage?.hydratedFourRowTemplate?.imageMessage || - msg.message?.interactiveMessage?.header?.imageMessage, - messageType - ); - } catch (error) { - // eslint-disable-next-line no-plusplus - contDownload++; - // eslint-disable-next-line no-await-in-loop, no-loop-func - await new Promise(resolve => - setTimeout(resolve, 1000 * contDownload * 2) - ); - logger.warn( - `>>>> erro ${contDownload} de baixar o arquivo ${msg?.key.id}` - ); - } - } - - let buffer = Buffer.from([]); - // eslint-disable-next-line no-restricted-syntax - try { - // eslint-disable-next-line no-restricted-syntax - for await (const chunk of stream) { - buffer = Buffer.concat([buffer, chunk]); - } - } catch (error) { - return { data: "error", mimetype: "", filename: "" }; - } - - if (!buffer) { - Sentry.setExtra("ERR_WAPP_DOWNLOAD_MEDIA", { msg }); - Sentry.captureException(new Error("ERR_WAPP_DOWNLOAD_MEDIA")); - throw new Error("ERR_WAPP_DOWNLOAD_MEDIA"); - } - let filename = msg.message?.documentMessage?.fileName || ""; + if (!mineType) return null; if (!filename) { const ext = mineType.mimetype.split("/")[1].split(";")[0]; filename = `${new Date().getTime()}.${ext}`; + } else { + filename = `${new Date().getTime()}_${filename}`; } - const media = { + + return { data: buffer, mimetype: mineType.mimetype, filename }; - return media; -}; -const verifyContact = async ( - msgContact: IMe, - wbot: Session -): Promise => { - let profilePicUrl: string; - try { - profilePicUrl = await wbot.profilePictureUrl(msgContact.id); - } catch { - profilePicUrl = `${process.env.FRONTEND_URL}/nopicture.png`; - } - - const contactData = { - name: msgContact?.name || msgContact.id.replace(/\D/g, ""), - number: msgContact.id.replace(/\D/g, ""), - profilePicUrl, - isGroup: msgContact.id.includes("g.us") - }; - - const contact = CreateOrUpdateContactService(contactData); - - return contact; }; +// ========== FUNCIÓN HKDF SIMPLIFICADA ========== +function deriveWhatsAppKeys(mediaKey: Buffer): Buffer { + const crypto = require('crypto'); + + // Parámetros para WhatsApp + const salt = Buffer.alloc(32); // Salt vacío + const info = 'WhatsApp Image Keys'; // Info string + const length = 112; // Longitud total requerida + + // Extraer PRK (Pseudorandom Key) + const prk = crypto.createHmac('sha256', salt).update(mediaKey).digest(); + + // Expandir + const infoBuffer = Buffer.from(info); + const blocks = Math.ceil(length / 32); + + let t = Buffer.alloc(0); + let result = Buffer.alloc(0); + + for (let i = 0; i < blocks; i++) { + const tBuf = Buffer.concat([ + t, + infoBuffer, + Buffer.from([i + 1]) + ]); + t = crypto.createHmac('sha256', prk).update(tBuf).digest(); + result = Buffer.concat([result, t]); + } + + return result.slice(0, length); +} const verifyQuotedMessage = async ( msg: proto.IWebMessageInfo ): Promise => { - if (!msg) return null; - const quoted = getQuotedMessageId(msg); - - if (!quoted) return null; + if (!msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + return null; + } - const quotedMsg = await Message.findOne({ - where: { id: quoted } + const quoted = msg.message.extendedTextMessage.contextInfo; + + const quotedMessage = await Message.findOne({ + where: { id: quoted.stanzaId } }); - if (!quotedMsg) return null; - - return quotedMsg; + return quotedMessage; }; const verifyMediaMessage = async ( @@ -357,415 +426,256 @@ const verifyMediaMessage = async ( ticket: Ticket, contact: Contact ): Promise => { - const quotedMsg = await verifyQuotedMessage(msg); - - const media = await downloadMedia(msg); - - if (!media) { - throw new Error("ERR_WAPP_DOWNLOAD_MEDIA"); - } + console.log('✅ verifyMediaMessage EJECUTÁNDOSE'); + console.log(' Tipo de mensaje:', getTypeMessage(msg)); + console.log(' Contact ID:', contact.id); + console.log(' Ticket ID:', ticket.id); + + const io = getIO(); + let quotedMsg: Message | null = null; - if (!media.filename) { - const ext = media.mimetype.split("/")[1].split(";")[0]; - media.filename = `${new Date().getTime()}.${ext}`; + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); } - try { - await writeFileAsync( - join(__dirname, "..", "..", "..", "public", media.filename), - media.data, - "base64" - ); - } catch (err) { - Sentry.captureException(err); - logger.error(err); - } - - const body = getBodyMessage(msg); const messageData = { id: msg.key.id, ticketId: ticket.id, - contactId: msg.key.fromMe ? undefined : contact.id, - body: body || media.filename, + contactId: contact.id, + body: getBodyMessage(msg), fromMe: msg.key.fromMe, read: msg.key.fromMe, - mediaUrl: media.filename, - mediaType: media.mimetype.split("/")[0], - quotedMsgId: quotedMsg?.id, - ack: msg.status, - remoteJid: msg.key.remoteJid, - participant: msg.key.participant, - dataJson: JSON.stringify(msg) + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, }; - await ticket.update({ - lastMessage: body || media.filename - }); + try { + console.log('📥 ANTES de downloadMedia'); + console.log(' messageData.id:', messageData.id); + console.log(' Tipo de mensaje:', getTypeMessage(msg)); + console.log(' filename que se intentará crear:', `${messageData.id}.${getTypeMessage(msg).split("Message")[0]}`); + + const mediaPath = join(__dirname, "..", "..", "..", "public"); + const filename = `${messageData.id}.${getTypeMessage(msg).split("Message")[0]}`; + const path = join(mediaPath, filename); + + const mediaResult = await downloadMedia(msg); + console.log('📊 RESULTADO de downloadMedia:'); + console.log(' ¿Tiene resultado?:', mediaResult ? 'SÍ' : 'NO'); + + if (mediaResult) { + console.log(' Mimetype:', mediaResult.mimetype); + console.log(' Filename:', mediaResult.filename); + console.log(' Tamaño del buffer:', mediaResult.data?.length || 0); + + // Intentar guardar el archivo manualmente + try { + await writeFileAsync(path, mediaResult.data); + console.log('💾 Archivo guardado en:', path); + messageData.mediaUrl = filename; + } catch (writeError) { + console.error('❌ Error guardando archivo:', writeError); + } + } else { + console.log('❌ downloadMedia devolvió null/undefined'); + } + } catch (err) { + console.error('❌ ERROR en downloadMedia:', err); + Sentry.captureException(err); + logger.error(err); + } const newMessage = await CreateMessageService({ messageData }); + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + return newMessage; }; -export const verifyMessage = async ( +const verifyMessage = async ( msg: proto.IWebMessageInfo, ticket: Ticket, contact: Contact ): Promise => { - const quotedMsg = await verifyQuotedMessage(msg); - const body = getBodyMessage(msg); + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } const messageData = { id: msg.key.id, ticketId: ticket.id, - contactId: msg.key.fromMe ? undefined : contact.id, - body, + contactId: contact.id, + body: getBodyMessage(msg), fromMe: msg.key.fromMe, - mediaType: getTypeMessage(msg), read: msg.key.fromMe, - quotedMsgId: quotedMsg?.id, - ack: msg.status, - remoteJid: msg.key.remoteJid, - participant: msg.key.participant, - dataJson: JSON.stringify(msg) + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, }; - await ticket.update({ - lastMessage: body + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, }); - return CreateMessageService({ messageData }); + return newMessage; }; +// ========== FUNCIÓN isValidMsg ========== const isValidMsg = (msg: proto.IWebMessageInfo): boolean => { if (msg.key.remoteJid === "status@broadcast") return false; - try { - const msgType = getTypeMessage(msg); - if (!msgType) { - return; - } + + const msgStubType = msg.messageStubType; + const isStubMessage = [ + WAMessageStubType.REVOKE, + WAMessageStubType.E2E_DEVICE_CHANGED, + ].includes(msgStubType as number); + + return !isStubMessage; +}; - const ifType = - msgType === "conversation" || - msgType === "extendedTextMessage" || - msgType === "audioMessage" || - msgType === "videoMessage" || - msgType === "imageMessage" || - msgType === "documentMessage" || - msgType === "stickerMessage" || - msgType === "buttonsResponseMessage" || - msgType === "buttonsMessage" || - msgType === "messageContextInfo" || - msgType === "locationMessage" || - msgType === "liveLocationMessage" || - msgType === "contactMessage" || - msgType === "voiceMessage" || - msgType === "mediaMessage" || - msgType === "contactsArrayMessage" || - msgType === "reactionMessage" || - msgType === "ephemeralMessage" || - msgType === "protocolMessage" || - msgType === "listResponseMessage" || - msgType === "listMessage" || - msgType === "viewOnceMessage"; - - if (!ifType) { - logger.warn(`#### Nao achou o type em isValidMsg: ${msgType} -${JSON.stringify(msg?.message)}`); - Sentry.setExtra("Mensagem", { BodyMsg: msg.message, msg, msgType }); - Sentry.captureException(new Error("Novo Tipo de Mensagem em isValidMsg")); - } - return !!ifType; - } catch (error) { - Sentry.setExtra("Error isValidMsg", { msg }); - Sentry.captureException(error); - } -}; const verifyQueue = async ( wbot: Session, msg: proto.IWebMessageInfo, ticket: Ticket, contact: Contact -) => { - const { queues, greetingMessage } = await ShowWhatsAppService(wbot.id!); - - if (queues.length === 1) { +): Promise => { + const { queues } = await ShowWhatsAppService(wbot.id!); + + if (queues.length === 0) { await UpdateTicketService({ - ticketData: { queueId: queues[0].id }, - ticketId: ticket.id + ticketId: ticket.id, + ticketData: { userId: 1 } // Asignar a un usuario por defecto }); - return; } - const selectedOption = - msg?.message?.buttonsResponseMessage?.selectedButtonId || - msg?.message?.listResponseMessage?.singleSelectReply.selectedRowId || - getBodyMessage(msg); - - const choosenQueue = queues[+selectedOption - 1]; - - const buttonActive = await Setting.findOne({ - where: { - key: "chatBotType" + // Lógica para asignar a una cola + // Aquí puedes implementar tu lógica de asignación + const selectedQueue = queues[0]; // Por ahora toma la primera cola + + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { + queueId: selectedQueue.id, + status: "pending" } }); +}; +// ========== FIN FUNCIONES FALTANTES ========== - const botText = async () => { - if (choosenQueue) { - await UpdateTicketService({ - ticketData: { queueId: choosenQueue.id }, - ticketId: ticket.id - }); - - if (choosenQueue.chatbots.length > 0) { - let options = ""; - choosenQueue.chatbots.forEach((chatbot, index) => { - options += `*${index + 1}* - ${chatbot.name}\n`; - }); - - const body = formatBody( - `\u200e${choosenQueue.greetingMessage}\n\n${options}\n*#* Voltar para o menu principal`, - contact - ); - const sentMessage = await wbot.sendMessage( - `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, - { - text: body - } - ); - - await verifyMessage(sentMessage, ticket, contact); - } +// ========== VERIFYCONTACT ACTUALIZADO ========== +const verifyContact = async (msgContact: IMe, wbot: Session): Promise => { + let profilePicUrl: string; + try { + profilePicUrl = await wbot.profilePictureUrl(msgContact.id); + } catch { + profilePicUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/nopicture.png`; + } - if (!choosenQueue.chatbots.length) { - const body = formatBody( - `\u200e${choosenQueue.greetingMessage}`, - contact - ); - const sentMessage = await wbot.sendMessage( - `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, - { - text: body - } - ); - - await verifyMessage(sentMessage, ticket, contact); + let phoneNumber = msgContact.number || msgContact.id.replace(/\D/g, ""); + +// ========== LOGGING ADICIONAL ========== +console.log('🔍🔍🔍 VERIFYCONTACT - DEBUG DETALLADO 🔍🔍🔍'); +console.log('msgContact.id:', msgContact.id); +console.log('msgContact.number:', msgContact.number); +console.log('msgContact.lid:', msgContact.lid); +console.log('phoneNumber inicial:', phoneNumber); +console.log('msgContact.id sin @s.whatsapp.net:', msgContact.id.replace('@s.whatsapp.net', '')); +console.log('msgContact.id solo números:', msgContact.id.replace(/\D/g, '')); +// ========== FIN LOGGING ========== + + console.log('👤 VERIFICANDO CONTACTO:'); + console.log(' ID recibido:', msgContact.id); + console.log(' Número en msgContact:', msgContact.number); + console.log(' Es LID?:', msgContact.esLid ? 'SÍ' : 'NO'); + console.log(' LID original:', msgContact.lid || 'N/A'); + console.log(' JID original:', msgContact.originalJid || 'N/A'); + + // Si tenemos un LID, intentar obtener el número real + if (msgContact.lid) { + try { + console.log('🔍 Contacto tiene LID, verificando número real:', msgContact.lid); + const results = await wbot.onWhatsApp(msgContact.lid); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ LID convertido a número en verifyContact:', phoneNumber); } - } else { - let options = ""; - - queues.forEach((queue, index) => { - options += `*${index + 1}* - ${queue.name}\n`; - }); - - const body = formatBody( - `\u200e${greetingMessage}\n\n${options}`, - contact - ); - - const debouncedSentMessage = debounce( - async () => { - const sentMessage = await wbot.sendMessage( - `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, - { - text: body - } - ); - - verifyMessage(sentMessage, ticket, contact); - }, - 3000, - ticket.id - ); - - debouncedSentMessage(); + } catch (error) { + console.error('❌ Error obteniendo número de LID:', error); } - }; - - const botButton = async () => { - if (choosenQueue) { - await UpdateTicketService({ - ticketData: { queueId: choosenQueue.id }, - ticketId: ticket.id - }); - - if (choosenQueue.chatbots.length > 0) { - const buttons = []; - choosenQueue.chatbots.forEach((queue, index) => { - buttons.push({ - buttonId: `${index + 1}`, - buttonText: { displayText: queue.name }, - type: 1 - }); - }); - - const buttonMessage = { - text: formatBody(`\u200e${choosenQueue.greetingMessage}`, contact), - buttons, - headerType: 4 - }; - - const sendMsg = await wbot.sendMessage( - `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, - buttonMessage - ); - - await verifyMessage(sendMsg, ticket, contact); - } - - if (!choosenQueue.chatbots.length) { - const body = formatBody( - `\u200e${choosenQueue.greetingMessage}`, - contact - ); - const sentMessage = await wbot.sendMessage( - `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, - { - text: body - } - ); - - await verifyMessage(sentMessage, ticket, contact); + } + + // Si el ID mismo es un LID pero no lo capturamos antes + if (!msgContact.lid && (msgContact.id.includes(':') || (msgContact.id.includes('.') && !msgContact.id.includes('@s.whatsapp.net')))) { + try { + console.log('🔍 ID parece LID, verificando:', msgContact.id); + const results = await wbot.onWhatsApp(msgContact.id); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ ID LID convertido a número:', phoneNumber); } - } else { - const buttons = []; - queues.forEach((queue, index) => { - buttons.push({ - buttonId: `${index + 1}`, - buttonText: { displayText: queue.name }, - type: 4 - }); - }); - - const buttonMessage = { - text: formatBody(`\u200e${greetingMessage}`, contact), - buttons, - headerType: 4 - }; - - const sendMsg = await wbot.sendMessage( - `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, - buttonMessage - ); - - await verifyMessage(sendMsg, ticket, contact); + } catch (error) { + console.error('❌ Error verificando ID LID:', error); } - }; - - const botList = async () => { - if (choosenQueue) { - await UpdateTicketService({ - ticketData: { queueId: choosenQueue.id }, - ticketId: ticket.id - }); - - if (choosenQueue.chatbots.length > 0) { - const sectionsRows = []; - choosenQueue.chatbots.forEach((queue, index) => { - sectionsRows.push({ - title: queue.name, - rowId: `${index + 1}` - }); - }); - - const sections = [ - { - title: "Menu", - rows: sectionsRows - } - ]; - - const listMessage = { - text: formatBody(`\u200e${choosenQueue.greetingMessage}`, contact), - buttonText: "Escolha uma opção", - sections - }; - - const sendMsg = await wbot.sendMessage( - `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, - listMessage - ); - - await verifyMessage(sendMsg, ticket, contact); - } - - if (!choosenQueue.chatbots.length) { - const body = formatBody( - `\u200e${choosenQueue.greetingMessage}`, - contact - ); - - const sentMessage = await wbot.sendMessage( - `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, - { - text: body - } - ); - - await verifyMessage(sentMessage, ticket, contact); - } - } else { - const sectionsRows = []; - - queues.forEach((queue, index) => { - sectionsRows.push({ - title: queue.name, - rowId: `${index + 1}` - }); - }); - - const sections = [ - { - title: "Menu", - rows: sectionsRows - } - ]; - - const listMessage = { - text: formatBody(`\u200e${greetingMessage}`, contact), - buttonText: "Escolha uma opção", - sections - }; + } - const sendMsg = await wbot.sendMessage( - `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, - listMessage - ); + // OBTENER EL companyId DEL WHATSAPP O USAR UN VALOR POR DEFECTO + // Esto depende de cómo tengas configurada tu aplicación + const companyId = wbot.id ? 1 : 1; // Ajusta esta lógica según tu aplicación - await verifyMessage(sendMsg, ticket, contact); - } + const contactData = { + name: msgContact?.name || phoneNumber, + number: phoneNumber, + profilePicUrl, + isGroup: msgContact.id.includes("g.us"), + companyId: companyId, // ← AÑADIDO + lid: msgContact.lid || null, + originalJid: msgContact.originalJid || null, + whatsappId: wbot.id }; - if (buttonActive.value === "text") { - return botText(); - } - - if (buttonActive.value === "button" && queues.length > 4) { - return botText(); - } - - if (buttonActive.value === "button" && queues.length <= 4) { - return botButton(); - } + console.log('📝 DATOS FINALES DEL CONTACTO:'); + console.log(' Nombre:', contactData.name); + console.log(' Número:', contactData.number); + console.log(' LID guardado:', contactData.lid); + console.log(' Company ID:', contactData.companyId); - if (buttonActive.value === "list") { - return botList(); - } + const contact = await CreateOrUpdateContactService(contactData); + return contact; }; +// ========== FIN VERIFYCONTACT ACTUALIZADO ========== const handleMessage = async ( msg: proto.IWebMessageInfo, wbot: Session ): Promise => { if (!isValidMsg(msg)) return; + try { let msgContact: IMe; let groupContact: Contact | undefined; - const isGroup = msg.key.remoteJid?.endsWith("@g.us"); + const isGroup = msg.key.remoteJid?.endsWith("@g.us") || false; const msgIsGroupBlock = await Setting.findOne({ where: { key: "CheckMsgIsGroup" } @@ -774,18 +684,18 @@ const handleMessage = async ( const bodyMessage = getBodyMessage(msg); const msgType = getTypeMessage(msg); - if (msgType === "protocolMessage") return; // Tratar isso no futuro para excluir msgs se vor REVOKE + if (msgType === "protocolMessage") return; + const hasMedia = msg.message?.audioMessage || msg.message?.imageMessage || msg.message?.videoMessage || msg.message?.documentMessage || - msg.message.stickerMessage || - msg.message?.extendedTextMessage?.contextInfo?.quotedMessage - ?.imageMessage; + msg.message?.stickerMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; if (msg.key.fromMe) { - if (/\u200e/.test(bodyMessage)) return; + if (/\u200e/.test(bodyMessage || "")) return; if ( !hasMedia && @@ -798,6 +708,7 @@ const handleMessage = async ( msgType !== "viewOnceMessage" ) return; + msgContact = await getContactMessage(msg, wbot); } else { msgContact = await getContactMessage(msg, wbot); @@ -806,29 +717,53 @@ const handleMessage = async ( if (msgIsGroupBlock?.value === "enabled" && isGroup) return; if (isGroup) { - const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid); - const msgGroupContact = { - id: grupoMeta.id, - name: grupoMeta.subject - }; - groupContact = await verifyContact(msgGroupContact, wbot); + try { + const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid!); + const msgGroupContact = { + id: grupoMeta.id, + name: grupoMeta.subject || "" + }; + groupContact = await verifyContact(msgGroupContact, wbot); + } catch (error) { + console.error('Error obteniendo metadatos del grupo:', error); + } } + const whatsapp = await ShowWhatsAppService(wbot.id!); - const count = wbot.store.chats.get( - msg.key.remoteJid || msg.key.participant - ); + // Obtener conteo de mensajes no leídos + const count = wbot.store && wbot.store.chats ? + wbot.store.chats.get(msg.key.remoteJid || msg.key.participant) : + undefined; const unreadMessages = msg.key.fromMe ? 0 : count?.unreadCount || 1; const contact = await verifyContact(msgContact, wbot); - if ( - unreadMessages === 0 && - whatsapp.farewellMessage && - formatBody(whatsapp.farewellMessage, contact) === bodyMessage - ) - return; + // ========== DIAGNÓSTICO COMPLETO ========== + console.log('\n📊 DIAGNÓSTICO COMPLETO DEL MENSAJE:'); + console.log('======================================'); + console.log('📱 DATOS CRUDOS:'); + console.log(' RemoteJid:', msg.key.remoteJid); + console.log(' Participant:', msg.key.participant); + console.log(' FromMe:', msg.key.fromMe); + console.log(' PushName:', msg.pushName); + console.log(' Tipo de mensaje:', msgType); + + console.log('\n🔍 ANÁLISIS LID:'); + console.log(' JID analizado:', msg.key.remoteJid); + if (msg.key.remoteJid?.includes(':')) { + const partes = msg.key.remoteJid.split(':'); + console.log(' Parte 1 (posible número):', partes[0]); + console.log(' Parte 2 (identificador):', partes[1]); + } + + console.log('\n👤 DATOS DEL CONTACTO:'); + console.log(' Número en contacto BD:', contact.number); + console.log(' Contact ID:', contact.id); + console.log(' Nombre:', contact.name); + console.log(' LID guardado:', contact.lid); + console.log('======================================\n'); const ticket = await FindOrCreateTicketService({ contact, @@ -887,90 +822,56 @@ const handleMessage = async ( await verifyMessage(sentMessage, ticket, contact); } } catch (err) { - console.log(err); + console.error('❌ Error en handleMessage:', err); Sentry.captureException(err); logger.error(`Error handling whatsapp message: Err: ${err}`); } }; -const handleMsgAck = async ( - msg: WAMessage, - chat: number | null | undefined -) => { - await new Promise(r => setTimeout(r, 500)); - const io = getIO(); - try { - const messageToUpdate = await Message.findByPk(msg.key.id, { - include: [ - "contact", - { - model: Message, - as: "quotedMsg", - include: ["contact"] - } - ] - }); +const wbotMessageListener = (wbot: Session): void => { + wbot.ev.on("messages.upsert", async (messageUpsert: ImessageUpsert) => { + const messages = messageUpsert.messages; - if (!messageToUpdate) return; - await messageToUpdate.update({ ack: chat }); - io.to(messageToUpdate.ticketId.toString()).emit("appMessage", { - action: "update", - message: messageToUpdate - }); - } catch (err) { - Sentry.captureException(err); - logger.error(`Error handling message ack. Err: ${err}`); - } -}; + if (messages.length === 0) return; -const filterMessages = (msg: WAMessage): boolean => { - if (msg.message?.protocolMessage) return false; + const message = messages[0]; - if ( - [ - WAMessageStubType.REVOKE, - WAMessageStubType.E2E_DEVICE_CHANGED, - WAMessageStubType.E2E_IDENTITY_CHANGED, - WAMessageStubType.CIPHERTEXT - ].includes(msg.messageStubType as WAMessageStubType) - ) - return false; + if (message.key.id.startsWith("BAE5") && message.key.fromMe) return; - return true; -}; + handleMessage(message, wbot); + }); -const wbotMessageListener = async (wbot: Session): Promise => { - try { - wbot.ev.on("messages.upsert", async (messageUpsert: ImessageUpsert) => { - const messages = messageUpsert.messages - .filter(filterMessages) - .map(msg => msg); - - if (!messages) return; - - messages.forEach(async (message: proto.IWebMessageInfo) => { - if ( - wbot.type === "md" && - !message.key.fromMe && - messageUpsert.type === "notify" - ) { - (wbot as WASocket)!.readMessages([message.key]); - } - // console.log(JSON.stringify(message)); - handleMessage(message, wbot); - }); - }); + wbot.ev.on("messages.update", (messageUpdate: WAMessageUpdate[]) => { + if (messageUpdate.length === 0) return; - wbot.ev.on("messages.update", (messageUpdate: WAMessageUpdate[]) => { - if (messageUpdate.length === 0) return; - messageUpdate.forEach(async (message: WAMessageUpdate) => { - handleMsgAck(message, message.update.status); - }); + messageUpdate.forEach(async (update) => { + if (!update.key?.id || !update.update) return; + + const message = await Message.findOne({ where: { id: update.key.id } }); + if (!message) return; + + if (update.update?.status) { + await message.update({ status: update.update.status }); + const io = getIO(); + io.to(message.ticketId.toString()).emit("appMessage", { + action: "update", + message + }); + } }); - } catch (error) { - Sentry.captureException(error); - logger.error(`Error handling wbot message listener. Err: ${error}`); - } + }); }; -export { wbotMessageListener, handleMessage }; + +export { + wbotMessageListener, + handleMessage, + verifyMessage, + getBodyMessage, + verifyMediaMessage, + isValidMsg, // ← ¡AGREGADA! + verifyQueue, + downloadMedia, // ← ¡AGREGAR TAMBIÉN ESTA! + getContactMessage, // ← Y ESTA SI NO ESTÁ + // Agrega cualquier otra función que se use externamente +}; \ No newline at end of file diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts.backup b/backend/src/services/WbotServices/wbotMessageListener.ts.backup new file mode 100644 index 0000000..cd5fa26 --- /dev/null +++ b/backend/src/services/WbotServices/wbotMessageListener.ts.backup @@ -0,0 +1,976 @@ +import { join } from "path"; +import { promisify } from "util"; +import { writeFile } from "fs"; +import * as Sentry from "@sentry/node"; + +import { + downloadContentFromMessage, + jidNormalizedUser, + MediaType, + MessageUpsertType, + proto, + WAMessage, + WAMessageUpdate, + WASocket, + getContentType, + extractMessageContent, + WAMessageStubType +} from "@whiskeysockets/baileys"; + +import Contact from "../../models/Contact"; +import Ticket from "../../models/Ticket"; +import Message from "../../models/Message"; + +import { getIO } from "../../libs/socket"; +import CreateMessageService from "../MessageServices/CreateMessageService"; +import { logger } from "../../utils/logger"; +import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService"; +import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; +import formatBody from "../../helpers/Mustache"; +import { Store } from "../../libs/store"; +import Setting from "../../models/Setting"; +import { debounce } from "../../helpers/Debounce"; +import UpdateTicketService from "../TicketServices/UpdateTicketService"; +import { sayChatbot } from "./ChatBotListener"; +import hourExpedient from "./hourExpedient"; + +type Session = WASocket & { + id?: number; + store?: Store; +}; + +interface ImessageUpsert { + messages: proto.IWebMessageInfo[]; + type: MessageUpsertType; +} + +interface IMe { + name: string; + id: string; +} + +const writeFileAsync = promisify(writeFile); + +const getTypeMessage = (msg: proto.IWebMessageInfo): string => { + return getContentType(msg.message); +}; + +const getBodyButton = (msg: proto.IWebMessageInfo): string => { + if (msg.key.fromMe && msg?.message?.buttonsMessage?.contentText) { + let bodyMessage = `*${msg?.message?.buttonsMessage?.contentText}*`; + // eslint-disable-next-line no-restricted-syntax + for (const buton of msg.message?.buttonsMessage?.buttons) { + bodyMessage += `\n\n${buton.buttonText?.displayText}`; + } + return bodyMessage; + } + + if (msg.key.fromMe && msg?.message?.listMessage) { + let bodyMessage = `*${msg?.message?.listMessage?.description}*`; + // eslint-disable-next-line no-restricted-syntax + for (const buton of msg.message?.listMessage?.sections) { + // eslint-disable-next-line no-restricted-syntax + for (const rows of buton.rows) { + bodyMessage += `\n\n${rows.title}`; + } + } + + return bodyMessage; + } + if (msg.key.fromMe && msg?.message?.viewOnceMessage?.message?.listMessage) { + let bodyMessage = `*${msg?.message?.viewOnceMessage?.message?.listMessage?.description}*`; + // eslint-disable-next-line no-restricted-syntax + for (const buton of msg?.message?.viewOnceMessage?.message?.listMessage + ?.sections) { + // eslint-disable-next-line no-restricted-syntax + for (const rows of buton.rows) { + bodyMessage += `\n\n${rows.title}`; + } + } + + return bodyMessage; + } + if ( + msg.key.fromMe && + msg?.message?.viewOnceMessage?.message?.buttonsMessage + ) { + let bodyMessage = `*${msg?.message?.viewOnceMessage?.message?.buttonsMessage?.contentText}*`; + // eslint-disable-next-line no-restricted-syntax + for (const buton of msg?.message?.viewOnceMessage?.message?.buttonsMessage + ?.buttons) { + bodyMessage += `\n\n${buton.buttonText?.displayText}`; + } + + return bodyMessage; + } +}; + +const msgLocation = (image, latitude, longitude) => { + if (image) { + const b64 = Buffer.from(image).toString("base64"); + + const data = `data:image/png;base64, ${b64} | https://maps.google.com/maps?q=${latitude}%2C${longitude}&z=17&hl=pt-BR|${latitude}, ${longitude} `; + return data; + } +}; + +export const getBodyMessage = (msg: proto.IWebMessageInfo): string | null => { + try { + const type = getTypeMessage(msg); + + const types = { + conversation: msg.message.conversation, + imageMessage: msg.message.imageMessage?.caption, + videoMessage: msg.message.videoMessage?.caption, + extendedTextMessage: msg.message.extendedTextMessage?.text, + buttonsResponseMessage: + msg.message.buttonsResponseMessage?.selectedDisplayText, + listResponseMessage: + msg.message.listResponseMessage?.title || + msg.message.listResponseMessage?.singleSelectReply?.selectedRowId, + templateButtonReplyMessage: + msg.message?.templateButtonReplyMessage?.selectedId, + messageContextInfo: + msg.message.buttonsResponseMessage?.selectedButtonId || + msg.message.listResponseMessage?.title, + buttonsMessage: + getBodyButton(msg) || msg.message.listResponseMessage?.title, + stickerMessage: "sticker", + contactMessage: msg.message?.contactMessage?.vcard, + contactsArrayMessage: "varios contatos", + // locationMessage: `Latitude: ${msg.message.locationMessage?.degreesLatitude} - Longitude: ${msg.message.locationMessage?.degreesLongitude}`, + locationMessage: msgLocation( + msg.message?.locationMessage?.jpegThumbnail, + msg.message?.locationMessage?.degreesLatitude, + msg.message?.locationMessage?.degreesLongitude + ), + liveLocationMessage: `Latitude: ${msg.message.liveLocationMessage?.degreesLatitude} - Longitude: ${msg.message.liveLocationMessage?.degreesLongitude}`, + documentMessage: msg.message.documentMessage?.title, + audioMessage: "Áudio", + listMessage: getBodyButton(msg) || msg.message.listResponseMessage?.title, + viewOnceMessage: getBodyButton(msg), + reactionMessage: msg.message.reactionMessage?.text || "reaction" + }; + + const objKey = Object.keys(types).find(key => key === type); + + if (!objKey) { + logger.warn(`#### Nao achou o type 152: ${type} +${JSON.stringify(msg)}`); + Sentry.setExtra("Mensagem", { BodyMsg: msg.message, msg, type }); + Sentry.captureException( + new Error("Novo Tipo de Mensagem em getTypeMessage") + ); + } + return types[type]; + } catch (error) { + Sentry.setExtra("Error getTypeMessage", { msg, BodyMsg: msg.message }); + Sentry.captureException(error); + console.log(error); + } +}; + +export const getQuotedMessage = (msg: proto.IWebMessageInfo) => { + const body = extractMessageContent(msg.message)[ + Object.keys(msg?.message).values().next().value + ]; + + if (!body?.contextInfo?.quotedMessage) return; + const quoted = extractMessageContent( + body?.contextInfo?.quotedMessage[ + Object.keys(body?.contextInfo?.quotedMessage).values().next().value + ] + ); + + return quoted; +}; + +export const getQuotedMessageId = (msg: proto.IWebMessageInfo) => { + const body = extractMessageContent(msg.message)[ + Object.keys(msg?.message).values().next().value + ]; + const reaction = msg?.message?.reactionMessage + ? msg?.message?.reactionMessage?.key?.id + : ""; + + return reaction || body?.contextInfo?.stanzaId; +}; + +const getMeSocket = (wbot: Session): IMe => { + return { + id: jidNormalizedUser((wbot as WASocket).user.id), + name: (wbot as WASocket).user.name + }; +}; + +const getSenderMessage = ( + msg: proto.IWebMessageInfo, + wbot: Session +): string => { + const me = getMeSocket(wbot); + if (msg.key.fromMe) return me.id; + + const senderId = + msg.participant || msg.key.participant || msg.key.remoteJid || undefined; + + return senderId && jidNormalizedUser(senderId); +}; + +const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session) => { + const isGroup = msg.key.remoteJid.includes("g.us"); + const rawNumber = msg.key.remoteJid.replace(/\D/g, ""); + return isGroup + ? { + id: getSenderMessage(msg, wbot), + name: msg.pushName + } + : { + id: msg.key.remoteJid, + name: msg.key.fromMe ? rawNumber : msg.pushName + }; +}; + +const downloadMedia = async (msg: proto.IWebMessageInfo) => { + const mineType = + msg.message?.imageMessage || + msg.message?.audioMessage || + msg.message?.videoMessage || + msg.message?.stickerMessage || + msg.message?.documentMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; + + // eslint-disable-next-line no-nested-ternary + const messageType = msg.message?.documentMessage + ? "document" + : mineType.mimetype.split("/")[0].replace("application", "document") + ? (mineType.mimetype + .split("/")[0] + .replace("application", "document") as MediaType) + : (mineType.mimetype.split("/")[0] as MediaType); + + let stream; + let contDownload = 0; + + while (contDownload < 10 && !stream) { + try { + // eslint-disable-next-line no-await-in-loop + stream = await downloadContentFromMessage( + msg.message.audioMessage || + msg.message.videoMessage || + msg.message.documentMessage || + msg.message.imageMessage || + msg.message.stickerMessage || + msg.message.extendedTextMessage?.contextInfo.quotedMessage + .imageMessage || + msg.message?.buttonsMessage?.imageMessage || + msg.message?.templateMessage?.fourRowTemplate?.imageMessage || + msg.message?.templateMessage?.hydratedTemplate?.imageMessage || + msg.message?.templateMessage?.hydratedFourRowTemplate?.imageMessage || + msg.message?.interactiveMessage?.header?.imageMessage, + messageType + ); + } catch (error) { + // eslint-disable-next-line no-plusplus + contDownload++; + // eslint-disable-next-line no-await-in-loop, no-loop-func + await new Promise(resolve => + setTimeout(resolve, 1000 * contDownload * 2) + ); + logger.warn( + `>>>> erro ${contDownload} de baixar o arquivo ${msg?.key.id}` + ); + } + } + + let buffer = Buffer.from([]); + // eslint-disable-next-line no-restricted-syntax + try { + // eslint-disable-next-line no-restricted-syntax + for await (const chunk of stream) { + buffer = Buffer.concat([buffer, chunk]); + } + } catch (error) { + return { data: "error", mimetype: "", filename: "" }; + } + + if (!buffer) { + Sentry.setExtra("ERR_WAPP_DOWNLOAD_MEDIA", { msg }); + Sentry.captureException(new Error("ERR_WAPP_DOWNLOAD_MEDIA")); + throw new Error("ERR_WAPP_DOWNLOAD_MEDIA"); + } + let filename = msg.message?.documentMessage?.fileName || ""; + + if (!filename) { + const ext = mineType.mimetype.split("/")[1].split(";")[0]; + filename = `${new Date().getTime()}.${ext}`; + } + const media = { + data: buffer, + mimetype: mineType.mimetype, + filename + }; + return media; +}; +const verifyContact = async ( + msgContact: IMe, + wbot: Session +): Promise => { + let profilePicUrl: string; + try { + profilePicUrl = await wbot.profilePictureUrl(msgContact.id); + } catch { + profilePicUrl = `${process.env.FRONTEND_URL}/nopicture.png`; + } + + const contactData = { + name: msgContact?.name || msgContact.id.replace(/\D/g, ""), + number: msgContact.id.replace(/\D/g, ""), + profilePicUrl, + isGroup: msgContact.id.includes("g.us") + }; + + const contact = CreateOrUpdateContactService(contactData); + + return contact; +}; + +const verifyQuotedMessage = async ( + msg: proto.IWebMessageInfo +): Promise => { + if (!msg) return null; + const quoted = getQuotedMessageId(msg); + + if (!quoted) return null; + + const quotedMsg = await Message.findOne({ + where: { id: quoted } + }); + + if (!quotedMsg) return null; + + return quotedMsg; +}; + +const verifyMediaMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const quotedMsg = await verifyQuotedMessage(msg); + + const media = await downloadMedia(msg); + + if (!media) { + throw new Error("ERR_WAPP_DOWNLOAD_MEDIA"); + } + + if (!media.filename) { + const ext = media.mimetype.split("/")[1].split(";")[0]; + media.filename = `${new Date().getTime()}.${ext}`; + } + + try { + await writeFileAsync( + join(__dirname, "..", "..", "..", "public", media.filename), + media.data, + "base64" + ); + } catch (err) { + Sentry.captureException(err); + logger.error(err); + } + + const body = getBodyMessage(msg); + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: msg.key.fromMe ? undefined : contact.id, + body: body || media.filename, + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: media.filename, + mediaType: media.mimetype.split("/")[0], + quotedMsgId: quotedMsg?.id, + ack: msg.status, + remoteJid: msg.key.remoteJid, + participant: msg.key.participant, + dataJson: JSON.stringify(msg) + }; + + await ticket.update({ + lastMessage: body || media.filename + }); + + const newMessage = await CreateMessageService({ messageData }); + + return newMessage; +}; + +export const verifyMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const quotedMsg = await verifyQuotedMessage(msg); + const body = getBodyMessage(msg); + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: msg.key.fromMe ? undefined : contact.id, + body, + fromMe: msg.key.fromMe, + mediaType: getTypeMessage(msg), + read: msg.key.fromMe, + quotedMsgId: quotedMsg?.id, + ack: msg.status, + remoteJid: msg.key.remoteJid, + participant: msg.key.participant, + dataJson: JSON.stringify(msg) + }; + + await ticket.update({ + lastMessage: body + }); + + return CreateMessageService({ messageData }); +}; + +const isValidMsg = (msg: proto.IWebMessageInfo): boolean => { + if (msg.key.remoteJid === "status@broadcast") return false; + try { + const msgType = getTypeMessage(msg); + if (!msgType) { + return; + } + + const ifType = + msgType === "conversation" || + msgType === "extendedTextMessage" || + msgType === "audioMessage" || + msgType === "videoMessage" || + msgType === "imageMessage" || + msgType === "documentMessage" || + msgType === "stickerMessage" || + msgType === "buttonsResponseMessage" || + msgType === "buttonsMessage" || + msgType === "messageContextInfo" || + msgType === "locationMessage" || + msgType === "liveLocationMessage" || + msgType === "contactMessage" || + msgType === "voiceMessage" || + msgType === "mediaMessage" || + msgType === "contactsArrayMessage" || + msgType === "reactionMessage" || + msgType === "ephemeralMessage" || + msgType === "protocolMessage" || + msgType === "listResponseMessage" || + msgType === "listMessage" || + msgType === "viewOnceMessage"; + + if (!ifType) { + logger.warn(`#### Nao achou o type em isValidMsg: ${msgType} +${JSON.stringify(msg?.message)}`); + Sentry.setExtra("Mensagem", { BodyMsg: msg.message, msg, msgType }); + Sentry.captureException(new Error("Novo Tipo de Mensagem em isValidMsg")); + } + + return !!ifType; + } catch (error) { + Sentry.setExtra("Error isValidMsg", { msg }); + Sentry.captureException(error); + } +}; + +const verifyQueue = async ( + wbot: Session, + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +) => { + const { queues, greetingMessage } = await ShowWhatsAppService(wbot.id!); + + if (queues.length === 1) { + await UpdateTicketService({ + ticketData: { queueId: queues[0].id }, + ticketId: ticket.id + }); + + return; + } + + const selectedOption = + msg?.message?.buttonsResponseMessage?.selectedButtonId || + msg?.message?.listResponseMessage?.singleSelectReply.selectedRowId || + getBodyMessage(msg); + + const choosenQueue = queues[+selectedOption - 1]; + + const buttonActive = await Setting.findOne({ + where: { + key: "chatBotType" + } + }); + + const botText = async () => { + if (choosenQueue) { + await UpdateTicketService({ + ticketData: { queueId: choosenQueue.id }, + ticketId: ticket.id + }); + + if (choosenQueue.chatbots.length > 0) { + let options = ""; + choosenQueue.chatbots.forEach((chatbot, index) => { + options += `*${index + 1}* - ${chatbot.name}\n`; + }); + + const body = formatBody( + `\u200e${choosenQueue.greetingMessage}\n\n${options}\n*#* Voltar para o menu principal`, + contact + ); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + + if (!choosenQueue.chatbots.length) { + const body = formatBody( + `\u200e${choosenQueue.greetingMessage}`, + contact + ); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } else { + let options = ""; + + queues.forEach((queue, index) => { + options += `*${index + 1}* - ${queue.name}\n`; + }); + + const body = formatBody( + `\u200e${greetingMessage}\n\n${options}`, + contact + ); + + const debouncedSentMessage = debounce( + async () => { + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + verifyMessage(sentMessage, ticket, contact); + }, + 3000, + ticket.id + ); + + debouncedSentMessage(); + } + }; + + const botButton = async () => { + if (choosenQueue) { + await UpdateTicketService({ + ticketData: { queueId: choosenQueue.id }, + ticketId: ticket.id + }); + + if (choosenQueue.chatbots.length > 0) { + const buttons = []; + choosenQueue.chatbots.forEach((queue, index) => { + buttons.push({ + buttonId: `${index + 1}`, + buttonText: { displayText: queue.name }, + type: 1 + }); + }); + + const buttonMessage = { + text: formatBody(`\u200e${choosenQueue.greetingMessage}`, contact), + buttons, + headerType: 4 + }; + + const sendMsg = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + buttonMessage + ); + + await verifyMessage(sendMsg, ticket, contact); + } + + if (!choosenQueue.chatbots.length) { + const body = formatBody( + `\u200e${choosenQueue.greetingMessage}`, + contact + ); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } else { + const buttons = []; + queues.forEach((queue, index) => { + buttons.push({ + buttonId: `${index + 1}`, + buttonText: { displayText: queue.name }, + type: 4 + }); + }); + + const buttonMessage = { + text: formatBody(`\u200e${greetingMessage}`, contact), + buttons, + headerType: 4 + }; + + const sendMsg = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + buttonMessage + ); + + await verifyMessage(sendMsg, ticket, contact); + } + }; + + const botList = async () => { + if (choosenQueue) { + await UpdateTicketService({ + ticketData: { queueId: choosenQueue.id }, + ticketId: ticket.id + }); + + if (choosenQueue.chatbots.length > 0) { + const sectionsRows = []; + choosenQueue.chatbots.forEach((queue, index) => { + sectionsRows.push({ + title: queue.name, + rowId: `${index + 1}` + }); + }); + + const sections = [ + { + title: "Menu", + rows: sectionsRows + } + ]; + + const listMessage = { + text: formatBody(`\u200e${choosenQueue.greetingMessage}`, contact), + buttonText: "Escolha uma opção", + sections + }; + + const sendMsg = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + listMessage + ); + + await verifyMessage(sendMsg, ticket, contact); + } + + if (!choosenQueue.chatbots.length) { + const body = formatBody( + `\u200e${choosenQueue.greetingMessage}`, + contact + ); + + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } else { + const sectionsRows = []; + + queues.forEach((queue, index) => { + sectionsRows.push({ + title: queue.name, + rowId: `${index + 1}` + }); + }); + + const sections = [ + { + title: "Menu", + rows: sectionsRows + } + ]; + + const listMessage = { + text: formatBody(`\u200e${greetingMessage}`, contact), + buttonText: "Escolha uma opção", + sections + }; + + const sendMsg = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + listMessage + ); + + await verifyMessage(sendMsg, ticket, contact); + } + }; + + if (buttonActive.value === "text") { + return botText(); + } + + if (buttonActive.value === "button" && queues.length > 4) { + return botText(); + } + + if (buttonActive.value === "button" && queues.length <= 4) { + return botButton(); + } + + if (buttonActive.value === "list") { + return botList(); + } +}; + +const handleMessage = async ( + msg: proto.IWebMessageInfo, + wbot: Session +): Promise => { + if (!isValidMsg(msg)) return; + try { + let msgContact: IMe; + let groupContact: Contact | undefined; + + const isGroup = msg.key.remoteJid?.endsWith("@g.us"); + + const msgIsGroupBlock = await Setting.findOne({ + where: { key: "CheckMsgIsGroup" } + }); + + const bodyMessage = getBodyMessage(msg); + const msgType = getTypeMessage(msg); + + if (msgType === "protocolMessage") return; // Tratar isso no futuro para excluir msgs se vor REVOKE + const hasMedia = + msg.message?.audioMessage || + msg.message?.imageMessage || + msg.message?.videoMessage || + msg.message?.documentMessage || + msg.message.stickerMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage + ?.imageMessage; + + if (msg.key.fromMe) { + if (/\u200e/.test(bodyMessage)) return; + + if ( + !hasMedia && + msgType !== "conversation" && + msgType !== "extendedTextMessage" && + msgType !== "vcard" && + msgType !== "reactionMessage" && + msgType !== "ephemeralMessage" && + msgType !== "protocolMessage" && + msgType !== "viewOnceMessage" + ) + return; + msgContact = await getContactMessage(msg, wbot); + } else { + msgContact = await getContactMessage(msg, wbot); + } + + if (msgIsGroupBlock?.value === "enabled" && isGroup) return; + + if (isGroup) { + const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid); + const msgGroupContact = { + id: grupoMeta.id, + name: grupoMeta.subject + }; + groupContact = await verifyContact(msgGroupContact, wbot); + } + const whatsapp = await ShowWhatsAppService(wbot.id!); + + const count = wbot.store.chats.get( + msg.key.remoteJid || msg.key.participant + ); + + const unreadMessages = msg.key.fromMe ? 0 : count?.unreadCount || 1; + + const contact = await verifyContact(msgContact, wbot); + + if ( + unreadMessages === 0 && + whatsapp.farewellMessage && + formatBody(whatsapp.farewellMessage, contact) === bodyMessage + ) + return; + + const ticket = await FindOrCreateTicketService({ + contact, + whatsappId: wbot.id!, + unreadMessages, + groupContact, + channel: "whatsapp" + }); + + if (hasMedia) { + await verifyMediaMessage(msg, ticket, contact); + } else { + await verifyMessage(msg, ticket, contact); + } + + const checkExpedient = await hourExpedient(); + if (checkExpedient) { + if ( + !ticket.queue && + !isGroup && + !msg.key.fromMe && + !ticket.userId && + whatsapp.queues.length >= 1 + ) { + await verifyQueue(wbot, msg, ticket, contact); + } + + if (ticket.queue && ticket.queueId) { + if (!ticket.user) { + await sayChatbot(ticket.queueId, wbot, ticket, contact, msg); + } + } + } else { + const getLastMessageFromMe = await Message.findOne({ + where: { + ticketId: ticket.id, + fromMe: true + }, + order: [["createdAt", "DESC"]] + }); + + if ( + getLastMessageFromMe?.body === + formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact) + ) + return; + + const body = formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } catch (err) { + console.log(err); + Sentry.captureException(err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } +}; + +const handleMsgAck = async ( + msg: WAMessage, + chat: number | null | undefined +) => { + await new Promise(r => setTimeout(r, 500)); + const io = getIO(); + try { + const messageToUpdate = await Message.findByPk(msg.key.id, { + include: [ + "contact", + { + model: Message, + as: "quotedMsg", + include: ["contact"] + } + ] + }); + + if (!messageToUpdate) return; + await messageToUpdate.update({ ack: chat }); + io.to(messageToUpdate.ticketId.toString()).emit("appMessage", { + action: "update", + message: messageToUpdate + }); + } catch (err) { + Sentry.captureException(err); + logger.error(`Error handling message ack. Err: ${err}`); + } +}; + +const filterMessages = (msg: WAMessage): boolean => { + if (msg.message?.protocolMessage) return false; + + if ( + [ + WAMessageStubType.REVOKE, + WAMessageStubType.E2E_DEVICE_CHANGED, + WAMessageStubType.E2E_IDENTITY_CHANGED, + WAMessageStubType.CIPHERTEXT + ].includes(msg.messageStubType) + ) + return false; + + return true; +}; + +const wbotMessageListener = async (wbot: Session): Promise => { + try { + wbot.ev.on("messages.upsert", async (messageUpsert: ImessageUpsert) => { + const messages = messageUpsert.messages + .filter(filterMessages) + .map(msg => msg); + + if (!messages) return; + + messages.forEach(async (message: proto.IWebMessageInfo) => { + if ( + wbot.type === "md" && + !message.key.fromMe && + messageUpsert.type === "notify" + ) { + (wbot as WASocket)!.readMessages([message.key]); + } + // console.log(JSON.stringify(message)); + handleMessage(message, wbot); + }); + }); + + wbot.ev.on("messages.update", (messageUpdate: WAMessageUpdate[]) => { + if (messageUpdate.length === 0) return; + messageUpdate.forEach(async (message: WAMessageUpdate) => { + handleMsgAck(message, message.update.status); + }); + }); + } catch (error) { + Sentry.captureException(error); + logger.error(`Error handling wbot message listener. Err: ${error}`); + } +}; + +export { wbotMessageListener, handleMessage }; diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts.backup1 b/backend/src/services/WbotServices/wbotMessageListener.ts.backup1 new file mode 100644 index 0000000..d7ca9a3 --- /dev/null +++ b/backend/src/services/WbotServices/wbotMessageListener.ts.backup1 @@ -0,0 +1,423 @@ +import { join } from "path"; +import { promisify } from "util"; +import { writeFile } from "fs"; +import * as Sentry from "@sentry/node"; + +import { + downloadContentFromMessage, + jidNormalizedUser, + MediaType, + MessageUpsertType, + proto, + WAMessage, + WAMessageUpdate, + WASocket, + getContentType, + extractMessageContent, + WAMessageStubType, + AnyMessageContent +} from "@whiskeysockets/baileys"; + +import Contact from "../../models/Contact"; +import Ticket from "../../models/Ticket"; +import Message from "../../models/Message"; + +import { getIO } from "../../libs/socket"; +import CreateMessageService from "../MessageServices/CreateMessageService"; +import { logger } from "../../utils/logger"; +import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService"; +import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; +import formatBody from "../../helpers/Mustache"; +import { Store } from "../../libs/store"; +import Setting from "../../models/Setting"; +import { debounce } from "../../helpers/Debounce"; +import UpdateTicketService from "../TicketServices/UpdateTicketService"; +import { sayChatbot } from "./ChatBotListener"; +import hourExpedient from "./hourExpedient"; +import { NormalizadorMejorado } from "../../utils/NormalizadorMejorado"; // IMPORTAR EL NUEVO NORMALIZADOR + +type Session = WASocket & { + id?: number; + store?: Store; +}; + +interface ImessageUpsert { + messages: proto.IWebMessageInfo[]; + type: MessageUpsertType; +} + +interface IMe { + name: string; + id: string; + lid?: string; + esLid?: boolean; + number?: string; + originalJid?: string; // NUEVO: Guardar el JID original +} + +const writeFileAsync = promisify(writeFile); + +// Instancia global del normalizador +const normalizador = new NormalizadorMejorado(); + +// ========== FUNCIÓN MEJORADA PARA OBTENER NÚMERO REAL ========== +async function obtenerNumeroRealDeMensaje(wbot: Session, message: proto.IWebMessageInfo): Promise<{ + numeroReal: string; + jidReal: string; + esLid: boolean; + lidOriginal?: string; +}> { + let jid = message.key.remoteJid || ''; + + if (message.key.participant) { + jid = message.key.participant; + } + + if (!jid) { + return { + numeroReal: '', + jidReal: '', + esLid: false + }; + } + + console.log('🔍 ANALIZANDO JID:', jid); + console.log(' Tipo:', jid.includes(':') ? 'LID' : jid.includes('@s.whatsapp.net') ? 'Tradicional' : 'Desconocido'); + + // Usar el normalizador para obtener el número real + try { + const numeroReal = await normalizador.obtenerNumeroReal(wbot, jid); + const esLid = jid.includes(':') || (jid.includes('.') && !jid.includes('@s.whatsapp.net')); + + console.log('✅ RESULTADO:'); + console.log(' JID original:', jid); + console.log(' Número real obtenido:', numeroReal); + console.log(' Es LID?:', esLid ? 'SÍ' : 'NO'); + + return { + numeroReal, + jidReal: numeroReal + '@s.whatsapp.net', + esLid, + lidOriginal: esLid ? jid : undefined + }; + + } catch (error) { + console.error('❌ Error obteniendo número real:', error); + + // Fallback: intentar extraer número manualmente + let numeroFallback = jid.split('@')[0]; + if (jid.includes(':')) { + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + numeroFallback = match[1]; + } + } + + return { + numeroReal: numeroFallback, + jidReal: numeroFallback + '@s.whatsapp.net', + esLid: false + }; + } +} +// ========== FIN FUNCIÓN MEJORADA ========== + +const getTypeMessage = (msg: proto.IWebMessageInfo): string => { + return getContentType(msg.message); +}; + +// ... (mantén las funciones getBodyButton, msgLocation, getBodyMessage igual que antes) ... + +const getMeSocket = (wbot: Session): IMe => { + return { + id: jidNormalizedUser((wbot as WASocket).user.id), + name: (wbot as WASocket).user.name || "" + }; +}; + +const getSenderMessage = (msg: proto.IWebMessageInfo, wbot: Session): string => { + const me = getMeSocket(wbot); + if (msg.key.fromMe) return me.id; + + const senderId = msg.participant || msg.key.participant || msg.key.remoteJid || undefined; + return senderId ? jidNormalizedUser(senderId) : ""; +}; + +// ========== FUNCIÓN COMPLETAMENTE REESCRITA PARA MANEJAR LIDs ========== +const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session): Promise => { + const isGroup = msg.key.remoteJid?.includes("g.us") || false; + + if (isGroup) { + return { + id: getSenderMessage(msg, wbot), + name: msg.pushName || "" + }; + } + + // Para mensajes individuales, usar nuestro normalizador + try { + const { numeroReal, jidReal, esLid, lidOriginal } = await obtenerNumeroRealDeMensaje(wbot, msg); + + if (!numeroReal) { + throw new Error('No se pudo obtener número real'); + } + + return { + id: jidReal, + name: msg.key.fromMe ? numeroReal : msg.pushName || numeroReal, + number: numeroReal, + lid: lidOriginal, + esLid, + originalJid: msg.key.remoteJid || '' // Guardar el JID original + }; + + } catch (error) { + console.error('❌ Error en getContactMessage:', error); + + // Fallback extremo + const rawNumber = msg.key.remoteJid?.replace(/\D/g, "") || ""; + return { + id: msg.key.remoteJid || "", + name: msg.key.fromMe ? rawNumber : msg.pushName || rawNumber, + number: rawNumber + }; + } +}; +// ========== FIN FUNCIÓN REESCRITA ========== + +// ... (mantén las funciones downloadMedia, verifyContact, verifyQuotedMessage, etc.) ... + +// ========== VERIFYCONTACT ACTUALIZADO ========== +const verifyContact = async (msgContact: IMe, wbot: Session): Promise => { + let profilePicUrl: string; + try { + profilePicUrl = await wbot.profilePictureUrl(msgContact.id); + } catch { + profilePicUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/nopicture.png`; + } + + let phoneNumber = msgContact.number || msgContact.id.replace(/\D/g, ""); + + console.log('👤 VERIFICANDO CONTACTO:'); + console.log(' ID recibido:', msgContact.id); + console.log(' Número en msgContact:', msgContact.number); + console.log(' Es LID?:', msgContact.esLid ? 'SÍ' : 'NO'); + console.log(' LID original:', msgContact.lid || 'N/A'); + console.log(' JID original:', msgContact.originalJid || 'N/A'); + + // Si tenemos un LID, intentar obtener el número real + if (msgContact.lid) { + try { + console.log('🔍 Contacto tiene LID, verificando número real:', msgContact.lid); + const results = await wbot.onWhatsApp(msgContact.lid); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ LID convertido a número en verifyContact:', phoneNumber); + } + } catch (error) { + console.error('❌ Error obteniendo número de LID:', error); + } + } + + // Si el ID mismo es un LID pero no lo capturamos antes + if (!msgContact.lid && (msgContact.id.includes(':') || (msgContact.id.includes('.') && !msgContact.id.includes('@s.whatsapp.net')))) { + try { + console.log('🔍 ID parece LID, verificando:', msgContact.id); + const results = await wbot.onWhatsApp(msgContact.id); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ ID LID convertido a número:', phoneNumber); + } + } catch (error) { + console.error('❌ Error verificando ID LID:', error); + } + } + + const contactData = { + name: msgContact?.name || phoneNumber, + number: phoneNumber, + profilePicUrl, + isGroup: msgContact.id.includes("g.us"), + lid: msgContact.lid || null, + originalJid: msgContact.originalJid || null + }; + + console.log('📝 DATOS FINALES DEL CONTACTO:'); + console.log(' Nombre:', contactData.name); + console.log(' Número:', contactData.number); + console.log(' LID guardado:', contactData.lid); + + const contact = CreateOrUpdateContactService(contactData); + return contact; +}; +// ========== FIN VERIFYCONTACT ACTUALIZADO ========== + +// ... (mantén el resto de las funciones igual) ... + +const handleMessage = async ( + msg: proto.IWebMessageInfo, + wbot: Session +): Promise => { + if (!isValidMsg(msg)) return; + + try { + let msgContact: IMe; + let groupContact: Contact | undefined; + + const isGroup = msg.key.remoteJid?.endsWith("@g.us") || false; + + const msgIsGroupBlock = await Setting.findOne({ + where: { key: "CheckMsgIsGroup" } + }); + + const bodyMessage = getBodyMessage(msg); + const msgType = getTypeMessage(msg); + + if (msgType === "protocolMessage") return; + + const hasMedia = + msg.message?.audioMessage || + msg.message?.imageMessage || + msg.message?.videoMessage || + msg.message?.documentMessage || + msg.message?.stickerMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; + + if (msg.key.fromMe) { + if (/\u200e/.test(bodyMessage || "")) return; + + if ( + !hasMedia && + msgType !== "conversation" && + msgType !== "extendedTextMessage" && + msgType !== "vcard" && + msgType !== "reactionMessage" && + msgType !== "ephemeralMessage" && + msgType !== "protocolMessage" && + msgType !== "viewOnceMessage" + ) + return; + + msgContact = await getContactMessage(msg, wbot); + } else { + msgContact = await getContactMessage(msg, wbot); + } + + if (msgIsGroupBlock?.value === "enabled" && isGroup) return; + + if (isGroup) { + try { + const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid!); + const msgGroupContact = { + id: grupoMeta.id, + name: grupoMeta.subject || "" + }; + groupContact = await verifyContact(msgGroupContact, wbot); + } catch (error) { + console.error('Error obteniendo metadatos del grupo:', error); + } + } + + const whatsapp = await ShowWhatsAppService(wbot.id!); + + // Obtener conteo de mensajes no leídos + const count = wbot.store && wbot.store.chats ? + wbot.store.chats.get(msg.key.remoteJid || msg.key.participant) : + undefined; + + const unreadMessages = msg.key.fromMe ? 0 : count?.unreadCount || 1; + + const contact = await verifyContact(msgContact, wbot); + + // ========== DIAGNÓSTICO COMPLETO ========== + console.log('\n📊 DIAGNÓSTICO COMPLETO DEL MENSAJE:'); + console.log('======================================'); + console.log('📱 DATOS CRUDOS:'); + console.log(' RemoteJid:', msg.key.remoteJid); + console.log(' Participant:', msg.key.participant); + console.log(' FromMe:', msg.key.fromMe); + console.log(' PushName:', msg.pushName); + console.log(' Tipo de mensaje:', msgType); + + console.log('\n🔍 ANÁLISIS LID:'); + console.log(' JID analizado:', msg.key.remoteJid); + if (msg.key.remoteJid?.includes(':')) { + const partes = msg.key.remoteJid.split(':'); + console.log(' Parte 1 (posible número):', partes[0]); + console.log(' Parte 2 (identificador):', partes[1]); + } + + console.log('\n👤 DATOS DEL CONTACTO:'); + console.log(' Número en contacto BD:', contact.number); + console.log(' Contact ID:', contact.id); + console.log(' Nombre:', contact.name); + console.log(' LID guardado:', contact.lid); + console.log('======================================\n'); + + const ticket = await FindOrCreateTicketService({ + contact, + whatsappId: wbot.id!, + unreadMessages, + groupContact, + channel: "whatsapp" + }); + + if (hasMedia) { + await verifyMediaMessage(msg, ticket, contact); + } else { + await verifyMessage(msg, ticket, contact); + } + + const checkExpedient = await hourExpedient(); + if (checkExpedient) { + if ( + !ticket.queue && + !isGroup && + !msg.key.fromMe && + !ticket.userId && + whatsapp.queues.length >= 1 + ) { + await verifyQueue(wbot, msg, ticket, contact); + } + + if (ticket.queue && ticket.queueId) { + if (!ticket.user) { + await sayChatbot(ticket.queueId, wbot, ticket, contact, msg); + } + } + } else { + const getLastMessageFromMe = await Message.findOne({ + where: { + ticketId: ticket.id, + fromMe: true + }, + order: [["createdAt", "DESC"]] + }); + + if ( + getLastMessageFromMe?.body === + formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact) + ) + return; + + const body = formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } catch (err) { + console.error('❌ Error en handleMessage:', err); + Sentry.captureException(err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } +}; + +// ... (mantén el resto del archivo igual) ... + +export { wbotMessageListener, handleMessage }; \ No newline at end of file diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts.backup2 b/backend/src/services/WbotServices/wbotMessageListener.ts.backup2 new file mode 100644 index 0000000..a0230c5 --- /dev/null +++ b/backend/src/services/WbotServices/wbotMessageListener.ts.backup2 @@ -0,0 +1,755 @@ +import { join } from "path"; +import { promisify } from "util"; +import { writeFile } from "fs"; +import * as Sentry from "@sentry/node"; + +import { + downloadContentFromMessage, + jidNormalizedUser, + MediaType, + MessageUpsertType, + proto, + WAMessage, + WAMessageUpdate, + WASocket, + getContentType, + extractMessageContent, + WAMessageStubType, + AnyMessageContent +} from "@whiskeysockets/baileys"; + +import Contact from "../../models/Contact"; +import Ticket from "../../models/Ticket"; +import Message from "../../models/Message"; + +import { getIO } from "../../libs/socket"; +import CreateMessageService from "../MessageServices/CreateMessageService"; +import { logger } from "../../utils/logger"; +import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService"; +import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; +import formatBody from "../../helpers/Mustache"; +import { Store } from "../../libs/store"; +import Setting from "../../models/Setting"; +import { debounce } from "../../helpers/Debounce"; +import UpdateTicketService from "../TicketServices/UpdateTicketService"; +import { sayChatbot } from "./ChatBotListener"; +import hourExpedient from "./hourExpedient"; +import { NormalizadorMejorado } from "../../utils/NormalizadorMejorado"; // IMPORTAR EL NUEVO NORMALIZADOR + +type Session = WASocket & { + id?: number; + store?: Store; +}; + +interface ImessageUpsert { + messages: proto.IWebMessageInfo[]; + type: MessageUpsertType; +} + +// ========== INTERFACES ACTUALIZADAS ========== +interface IMe { + name: string; + id: string; + lid?: string; + esLid?: boolean; + esLidValido?: boolean; // NUEVO: indica si el LID pudo convertirse a número real + number?: string; + originalJid?: string; +} + +const writeFileAsync = promisify(writeFile); + +// Instancia global del normalizador +const normalizador = new NormalizadorMejorado(); + +// ========== FUNCIÓN MEJORADA PARA OBTENER NÚMERO REAL ========== + +async function obtenerNumeroRealDeMensaje(wbot: Session, message: proto.IWebMessageInfo): Promise<{ + numeroReal: string; + jidReal: string; + esLid: boolean; + esLidValido: boolean; + lidOriginal?: string; +}> { + let jid = message.key.remoteJid || ''; + + if (message.key.participant) { + jid = message.key.participant; + } + + if (!jid) { + return { + numeroReal: '', + jidReal: '', + esLid: false, + esLidValido: false + }; + } + + console.log('🔍 ANALIZANDO JID:', jid); + console.log(' Tipo:', jid.includes('@lid') ? 'LID' : jid.includes('@s.whatsapp.net') ? 'Tradicional' : 'Desconocido'); + + // DETECCIÓN MEJORADA DE LIDs + const esLid = jid.includes('@lid') || jid.includes(':') || (jid.includes('.') && !jid.includes('@s.whatsapp.net')); + + if (esLid && jid.includes('@lid')) { + console.log('✅ DETECTADO LID CON @lid:', jid); + + try { + // Intentar obtener número real con onWhatsApp + const results = await wbot.onWhatsApp(jid); + + if (results && results.length > 0 && results[0]?.exists) { + // ¡ÉXITO! Tenemos el número real + const numeroReal = results[0].jid.split('@')[0]; + const jidReal = results[0].jid; + + console.log('🎯 LID CONVERTIDO A NÚMERO REAL:'); + console.log(' LID original:', jid); + console.log(' Número real:', numeroReal); + console.log(' JID real:', jidReal); + + return { + numeroReal, + jidReal, + esLid: true, + esLidValido: true, + lidOriginal: jid + }; + } else { + // onWhatsApp falló o el LID no existe + console.log('⚠️ LID NO PUDO CONVERTIRSE:', jid); + console.log(' onWhatsApp no devolvió resultados válidos'); + + // Extraer la parte numérica del LID para placeholder + const lidSinSuffix = jid.split('@')[0]; + const placeholder = `LID_${lidSinSuffix.substring(0, 10)}`; // Limitar longitud + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } catch (error) { + console.error('❌ Error en onWhatsApp para LID:', error); + + // Fallback: usar placeholder seguro + const lidSinSuffix = jid.split('@')[0].replace(/\D/g, ''); + const placeholder = lidSinSuffix ? `LID_${lidSinSuffix.substring(0, 10)}` : 'LID_DESCONOCIDO'; + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } + + // Si NO es LID con @lid, usar el normalizador existente + try { + const numeroReal = await normalizador.obtenerNumeroReal(wbot, jid); + + console.log('✅ RESULTADO (no LID @lid):'); + console.log(' JID original:', jid); + console.log(' Número obtenido:', numeroReal); + console.log(' Es LID?:', esLid ? 'SÍ' : 'NO'); + + return { + numeroReal, + jidReal: numeroReal + '@s.whatsapp.net', + esLid, + esLidValido: false, + lidOriginal: esLid ? jid : undefined + }; + + } catch (error) { + console.error('❌ Error obteniendo número real:', error); + + // Fallback + let numeroFallback = jid.split('@')[0]; + if (jid.includes(':')) { + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + numeroFallback = match[1]; + } + } + + return { + numeroReal: numeroFallback, + jidReal: numeroFallback + '@s.whatsapp.net', + esLid: false, + esLidValido: false + }; + } +} +// ========== FIN FUNCIÓN MEJORADA ========== + +const getTypeMessage = (msg: proto.IWebMessageInfo): string => { + return getContentType(msg.message); +}; + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +const getBodyButton = (message: proto.IWebMessageInfo): string => { + if (message.message?.templateMessage?.hydratedFourRowTemplate) { + return ( + message.message.templateMessage.hydratedFourRowTemplate.hydratedContentText || + "" + ); + } + if (message.message?.templateMessage?.hydratedTemplate) { + return ( + message.message.templateMessage.hydratedTemplate.hydratedContentText || + "" + ); + } + return ""; +}; + + +const msgLocation = (message: proto.IWebMessageInfo): proto.Message.ILocationMessage | null => { + return message.message?.locationMessage || null; +}; + +const getBodyMessage = (msg: proto.IWebMessageInfo): string => { + const extract = extractMessageContent(msg.message); + + if (extract) { + if (extract.conversation) { + return extract.conversation; + } + if (extract.extendedTextMessage?.text) { + return extract.extendedTextMessage.text; + } + if (extract.imageMessage?.caption) { + return extract.imageMessage.caption; + } + if (extract.videoMessage?.caption) { + return extract.videoMessage.caption; + } + if (extract.documentMessage?.caption) { + return extract.documentMessage.caption; + } + } + + const bodyButton = getBodyButton(msg); + if (bodyButton) return bodyButton; + + return ""; +}; +// ========== FIN FUNCIONES FALTANTES ========== + +const getMeSocket = (wbot: Session): IMe => { + return { + id: jidNormalizedUser((wbot as WASocket).user.id), + name: (wbot as WASocket).user.name || "" + }; +}; + +const getSenderMessage = (msg: proto.IWebMessageInfo, wbot: Session): string => { + const me = getMeSocket(wbot); + if (msg.key.fromMe) return me.id; + + const senderId = msg.participant || msg.key.participant || msg.key.remoteJid || undefined; + return senderId ? jidNormalizedUser(senderId) : ""; +}; + +// ========== FUNCIÓN COMPLETAMENTE REESCRITA PARA MANEJAR LIDs ========== +const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session): Promise => { + const isGroup = msg.key.remoteJid?.includes("g.us") || false; + + if (isGroup) { + return { + id: getSenderMessage(msg, wbot), + name: msg.pushName || "" + }; + } + + // Para mensajes individuales, usar nuestro normalizador + try { + const { numeroReal, jidReal, esLid, lidOriginal } = await obtenerNumeroRealDeMensaje(wbot, msg); + + if (!numeroReal) { + throw new Error('No se pudo obtener número real'); + } + + return { + id: jidReal, + name: msg.key.fromMe ? numeroReal : msg.pushName || numeroReal, + number: numeroReal, + lid: lidOriginal, + esLid, + originalJid: msg.key.remoteJid || '' // Guardar el JID original + }; + + } catch (error) { + console.error('❌ Error en getContactMessage:', error); + + // Fallback extremo + const rawNumber = msg.key.remoteJid?.replace(/\D/g, "") || ""; + return { + id: msg.key.remoteJid || "", + name: msg.key.fromMe ? rawNumber : msg.pushName || rawNumber, + number: rawNumber + }; + } +}; +// ========== FIN FUNCIÓN REESCRITA ========== + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +const downloadMedia = async ( + msg: proto.IWebMessageInfo, + path: string +): Promise => { + const type = getTypeMessage(msg); + const messageContent = msg.message?.[type as keyof typeof msg.message]; + + if (!messageContent || typeof messageContent !== "object") { + throw new Error("Invalid message content"); + } + + const stream = await downloadContentFromMessage( + messageContent as any, + type as MediaType + ); + + let buffer = Buffer.from([]); + for await (const chunk of stream) { + buffer = Buffer.concat([buffer, chunk]); + } + + await writeFileAsync(path, buffer); + return path; +}; + +const isValidMsg = (msg: proto.IWebMessageInfo): boolean => { + if (msg.key.remoteJid === "status@broadcast") return false; + + const msgStubType = msg.messageStubType; + const isStubMessage = [ + WAMessageStubType.REVOKE, + WAMessageStubType.E2E_DEVICE_CHANGED, + ].includes(msgStubType as number); + + return !isStubMessage; +}; + +const verifyQuotedMessage = async ( + msg: proto.IWebMessageInfo +): Promise => { + if (!msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + return null; + } + + const quoted = msg.message.extendedTextMessage.contextInfo; + + const quotedMessage = await Message.findOne({ + where: { id: quoted.stanzaId } + }); + + return quotedMessage; +}; + +const verifyMediaMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + try { + const mediaPath = join(__dirname, "..", "..", "..", "public"); + const filename = `${messageData.id}.${getTypeMessage(msg).split("Message")[0]}`; + const path = join(mediaPath, filename); + + await downloadMedia(msg, path); + messageData.mediaUrl = filename; + } catch (err) { + Sentry.captureException(err); + logger.error(err); + } + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +const verifyMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +const verifyQueue = async ( + wbot: Session, + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const { queues } = await ShowWhatsAppService(wbot.id!); + + if (queues.length === 0) { + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { userId: 1 } // Asignar a un usuario por defecto + }); + return; + } + + // Lógica para asignar a una cola + // Aquí puedes implementar tu lógica de asignación + const selectedQueue = queues[0]; // Por ahora toma la primera cola + + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { + queueId: selectedQueue.id, + status: "pending" + } + }); +}; +// ========== FIN FUNCIONES FALTANTES ========== + +// ========== VERIFYCONTACT ACTUALIZADO ========== +const verifyContact = async (msgContact: IMe, wbot: Session): Promise => { + let profilePicUrl: string; + try { + profilePicUrl = await wbot.profilePictureUrl(msgContact.id); + } catch { + profilePicUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/nopicture.png`; + } + + let phoneNumber = msgContact.number || msgContact.id.replace(/\D/g, ""); + + console.log('👤 VERIFICANDO CONTACTO:'); + console.log(' ID recibido:', msgContact.id); + console.log(' Número en msgContact:', msgContact.number); + console.log(' Es LID?:', msgContact.esLid ? 'SÍ' : 'NO'); + console.log(' LID original:', msgContact.lid || 'N/A'); + console.log(' JID original:', msgContact.originalJid || 'N/A'); + + // Si tenemos un LID, intentar obtener el número real + if (msgContact.lid) { + try { + console.log('🔍 Contacto tiene LID, verificando número real:', msgContact.lid); + const results = await wbot.onWhatsApp(msgContact.lid); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ LID convertido a número en verifyContact:', phoneNumber); + } + } catch (error) { + console.error('❌ Error obteniendo número de LID:', error); + } + } + + // Si el ID mismo es un LID pero no lo capturamos antes + if (!msgContact.lid && (msgContact.id.includes(':') || (msgContact.id.includes('.') && !msgContact.id.includes('@s.whatsapp.net')))) { + try { + console.log('🔍 ID parece LID, verificando:', msgContact.id); + const results = await wbot.onWhatsApp(msgContact.id); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ ID LID convertido a número:', phoneNumber); + } + } catch (error) { + console.error('❌ Error verificando ID LID:', error); + } + } + + // OBTENER EL companyId DEL WHATSAPP O USAR UN VALOR POR DEFECTO + // Esto depende de cómo tengas configurada tu aplicación + const companyId = wbot.id ? 1 : 1; // Ajusta esta lógica según tu aplicación + + const contactData = { + name: msgContact?.name || phoneNumber, + number: phoneNumber, + profilePicUrl, + isGroup: msgContact.id.includes("g.us"), + companyId: companyId, // ← AÑADIDO + lid: msgContact.lid || null, + originalJid: msgContact.originalJid || null, + whatsappId: wbot.id + }; + + console.log('📝 DATOS FINALES DEL CONTACTO:'); + console.log(' Nombre:', contactData.name); + console.log(' Número:', contactData.number); + console.log(' LID guardado:', contactData.lid); + console.log(' Company ID:', contactData.companyId); + + const contact = await CreateOrUpdateContactService(contactData); + return contact; +}; +// ========== FIN VERIFYCONTACT ACTUALIZADO ========== + +const handleMessage = async ( + msg: proto.IWebMessageInfo, + wbot: Session +): Promise => { + if (!isValidMsg(msg)) return; + + try { + let msgContact: IMe; + let groupContact: Contact | undefined; + + const isGroup = msg.key.remoteJid?.endsWith("@g.us") || false; + + const msgIsGroupBlock = await Setting.findOne({ + where: { key: "CheckMsgIsGroup" } + }); + + const bodyMessage = getBodyMessage(msg); + const msgType = getTypeMessage(msg); + + if (msgType === "protocolMessage") return; + + const hasMedia = + msg.message?.audioMessage || + msg.message?.imageMessage || + msg.message?.videoMessage || + msg.message?.documentMessage || + msg.message?.stickerMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; + + if (msg.key.fromMe) { + if (/\u200e/.test(bodyMessage || "")) return; + + if ( + !hasMedia && + msgType !== "conversation" && + msgType !== "extendedTextMessage" && + msgType !== "vcard" && + msgType !== "reactionMessage" && + msgType !== "ephemeralMessage" && + msgType !== "protocolMessage" && + msgType !== "viewOnceMessage" + ) + return; + + msgContact = await getContactMessage(msg, wbot); + } else { + msgContact = await getContactMessage(msg, wbot); + } + + if (msgIsGroupBlock?.value === "enabled" && isGroup) return; + + if (isGroup) { + try { + const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid!); + const msgGroupContact = { + id: grupoMeta.id, + name: grupoMeta.subject || "" + }; + groupContact = await verifyContact(msgGroupContact, wbot); + } catch (error) { + console.error('Error obteniendo metadatos del grupo:', error); + } + } + + const whatsapp = await ShowWhatsAppService(wbot.id!); + + // Obtener conteo de mensajes no leídos + const count = wbot.store && wbot.store.chats ? + wbot.store.chats.get(msg.key.remoteJid || msg.key.participant) : + undefined; + + const unreadMessages = msg.key.fromMe ? 0 : count?.unreadCount || 1; + + const contact = await verifyContact(msgContact, wbot); + + // ========== DIAGNÓSTICO COMPLETO ========== + console.log('\n📊 DIAGNÓSTICO COMPLETO DEL MENSAJE:'); + console.log('======================================'); + console.log('📱 DATOS CRUDOS:'); + console.log(' RemoteJid:', msg.key.remoteJid); + console.log(' Participant:', msg.key.participant); + console.log(' FromMe:', msg.key.fromMe); + console.log(' PushName:', msg.pushName); + console.log(' Tipo de mensaje:', msgType); + + console.log('\n🔍 ANÁLISIS LID:'); + console.log(' JID analizado:', msg.key.remoteJid); + if (msg.key.remoteJid?.includes(':')) { + const partes = msg.key.remoteJid.split(':'); + console.log(' Parte 1 (posible número):', partes[0]); + console.log(' Parte 2 (identificador):', partes[1]); + } + + console.log('\n👤 DATOS DEL CONTACTO:'); + console.log(' Número en contacto BD:', contact.number); + console.log(' Contact ID:', contact.id); + console.log(' Nombre:', contact.name); + console.log(' LID guardado:', contact.lid); + console.log('======================================\n'); + + const ticket = await FindOrCreateTicketService({ + contact, + whatsappId: wbot.id!, + unreadMessages, + groupContact, + channel: "whatsapp" + }); + + if (hasMedia) { + await verifyMediaMessage(msg, ticket, contact); + } else { + await verifyMessage(msg, ticket, contact); + } + + const checkExpedient = await hourExpedient(); + if (checkExpedient) { + if ( + !ticket.queue && + !isGroup && + !msg.key.fromMe && + !ticket.userId && + whatsapp.queues.length >= 1 + ) { + await verifyQueue(wbot, msg, ticket, contact); + } + + if (ticket.queue && ticket.queueId) { + if (!ticket.user) { + await sayChatbot(ticket.queueId, wbot, ticket, contact, msg); + } + } + } else { + const getLastMessageFromMe = await Message.findOne({ + where: { + ticketId: ticket.id, + fromMe: true + }, + order: [["createdAt", "DESC"]] + }); + + if ( + getLastMessageFromMe?.body === + formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact) + ) + return; + + const body = formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } catch (err) { + console.error('❌ Error en handleMessage:', err); + Sentry.captureException(err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } +}; + +const wbotMessageListener = (wbot: Session): void => { + wbot.ev.on("messages.upsert", async (messageUpsert: ImessageUpsert) => { + const messages = messageUpsert.messages; + + if (messages.length === 0) return; + + const message = messages[0]; + + if (message.key.id.startsWith("BAE5") && message.key.fromMe) return; + + handleMessage(message, wbot); + }); + + wbot.ev.on("messages.update", (messageUpdate: WAMessageUpdate[]) => { + if (messageUpdate.length === 0) return; + + messageUpdate.forEach(async (update) => { + if (!update.key?.id || !update.update) return; + + const message = await Message.findOne({ where: { id: update.key.id } }); + if (!message) return; + + if (update.update?.status) { + await message.update({ status: update.update.status }); + const io = getIO(); + io.to(message.ticketId.toString()).emit("appMessage", { + action: "update", + message + }); + } + }); + }); +}; + + +export { + wbotMessageListener, + handleMessage, + verifyMessage, + getBodyMessage, + verifyMediaMessage, + isValidMsg, + verifyQueue, + // Agrega cualquier otra función que se use externamente +}; diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts.backup_pre_metadata b/backend/src/services/WbotServices/wbotMessageListener.ts.backup_pre_metadata new file mode 100644 index 0000000..ca64347 --- /dev/null +++ b/backend/src/services/WbotServices/wbotMessageListener.ts.backup_pre_metadata @@ -0,0 +1,945 @@ +import { join } from "path"; +import { promisify } from "util"; +import { writeFile } from "fs"; +import * as Sentry from "@sentry/node"; + +import { + downloadContentFromMessage, + jidNormalizedUser, + MediaType, + MessageUpsertType, + proto, + WAMessage, + WAMessageUpdate, + WASocket, + getContentType, + extractMessageContent, + WAMessageStubType, + AnyMessageContent +} from "@whiskeysockets/baileys"; + +import Contact from "../../models/Contact"; +import Ticket from "../../models/Ticket"; +import Message from "../../models/Message"; + +import { getIO } from "../../libs/socket"; +import CreateMessageService from "../MessageServices/CreateMessageService"; +import { logger } from "../../utils/logger"; +import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService"; +import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; +import formatBody from "../../helpers/Mustache"; +import { Store } from "../../libs/store"; +import Setting from "../../models/Setting"; +import { debounce } from "../../helpers/Debounce"; +import UpdateTicketService from "../TicketServices/UpdateTicketService"; +import { sayChatbot } from "./ChatBotListener"; +import hourExpedient from "./hourExpedient"; +import { NormalizadorMejorado } from "../../utils/NormalizadorMejorado"; // IMPORTAR EL NUEVO NORMALIZADOR +const DEBUG_MEDIA = true; // Cambia a false en producción + + +type Session = WASocket & { + id?: number; + store?: Store; +}; + +interface ImessageUpsert { + messages: proto.IWebMessageInfo[]; + type: MessageUpsertType; +} + +// ========== INTERFACES ACTUALIZADAS ========== +interface IMe { + name: string; + id: string; + lid?: string; + esLid?: boolean; + esLidValido?: boolean; // NUEVO: indica si el LID pudo convertirse a número real + number?: string; + originalJid?: string; +} + +const writeFileAsync = promisify(writeFile); + +// Instancia global del normalizador +const normalizador = new NormalizadorMejorado(); + +// ========== FUNCIÓN MEJORADA PARA OBTENER NÚMERO REAL ========== + +async function obtenerNumeroRealDeMensaje(wbot: Session, message: proto.IWebMessageInfo): Promise<{ + numeroReal: string; + jidReal: string; + esLid: boolean; + esLidValido: boolean; + lidOriginal?: string; +}> { + let jid = message.key.remoteJid || ''; + + if (message.key.participant) { + jid = message.key.participant; + } + + if (!jid) { + return { + numeroReal: '', + jidReal: '', + esLid: false, + esLidValido: false + }; + } + + console.log('🔍 ANALIZANDO JID:', jid); + console.log(' Tipo:', jid.includes('@lid') ? 'LID' : jid.includes('@s.whatsapp.net') ? 'Tradicional' : 'Desconocido'); + + // DETECCIÓN MEJORADA DE LIDs + const esLid = jid.includes('@lid') || jid.includes(':') || (jid.includes('.') && !jid.includes('@s.whatsapp.net')); + + if (esLid && jid.includes('@lid')) { + console.log('✅ DETECTADO LID CON @lid:', jid); + + try { + // Intentar obtener número real con onWhatsApp + const results = await wbot.onWhatsApp(jid); + + if (results && results.length > 0 && results[0]?.exists) { + // ¡ÉXITO! Tenemos el número real + const numeroReal = results[0].jid.split('@')[0]; + const jidReal = results[0].jid; + + console.log('🎯 LID CONVERTIDO A NÚMERO REAL:'); + console.log(' LID original:', jid); + console.log(' Número real:', numeroReal); + console.log(' JID real:', jidReal); + + return { + numeroReal, + jidReal, + esLid: true, + esLidValido: true, + lidOriginal: jid + }; + } else { + // onWhatsApp falló o el LID no existe + console.log('⚠️ LID NO PUDO CONVERTIRSE:', jid); + console.log(' onWhatsApp no devolvió resultados válidos'); + + // Extraer la parte numérica del LID para placeholder + const lidSinSuffix = jid.split('@')[0]; + const placeholder = `LID_${lidSinSuffix.substring(0, 10)}`; // Limitar longitud + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } catch (error) { + console.error('❌ Error en onWhatsApp para LID:', error); + + // Fallback: usar placeholder seguro + const lidSinSuffix = jid.split('@')[0].replace(/\D/g, ''); + const placeholder = lidSinSuffix ? `LID_${lidSinSuffix.substring(0, 10)}` : 'LID_DESCONOCIDO'; + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } + + // Si NO es LID con @lid, usar el normalizador existente + try { + const numeroReal = await normalizador.obtenerNumeroReal(wbot, jid); + + console.log('✅ RESULTADO (no LID @lid):'); + console.log(' JID original:', jid); + console.log(' Número obtenido:', numeroReal); + console.log(' Es LID?:', esLid ? 'SÍ' : 'NO'); + + return { + numeroReal, + jidReal: numeroReal + '@s.whatsapp.net', + esLid, + esLidValido: false, + lidOriginal: esLid ? jid : undefined + }; + + } catch (error) { + console.error('❌ Error obteniendo número real:', error); + + // Fallback + let numeroFallback = jid.split('@')[0]; + if (jid.includes(':')) { + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + numeroFallback = match[1]; + } + } + + return { + numeroReal: numeroFallback, + jidReal: numeroFallback + '@s.whatsapp.net', + esLid: false, + esLidValido: false + }; + } +} +// ========== FIN FUNCIÓN MEJORADA ========== + +const getTypeMessage = (msg: proto.IWebMessageInfo): string => { + return getContentType(msg.message); +}; + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +const getBodyButton = (message: proto.IWebMessageInfo): string => { + if (message.message?.templateMessage?.hydratedFourRowTemplate) { + return ( + message.message.templateMessage.hydratedFourRowTemplate.hydratedContentText || + "" + ); + } + if (message.message?.templateMessage?.hydratedTemplate) { + return ( + message.message.templateMessage.hydratedTemplate.hydratedContentText || + "" + ); + } + return ""; +}; + + +const msgLocation = (message: proto.IWebMessageInfo): proto.Message.ILocationMessage | null => { + return message.message?.locationMessage || null; +}; + +const getBodyMessage = (msg: proto.IWebMessageInfo): string => { + const extract = extractMessageContent(msg.message); + + if (extract) { + if (extract.conversation) { + return extract.conversation; + } + if (extract.extendedTextMessage?.text) { + return extract.extendedTextMessage.text; + } + if (extract.imageMessage?.caption) { + return extract.imageMessage.caption; + } + if (extract.videoMessage?.caption) { + return extract.videoMessage.caption; + } + if (extract.documentMessage?.caption) { + return extract.documentMessage.caption; + } + } + + const bodyButton = getBodyButton(msg); + if (bodyButton) return bodyButton; + + return ""; +}; +// ========== FIN FUNCIONES FALTANTES ========== + +const getMeSocket = (wbot: Session): IMe => { + return { + id: jidNormalizedUser((wbot as WASocket).user.id), + name: (wbot as WASocket).user.name || "" + }; +}; + +const getSenderMessage = (msg: proto.IWebMessageInfo, wbot: Session): string => { + const me = getMeSocket(wbot); + if (msg.key.fromMe) return me.id; + + const senderId = msg.participant || msg.key.participant || msg.key.remoteJid || undefined; + return senderId ? jidNormalizedUser(senderId) : ""; +}; + +// ========== FUNCIÓN COMPLETAMENTE REESCRITA PARA MANEJAR LIDs ========== +const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session): Promise => { + const isGroup = msg.key.remoteJid?.includes("g.us") || false; + + if (isGroup) { + return { + id: getSenderMessage(msg, wbot), + name: msg.pushName || "" + }; + } + + // Para mensajes individuales, usar nuestro normalizador + try { + const { numeroReal, jidReal, esLid, lidOriginal } = await obtenerNumeroRealDeMensaje(wbot, msg); + + if (!numeroReal) { + throw new Error('No se pudo obtener número real'); + } + + return { + id: jidReal, + name: msg.key.fromMe ? numeroReal : msg.pushName || numeroReal, + number: numeroReal, + lid: lidOriginal, + esLid, + originalJid: msg.key.remoteJid || '' // Guardar el JID original + }; + + } catch (error) { + console.error('❌ Error en getContactMessage:', error); + + // Fallback extremo + const rawNumber = msg.key.remoteJid?.replace(/\D/g, "") || ""; + return { + id: msg.key.remoteJid || "", + name: msg.key.fromMe ? rawNumber : msg.pushName || rawNumber, + number: rawNumber + }; + } +}; +// ========== FIN FUNCIÓN REESCRITA ========== + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +// ========== FUNCIÓN downloadMedia REESCRITA Y SEGURA ========== +// ========== FUNCIÓN downloadMedia MEJORADA ========== +const downloadMedia = async ( + msg: proto.IWebMessageInfo, + path: string +): Promise => { + console.log('[MEDIA-DEBUG] === INICIANDO DEBUG COMPLETO ==='); + console.log('[MEDIA-DEBUG] Mensaje ID:', msg.key.id); + + try { + const type = getTypeMessage(msg); + const messageContent = msg.message?.[type as keyof typeof msg.message]; + + console.log('[MEDIA-DEBUG] Tipo de mensaje:', type); + + if (!messageContent || typeof messageContent !== "object") { + console.log('[MEDIA-DEBUG] ❌ Contenido inválido'); + throw new Error("Invalid message content"); + } + + // ==================== DEBUG ESTRUCTURAL COMPLETO ==================== + console.log('[MEDIA-DEBUG] === ESTRUCTURA COMPLETA DEL MENSAJE ==='); + + // 1. Verificar si es imageMessage + if (msg.message?.imageMessage) { + const imgMsg = msg.message.imageMessage; + console.log('[MEDIA-DEBUG] ✅ imageMessage encontrado en msg.message'); + console.log('[MEDIA-DEBUG] Propiedades de imageMessage:', Object.keys(imgMsg)); + + // 2. Buscar mediaKey específicamente +if (imgMsg.mediaKey) { + console.log('[MEDIA-DEBUG] 🎯 ¡MEDIAKEY ENCONTRADO EN imageMessage!'); + const mediaKey = imgMsg.mediaKey; + + console.log('[MEDIA-DEBUG] Tipo de mediaKey:', typeof mediaKey); + + if (Buffer.isBuffer(mediaKey)) { + console.log('[MEDIA-DEBUG] MediaKey como Buffer:'); + console.log('[MEDIA-DEBUG] Hex:', mediaKey.toString('hex')); + console.log('[MEDIA-DEBUG] Base64:', mediaKey.toString('base64')); + console.log('[MEDIA-DEBUG] Longitud:', mediaKey.length, 'bytes'); + + // Guardar para referencia + await writeFileAsync('/tmp/mediakey.bin', mediaKey); + console.log('[MEDIA-DEBUG] MediaKey guardado en /tmp/mediakey.bin'); + } else if (mediaKey instanceof Uint8Array) { + const buffer = Buffer.from(mediaKey); + console.log('[MEDIA-DEBUG] MediaKey como Uint8Array:'); + console.log('[MEDIA-DEBUG] Hex:', buffer.toString('hex')); + console.log('[MEDIA-DEBUG] Base64:', buffer.toString('base64')); + console.log('[MEDIA-DEBUG] Longitud:', buffer.length, 'bytes'); +} else if (typeof mediaKey === 'string') { + const mediaKeyStr = mediaKey as string; + console.log('[MEDIA-DEBUG] MediaKey como string:', mediaKeyStr.substring(0, 50) + '...'); + console.log('[MEDIA-DEBUG] Longitud string:', mediaKeyStr.length, 'caracteres'); + + // Intentar decodificar como base64 + try { + const buffer = Buffer.from(mediaKey, 'base64'); + console.log('[MEDIA-DEBUG] Decodificado como base64:'); + console.log('[MEDIA-DEBUG] Hex:', buffer.toString('hex')); + console.log('[MEDIA-DEBUG] Longitud:', buffer.length, 'bytes'); + } catch (e) { + console.log('[MEDIA-DEBUG] No es base64 válido'); + } + } else { + console.log('[MEDIA-DEBUG] Tipo desconocido:', mediaKey); + } +} + + // 3. Mostrar otras propiedades importantes + console.log('[MEDIA-DEBUG] === OTRAS PROPIEDADES IMPORTANTES ==='); + const importantProps = { + url: imgMsg.url, + directPath: imgMsg.directPath, + mimetype: imgMsg.mimetype, + fileSha256: imgMsg.fileSha256, + fileEncSha256: imgMsg.fileEncSha256, + mediaKeyTimestamp: imgMsg.mediaKeyTimestamp, + jpegThumbnail: imgMsg.jpegThumbnail ? 'PRESENTE' : 'AUSENTE' + }; + + Object.entries(importantProps).forEach(([key, value]) => { + if (value) { + if (Buffer.isBuffer(value) || value instanceof Uint8Array) { + const buf = Buffer.isBuffer(value) ? value : Buffer.from(value); + console.log(`[MEDIA-DEBUG] ${key}:`, buf.toString('hex').substring(0, 32) + '...'); + } else { + console.log(`[MEDIA-DEBUG] ${key}:`, value); + } + } else { + console.log(`[MEDIA-DEBUG] ${key}: AUSENTE`); + } + }); + } else { + console.log('[MEDIA-DEBUG] ❌ No es imageMessage'); + console.log('[MEDIA-DEBUG] Tipos en msg.message:', Object.keys(msg.message || {})); + } + + // 4. También verificar messageContent (el enfoque original) + console.log('[MEDIA-DEBUG] === messageContent (enfoque original) ==='); + console.log('[MEDIA-DEBUG] Tipo de messageContent:', typeof messageContent); + console.log('[MEDIA-DEBUG] Keys en messageContent:', Object.keys(messageContent)); + + // ==================== DESCARGA DIRECTA ==================== + console.log('[MEDIA-DEBUG] === DESCARGA DIRECTA ==='); + + // Usar URL directa si existe + const mediaMessage = messageContent as any; + const url = mediaMessage.url; + + if (url) { + console.log('[MEDIA-DEBUG] Descargando desde URL...'); + console.log('[MEDIA-DEBUG] URL:', url.substring(0, 80) + '...'); + + const https = require('https'); + + const buffer = await new Promise((resolve, reject) => { + https.get(url, (response) => { + const chunks: Buffer[] = []; + response.on('data', (chunk) => chunks.push(chunk)); + response.on('end', () => resolve(Buffer.concat(chunks))); + response.on('error', reject); + }).on('error', reject); + }); + + console.log('[MEDIA-DEBUG] ✅ Descarga completada:', buffer.length, 'bytes'); + + // Guardar archivo + await writeFileAsync(path, buffer); + console.log('[MEDIA-DEBUG] Archivo guardado en:', path); + + // Verificar primeros bytes + const firstBytes = buffer.slice(0, 8).toString('hex'); + console.log('[MEDIA-DEBUG] Primeros 8 bytes:', firstBytes); + + if (firstBytes.startsWith('ffd8')) { + console.log('[MEDIA-DEBUG] ✅ ¡Es JPEG!'); + } else if (firstBytes.startsWith('8950')) { + console.log('[MEDIA-DEBUG] ✅ ¡Es PNG!'); + } else { + console.log('[MEDIA-DEBUG] ❌ No es imagen conocida (probablemente cifrado)'); + } + } else { + console.log('[MEDIA-DEBUG] ❌ No hay URL, usando método tradicional'); + + const stream = await downloadContentFromMessage( + messageContent as any, + type as MediaType + ); + + let buffer = Buffer.from([]); + for await (const chunk of stream) { + buffer = Buffer.concat([buffer, chunk]); + } + + console.log('[MEDIA-DEBUG] Stream recibido:', buffer.length, 'bytes'); + await writeFileAsync(path, buffer); + } + + return path; + + } catch (error) { + console.error('[MEDIA-DEBUG] ❌ ERROR CRÍTICO:', error.message); + console.error(error.stack); + + // Guardar error + try { + await writeFileAsync(path, Buffer.from(`ERROR: ${error.message}`)); + } catch (e) { + // Ignorar + } + + return path; + } +}; + +// ========== FUNCIÓN HKDF SIMPLIFICADA ========== +function deriveWhatsAppKeys(mediaKey: Buffer): Buffer { + const crypto = require('crypto'); + + // Parámetros para WhatsApp + const salt = Buffer.alloc(32); // Salt vacío + const info = 'WhatsApp Image Keys'; // Info string + const length = 112; // Longitud total requerida + + // Extraer PRK (Pseudorandom Key) + const prk = crypto.createHmac('sha256', salt).update(mediaKey).digest(); + + // Expandir + const infoBuffer = Buffer.from(info); + const blocks = Math.ceil(length / 32); + + let t = Buffer.alloc(0); + let result = Buffer.alloc(0); + + for (let i = 0; i < blocks; i++) { + const tBuf = Buffer.concat([ + t, + infoBuffer, + Buffer.from([i + 1]) + ]); + t = crypto.createHmac('sha256', prk).update(tBuf).digest(); + result = Buffer.concat([result, t]); + } + + return result.slice(0, length); +} +const verifyQuotedMessage = async ( + msg: proto.IWebMessageInfo +): Promise => { + if (!msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + return null; + } + + const quoted = msg.message.extendedTextMessage.contextInfo; + + const quotedMessage = await Message.findOne({ + where: { id: quoted.stanzaId } + }); + + return quotedMessage; +}; + +const verifyMediaMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + try { + const mediaPath = join(__dirname, "..", "..", "..", "public"); + const filename = `${messageData.id}.${getTypeMessage(msg).split("Message")[0]}`; + const path = join(mediaPath, filename); + + await downloadMedia(msg, path); + messageData.mediaUrl = filename; + } catch (err) { + Sentry.captureException(err); + logger.error(err); + } + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +const verifyMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +// ========== FUNCIÓN isValidMsg ========== +const isValidMsg = (msg: proto.IWebMessageInfo): boolean => { + if (msg.key.remoteJid === "status@broadcast") return false; + + const msgStubType = msg.messageStubType; + const isStubMessage = [ + WAMessageStubType.REVOKE, + WAMessageStubType.E2E_DEVICE_CHANGED, + ].includes(msgStubType as number); + + return !isStubMessage; +}; + + + +const verifyQueue = async ( + wbot: Session, + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const { queues } = await ShowWhatsAppService(wbot.id!); + + if (queues.length === 0) { + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { userId: 1 } // Asignar a un usuario por defecto + }); + return; + } + + // Lógica para asignar a una cola + // Aquí puedes implementar tu lógica de asignación + const selectedQueue = queues[0]; // Por ahora toma la primera cola + + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { + queueId: selectedQueue.id, + status: "pending" + } + }); +}; +// ========== FIN FUNCIONES FALTANTES ========== + +// ========== VERIFYCONTACT ACTUALIZADO ========== +const verifyContact = async (msgContact: IMe, wbot: Session): Promise => { + let profilePicUrl: string; + try { + profilePicUrl = await wbot.profilePictureUrl(msgContact.id); + } catch { + profilePicUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/nopicture.png`; + } + + let phoneNumber = msgContact.number || msgContact.id.replace(/\D/g, ""); + + console.log('👤 VERIFICANDO CONTACTO:'); + console.log(' ID recibido:', msgContact.id); + console.log(' Número en msgContact:', msgContact.number); + console.log(' Es LID?:', msgContact.esLid ? 'SÍ' : 'NO'); + console.log(' LID original:', msgContact.lid || 'N/A'); + console.log(' JID original:', msgContact.originalJid || 'N/A'); + + // Si tenemos un LID, intentar obtener el número real + if (msgContact.lid) { + try { + console.log('🔍 Contacto tiene LID, verificando número real:', msgContact.lid); + const results = await wbot.onWhatsApp(msgContact.lid); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ LID convertido a número en verifyContact:', phoneNumber); + } + } catch (error) { + console.error('❌ Error obteniendo número de LID:', error); + } + } + + // Si el ID mismo es un LID pero no lo capturamos antes + if (!msgContact.lid && (msgContact.id.includes(':') || (msgContact.id.includes('.') && !msgContact.id.includes('@s.whatsapp.net')))) { + try { + console.log('🔍 ID parece LID, verificando:', msgContact.id); + const results = await wbot.onWhatsApp(msgContact.id); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ ID LID convertido a número:', phoneNumber); + } + } catch (error) { + console.error('❌ Error verificando ID LID:', error); + } + } + + // OBTENER EL companyId DEL WHATSAPP O USAR UN VALOR POR DEFECTO + // Esto depende de cómo tengas configurada tu aplicación + const companyId = wbot.id ? 1 : 1; // Ajusta esta lógica según tu aplicación + + const contactData = { + name: msgContact?.name || phoneNumber, + number: phoneNumber, + profilePicUrl, + isGroup: msgContact.id.includes("g.us"), + companyId: companyId, // ← AÑADIDO + lid: msgContact.lid || null, + originalJid: msgContact.originalJid || null, + whatsappId: wbot.id + }; + + console.log('📝 DATOS FINALES DEL CONTACTO:'); + console.log(' Nombre:', contactData.name); + console.log(' Número:', contactData.number); + console.log(' LID guardado:', contactData.lid); + console.log(' Company ID:', contactData.companyId); + + const contact = await CreateOrUpdateContactService(contactData); + return contact; +}; +// ========== FIN VERIFYCONTACT ACTUALIZADO ========== + +const handleMessage = async ( + msg: proto.IWebMessageInfo, + wbot: Session +): Promise => { + if (!isValidMsg(msg)) return; + + try { + let msgContact: IMe; + let groupContact: Contact | undefined; + + const isGroup = msg.key.remoteJid?.endsWith("@g.us") || false; + + const msgIsGroupBlock = await Setting.findOne({ + where: { key: "CheckMsgIsGroup" } + }); + + const bodyMessage = getBodyMessage(msg); + const msgType = getTypeMessage(msg); + + if (msgType === "protocolMessage") return; + + const hasMedia = + msg.message?.audioMessage || + msg.message?.imageMessage || + msg.message?.videoMessage || + msg.message?.documentMessage || + msg.message?.stickerMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; + + if (msg.key.fromMe) { + if (/\u200e/.test(bodyMessage || "")) return; + + if ( + !hasMedia && + msgType !== "conversation" && + msgType !== "extendedTextMessage" && + msgType !== "vcard" && + msgType !== "reactionMessage" && + msgType !== "ephemeralMessage" && + msgType !== "protocolMessage" && + msgType !== "viewOnceMessage" + ) + return; + + msgContact = await getContactMessage(msg, wbot); + } else { + msgContact = await getContactMessage(msg, wbot); + } + + if (msgIsGroupBlock?.value === "enabled" && isGroup) return; + + if (isGroup) { + try { + const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid!); + const msgGroupContact = { + id: grupoMeta.id, + name: grupoMeta.subject || "" + }; + groupContact = await verifyContact(msgGroupContact, wbot); + } catch (error) { + console.error('Error obteniendo metadatos del grupo:', error); + } + } + + const whatsapp = await ShowWhatsAppService(wbot.id!); + + // Obtener conteo de mensajes no leídos + const count = wbot.store && wbot.store.chats ? + wbot.store.chats.get(msg.key.remoteJid || msg.key.participant) : + undefined; + + const unreadMessages = msg.key.fromMe ? 0 : count?.unreadCount || 1; + + const contact = await verifyContact(msgContact, wbot); + + // ========== DIAGNÓSTICO COMPLETO ========== + console.log('\n📊 DIAGNÓSTICO COMPLETO DEL MENSAJE:'); + console.log('======================================'); + console.log('📱 DATOS CRUDOS:'); + console.log(' RemoteJid:', msg.key.remoteJid); + console.log(' Participant:', msg.key.participant); + console.log(' FromMe:', msg.key.fromMe); + console.log(' PushName:', msg.pushName); + console.log(' Tipo de mensaje:', msgType); + + console.log('\n🔍 ANÁLISIS LID:'); + console.log(' JID analizado:', msg.key.remoteJid); + if (msg.key.remoteJid?.includes(':')) { + const partes = msg.key.remoteJid.split(':'); + console.log(' Parte 1 (posible número):', partes[0]); + console.log(' Parte 2 (identificador):', partes[1]); + } + + console.log('\n👤 DATOS DEL CONTACTO:'); + console.log(' Número en contacto BD:', contact.number); + console.log(' Contact ID:', contact.id); + console.log(' Nombre:', contact.name); + console.log(' LID guardado:', contact.lid); + console.log('======================================\n'); + + const ticket = await FindOrCreateTicketService({ + contact, + whatsappId: wbot.id!, + unreadMessages, + groupContact, + channel: "whatsapp" + }); + + if (hasMedia) { + await verifyMediaMessage(msg, ticket, contact); + } else { + await verifyMessage(msg, ticket, contact); + } + + const checkExpedient = await hourExpedient(); + if (checkExpedient) { + if ( + !ticket.queue && + !isGroup && + !msg.key.fromMe && + !ticket.userId && + whatsapp.queues.length >= 1 + ) { + await verifyQueue(wbot, msg, ticket, contact); + } + + if (ticket.queue && ticket.queueId) { + if (!ticket.user) { + await sayChatbot(ticket.queueId, wbot, ticket, contact, msg); + } + } + } else { + const getLastMessageFromMe = await Message.findOne({ + where: { + ticketId: ticket.id, + fromMe: true + }, + order: [["createdAt", "DESC"]] + }); + + if ( + getLastMessageFromMe?.body === + formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact) + ) + return; + + const body = formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } catch (err) { + console.error('❌ Error en handleMessage:', err); + Sentry.captureException(err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } +}; + +const wbotMessageListener = (wbot: Session): void => { + wbot.ev.on("messages.upsert", async (messageUpsert: ImessageUpsert) => { + const messages = messageUpsert.messages; + + if (messages.length === 0) return; + + const message = messages[0]; + + if (message.key.id.startsWith("BAE5") && message.key.fromMe) return; + + handleMessage(message, wbot); + }); + + wbot.ev.on("messages.update", (messageUpdate: WAMessageUpdate[]) => { + if (messageUpdate.length === 0) return; + + messageUpdate.forEach(async (update) => { + if (!update.key?.id || !update.update) return; + + const message = await Message.findOne({ where: { id: update.key.id } }); + if (!message) return; + + if (update.update?.status) { + await message.update({ status: update.update.status }); + const io = getIO(); + io.to(message.ticketId.toString()).emit("appMessage", { + action: "update", + message + }); + } + }); + }); +}; + + +export { + wbotMessageListener, + handleMessage, + verifyMessage, + getBodyMessage, + verifyMediaMessage, + isValidMsg, // ← ¡AGREGADA! + verifyQueue, + downloadMedia, // ← ¡AGREGAR TAMBIÉN ESTA! + getContactMessage, // ← Y ESTA SI NO ESTÁ + // Agrega cualquier otra función que se use externamente +}; \ No newline at end of file diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts.backup_solucion_definitiva b/backend/src/services/WbotServices/wbotMessageListener.ts.backup_solucion_definitiva new file mode 100644 index 0000000..ca64347 --- /dev/null +++ b/backend/src/services/WbotServices/wbotMessageListener.ts.backup_solucion_definitiva @@ -0,0 +1,945 @@ +import { join } from "path"; +import { promisify } from "util"; +import { writeFile } from "fs"; +import * as Sentry from "@sentry/node"; + +import { + downloadContentFromMessage, + jidNormalizedUser, + MediaType, + MessageUpsertType, + proto, + WAMessage, + WAMessageUpdate, + WASocket, + getContentType, + extractMessageContent, + WAMessageStubType, + AnyMessageContent +} from "@whiskeysockets/baileys"; + +import Contact from "../../models/Contact"; +import Ticket from "../../models/Ticket"; +import Message from "../../models/Message"; + +import { getIO } from "../../libs/socket"; +import CreateMessageService from "../MessageServices/CreateMessageService"; +import { logger } from "../../utils/logger"; +import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService"; +import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; +import formatBody from "../../helpers/Mustache"; +import { Store } from "../../libs/store"; +import Setting from "../../models/Setting"; +import { debounce } from "../../helpers/Debounce"; +import UpdateTicketService from "../TicketServices/UpdateTicketService"; +import { sayChatbot } from "./ChatBotListener"; +import hourExpedient from "./hourExpedient"; +import { NormalizadorMejorado } from "../../utils/NormalizadorMejorado"; // IMPORTAR EL NUEVO NORMALIZADOR +const DEBUG_MEDIA = true; // Cambia a false en producción + + +type Session = WASocket & { + id?: number; + store?: Store; +}; + +interface ImessageUpsert { + messages: proto.IWebMessageInfo[]; + type: MessageUpsertType; +} + +// ========== INTERFACES ACTUALIZADAS ========== +interface IMe { + name: string; + id: string; + lid?: string; + esLid?: boolean; + esLidValido?: boolean; // NUEVO: indica si el LID pudo convertirse a número real + number?: string; + originalJid?: string; +} + +const writeFileAsync = promisify(writeFile); + +// Instancia global del normalizador +const normalizador = new NormalizadorMejorado(); + +// ========== FUNCIÓN MEJORADA PARA OBTENER NÚMERO REAL ========== + +async function obtenerNumeroRealDeMensaje(wbot: Session, message: proto.IWebMessageInfo): Promise<{ + numeroReal: string; + jidReal: string; + esLid: boolean; + esLidValido: boolean; + lidOriginal?: string; +}> { + let jid = message.key.remoteJid || ''; + + if (message.key.participant) { + jid = message.key.participant; + } + + if (!jid) { + return { + numeroReal: '', + jidReal: '', + esLid: false, + esLidValido: false + }; + } + + console.log('🔍 ANALIZANDO JID:', jid); + console.log(' Tipo:', jid.includes('@lid') ? 'LID' : jid.includes('@s.whatsapp.net') ? 'Tradicional' : 'Desconocido'); + + // DETECCIÓN MEJORADA DE LIDs + const esLid = jid.includes('@lid') || jid.includes(':') || (jid.includes('.') && !jid.includes('@s.whatsapp.net')); + + if (esLid && jid.includes('@lid')) { + console.log('✅ DETECTADO LID CON @lid:', jid); + + try { + // Intentar obtener número real con onWhatsApp + const results = await wbot.onWhatsApp(jid); + + if (results && results.length > 0 && results[0]?.exists) { + // ¡ÉXITO! Tenemos el número real + const numeroReal = results[0].jid.split('@')[0]; + const jidReal = results[0].jid; + + console.log('🎯 LID CONVERTIDO A NÚMERO REAL:'); + console.log(' LID original:', jid); + console.log(' Número real:', numeroReal); + console.log(' JID real:', jidReal); + + return { + numeroReal, + jidReal, + esLid: true, + esLidValido: true, + lidOriginal: jid + }; + } else { + // onWhatsApp falló o el LID no existe + console.log('⚠️ LID NO PUDO CONVERTIRSE:', jid); + console.log(' onWhatsApp no devolvió resultados válidos'); + + // Extraer la parte numérica del LID para placeholder + const lidSinSuffix = jid.split('@')[0]; + const placeholder = `LID_${lidSinSuffix.substring(0, 10)}`; // Limitar longitud + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } catch (error) { + console.error('❌ Error en onWhatsApp para LID:', error); + + // Fallback: usar placeholder seguro + const lidSinSuffix = jid.split('@')[0].replace(/\D/g, ''); + const placeholder = lidSinSuffix ? `LID_${lidSinSuffix.substring(0, 10)}` : 'LID_DESCONOCIDO'; + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } + + // Si NO es LID con @lid, usar el normalizador existente + try { + const numeroReal = await normalizador.obtenerNumeroReal(wbot, jid); + + console.log('✅ RESULTADO (no LID @lid):'); + console.log(' JID original:', jid); + console.log(' Número obtenido:', numeroReal); + console.log(' Es LID?:', esLid ? 'SÍ' : 'NO'); + + return { + numeroReal, + jidReal: numeroReal + '@s.whatsapp.net', + esLid, + esLidValido: false, + lidOriginal: esLid ? jid : undefined + }; + + } catch (error) { + console.error('❌ Error obteniendo número real:', error); + + // Fallback + let numeroFallback = jid.split('@')[0]; + if (jid.includes(':')) { + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + numeroFallback = match[1]; + } + } + + return { + numeroReal: numeroFallback, + jidReal: numeroFallback + '@s.whatsapp.net', + esLid: false, + esLidValido: false + }; + } +} +// ========== FIN FUNCIÓN MEJORADA ========== + +const getTypeMessage = (msg: proto.IWebMessageInfo): string => { + return getContentType(msg.message); +}; + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +const getBodyButton = (message: proto.IWebMessageInfo): string => { + if (message.message?.templateMessage?.hydratedFourRowTemplate) { + return ( + message.message.templateMessage.hydratedFourRowTemplate.hydratedContentText || + "" + ); + } + if (message.message?.templateMessage?.hydratedTemplate) { + return ( + message.message.templateMessage.hydratedTemplate.hydratedContentText || + "" + ); + } + return ""; +}; + + +const msgLocation = (message: proto.IWebMessageInfo): proto.Message.ILocationMessage | null => { + return message.message?.locationMessage || null; +}; + +const getBodyMessage = (msg: proto.IWebMessageInfo): string => { + const extract = extractMessageContent(msg.message); + + if (extract) { + if (extract.conversation) { + return extract.conversation; + } + if (extract.extendedTextMessage?.text) { + return extract.extendedTextMessage.text; + } + if (extract.imageMessage?.caption) { + return extract.imageMessage.caption; + } + if (extract.videoMessage?.caption) { + return extract.videoMessage.caption; + } + if (extract.documentMessage?.caption) { + return extract.documentMessage.caption; + } + } + + const bodyButton = getBodyButton(msg); + if (bodyButton) return bodyButton; + + return ""; +}; +// ========== FIN FUNCIONES FALTANTES ========== + +const getMeSocket = (wbot: Session): IMe => { + return { + id: jidNormalizedUser((wbot as WASocket).user.id), + name: (wbot as WASocket).user.name || "" + }; +}; + +const getSenderMessage = (msg: proto.IWebMessageInfo, wbot: Session): string => { + const me = getMeSocket(wbot); + if (msg.key.fromMe) return me.id; + + const senderId = msg.participant || msg.key.participant || msg.key.remoteJid || undefined; + return senderId ? jidNormalizedUser(senderId) : ""; +}; + +// ========== FUNCIÓN COMPLETAMENTE REESCRITA PARA MANEJAR LIDs ========== +const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session): Promise => { + const isGroup = msg.key.remoteJid?.includes("g.us") || false; + + if (isGroup) { + return { + id: getSenderMessage(msg, wbot), + name: msg.pushName || "" + }; + } + + // Para mensajes individuales, usar nuestro normalizador + try { + const { numeroReal, jidReal, esLid, lidOriginal } = await obtenerNumeroRealDeMensaje(wbot, msg); + + if (!numeroReal) { + throw new Error('No se pudo obtener número real'); + } + + return { + id: jidReal, + name: msg.key.fromMe ? numeroReal : msg.pushName || numeroReal, + number: numeroReal, + lid: lidOriginal, + esLid, + originalJid: msg.key.remoteJid || '' // Guardar el JID original + }; + + } catch (error) { + console.error('❌ Error en getContactMessage:', error); + + // Fallback extremo + const rawNumber = msg.key.remoteJid?.replace(/\D/g, "") || ""; + return { + id: msg.key.remoteJid || "", + name: msg.key.fromMe ? rawNumber : msg.pushName || rawNumber, + number: rawNumber + }; + } +}; +// ========== FIN FUNCIÓN REESCRITA ========== + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +// ========== FUNCIÓN downloadMedia REESCRITA Y SEGURA ========== +// ========== FUNCIÓN downloadMedia MEJORADA ========== +const downloadMedia = async ( + msg: proto.IWebMessageInfo, + path: string +): Promise => { + console.log('[MEDIA-DEBUG] === INICIANDO DEBUG COMPLETO ==='); + console.log('[MEDIA-DEBUG] Mensaje ID:', msg.key.id); + + try { + const type = getTypeMessage(msg); + const messageContent = msg.message?.[type as keyof typeof msg.message]; + + console.log('[MEDIA-DEBUG] Tipo de mensaje:', type); + + if (!messageContent || typeof messageContent !== "object") { + console.log('[MEDIA-DEBUG] ❌ Contenido inválido'); + throw new Error("Invalid message content"); + } + + // ==================== DEBUG ESTRUCTURAL COMPLETO ==================== + console.log('[MEDIA-DEBUG] === ESTRUCTURA COMPLETA DEL MENSAJE ==='); + + // 1. Verificar si es imageMessage + if (msg.message?.imageMessage) { + const imgMsg = msg.message.imageMessage; + console.log('[MEDIA-DEBUG] ✅ imageMessage encontrado en msg.message'); + console.log('[MEDIA-DEBUG] Propiedades de imageMessage:', Object.keys(imgMsg)); + + // 2. Buscar mediaKey específicamente +if (imgMsg.mediaKey) { + console.log('[MEDIA-DEBUG] 🎯 ¡MEDIAKEY ENCONTRADO EN imageMessage!'); + const mediaKey = imgMsg.mediaKey; + + console.log('[MEDIA-DEBUG] Tipo de mediaKey:', typeof mediaKey); + + if (Buffer.isBuffer(mediaKey)) { + console.log('[MEDIA-DEBUG] MediaKey como Buffer:'); + console.log('[MEDIA-DEBUG] Hex:', mediaKey.toString('hex')); + console.log('[MEDIA-DEBUG] Base64:', mediaKey.toString('base64')); + console.log('[MEDIA-DEBUG] Longitud:', mediaKey.length, 'bytes'); + + // Guardar para referencia + await writeFileAsync('/tmp/mediakey.bin', mediaKey); + console.log('[MEDIA-DEBUG] MediaKey guardado en /tmp/mediakey.bin'); + } else if (mediaKey instanceof Uint8Array) { + const buffer = Buffer.from(mediaKey); + console.log('[MEDIA-DEBUG] MediaKey como Uint8Array:'); + console.log('[MEDIA-DEBUG] Hex:', buffer.toString('hex')); + console.log('[MEDIA-DEBUG] Base64:', buffer.toString('base64')); + console.log('[MEDIA-DEBUG] Longitud:', buffer.length, 'bytes'); +} else if (typeof mediaKey === 'string') { + const mediaKeyStr = mediaKey as string; + console.log('[MEDIA-DEBUG] MediaKey como string:', mediaKeyStr.substring(0, 50) + '...'); + console.log('[MEDIA-DEBUG] Longitud string:', mediaKeyStr.length, 'caracteres'); + + // Intentar decodificar como base64 + try { + const buffer = Buffer.from(mediaKey, 'base64'); + console.log('[MEDIA-DEBUG] Decodificado como base64:'); + console.log('[MEDIA-DEBUG] Hex:', buffer.toString('hex')); + console.log('[MEDIA-DEBUG] Longitud:', buffer.length, 'bytes'); + } catch (e) { + console.log('[MEDIA-DEBUG] No es base64 válido'); + } + } else { + console.log('[MEDIA-DEBUG] Tipo desconocido:', mediaKey); + } +} + + // 3. Mostrar otras propiedades importantes + console.log('[MEDIA-DEBUG] === OTRAS PROPIEDADES IMPORTANTES ==='); + const importantProps = { + url: imgMsg.url, + directPath: imgMsg.directPath, + mimetype: imgMsg.mimetype, + fileSha256: imgMsg.fileSha256, + fileEncSha256: imgMsg.fileEncSha256, + mediaKeyTimestamp: imgMsg.mediaKeyTimestamp, + jpegThumbnail: imgMsg.jpegThumbnail ? 'PRESENTE' : 'AUSENTE' + }; + + Object.entries(importantProps).forEach(([key, value]) => { + if (value) { + if (Buffer.isBuffer(value) || value instanceof Uint8Array) { + const buf = Buffer.isBuffer(value) ? value : Buffer.from(value); + console.log(`[MEDIA-DEBUG] ${key}:`, buf.toString('hex').substring(0, 32) + '...'); + } else { + console.log(`[MEDIA-DEBUG] ${key}:`, value); + } + } else { + console.log(`[MEDIA-DEBUG] ${key}: AUSENTE`); + } + }); + } else { + console.log('[MEDIA-DEBUG] ❌ No es imageMessage'); + console.log('[MEDIA-DEBUG] Tipos en msg.message:', Object.keys(msg.message || {})); + } + + // 4. También verificar messageContent (el enfoque original) + console.log('[MEDIA-DEBUG] === messageContent (enfoque original) ==='); + console.log('[MEDIA-DEBUG] Tipo de messageContent:', typeof messageContent); + console.log('[MEDIA-DEBUG] Keys en messageContent:', Object.keys(messageContent)); + + // ==================== DESCARGA DIRECTA ==================== + console.log('[MEDIA-DEBUG] === DESCARGA DIRECTA ==='); + + // Usar URL directa si existe + const mediaMessage = messageContent as any; + const url = mediaMessage.url; + + if (url) { + console.log('[MEDIA-DEBUG] Descargando desde URL...'); + console.log('[MEDIA-DEBUG] URL:', url.substring(0, 80) + '...'); + + const https = require('https'); + + const buffer = await new Promise((resolve, reject) => { + https.get(url, (response) => { + const chunks: Buffer[] = []; + response.on('data', (chunk) => chunks.push(chunk)); + response.on('end', () => resolve(Buffer.concat(chunks))); + response.on('error', reject); + }).on('error', reject); + }); + + console.log('[MEDIA-DEBUG] ✅ Descarga completada:', buffer.length, 'bytes'); + + // Guardar archivo + await writeFileAsync(path, buffer); + console.log('[MEDIA-DEBUG] Archivo guardado en:', path); + + // Verificar primeros bytes + const firstBytes = buffer.slice(0, 8).toString('hex'); + console.log('[MEDIA-DEBUG] Primeros 8 bytes:', firstBytes); + + if (firstBytes.startsWith('ffd8')) { + console.log('[MEDIA-DEBUG] ✅ ¡Es JPEG!'); + } else if (firstBytes.startsWith('8950')) { + console.log('[MEDIA-DEBUG] ✅ ¡Es PNG!'); + } else { + console.log('[MEDIA-DEBUG] ❌ No es imagen conocida (probablemente cifrado)'); + } + } else { + console.log('[MEDIA-DEBUG] ❌ No hay URL, usando método tradicional'); + + const stream = await downloadContentFromMessage( + messageContent as any, + type as MediaType + ); + + let buffer = Buffer.from([]); + for await (const chunk of stream) { + buffer = Buffer.concat([buffer, chunk]); + } + + console.log('[MEDIA-DEBUG] Stream recibido:', buffer.length, 'bytes'); + await writeFileAsync(path, buffer); + } + + return path; + + } catch (error) { + console.error('[MEDIA-DEBUG] ❌ ERROR CRÍTICO:', error.message); + console.error(error.stack); + + // Guardar error + try { + await writeFileAsync(path, Buffer.from(`ERROR: ${error.message}`)); + } catch (e) { + // Ignorar + } + + return path; + } +}; + +// ========== FUNCIÓN HKDF SIMPLIFICADA ========== +function deriveWhatsAppKeys(mediaKey: Buffer): Buffer { + const crypto = require('crypto'); + + // Parámetros para WhatsApp + const salt = Buffer.alloc(32); // Salt vacío + const info = 'WhatsApp Image Keys'; // Info string + const length = 112; // Longitud total requerida + + // Extraer PRK (Pseudorandom Key) + const prk = crypto.createHmac('sha256', salt).update(mediaKey).digest(); + + // Expandir + const infoBuffer = Buffer.from(info); + const blocks = Math.ceil(length / 32); + + let t = Buffer.alloc(0); + let result = Buffer.alloc(0); + + for (let i = 0; i < blocks; i++) { + const tBuf = Buffer.concat([ + t, + infoBuffer, + Buffer.from([i + 1]) + ]); + t = crypto.createHmac('sha256', prk).update(tBuf).digest(); + result = Buffer.concat([result, t]); + } + + return result.slice(0, length); +} +const verifyQuotedMessage = async ( + msg: proto.IWebMessageInfo +): Promise => { + if (!msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + return null; + } + + const quoted = msg.message.extendedTextMessage.contextInfo; + + const quotedMessage = await Message.findOne({ + where: { id: quoted.stanzaId } + }); + + return quotedMessage; +}; + +const verifyMediaMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + try { + const mediaPath = join(__dirname, "..", "..", "..", "public"); + const filename = `${messageData.id}.${getTypeMessage(msg).split("Message")[0]}`; + const path = join(mediaPath, filename); + + await downloadMedia(msg, path); + messageData.mediaUrl = filename; + } catch (err) { + Sentry.captureException(err); + logger.error(err); + } + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +const verifyMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +// ========== FUNCIÓN isValidMsg ========== +const isValidMsg = (msg: proto.IWebMessageInfo): boolean => { + if (msg.key.remoteJid === "status@broadcast") return false; + + const msgStubType = msg.messageStubType; + const isStubMessage = [ + WAMessageStubType.REVOKE, + WAMessageStubType.E2E_DEVICE_CHANGED, + ].includes(msgStubType as number); + + return !isStubMessage; +}; + + + +const verifyQueue = async ( + wbot: Session, + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const { queues } = await ShowWhatsAppService(wbot.id!); + + if (queues.length === 0) { + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { userId: 1 } // Asignar a un usuario por defecto + }); + return; + } + + // Lógica para asignar a una cola + // Aquí puedes implementar tu lógica de asignación + const selectedQueue = queues[0]; // Por ahora toma la primera cola + + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { + queueId: selectedQueue.id, + status: "pending" + } + }); +}; +// ========== FIN FUNCIONES FALTANTES ========== + +// ========== VERIFYCONTACT ACTUALIZADO ========== +const verifyContact = async (msgContact: IMe, wbot: Session): Promise => { + let profilePicUrl: string; + try { + profilePicUrl = await wbot.profilePictureUrl(msgContact.id); + } catch { + profilePicUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/nopicture.png`; + } + + let phoneNumber = msgContact.number || msgContact.id.replace(/\D/g, ""); + + console.log('👤 VERIFICANDO CONTACTO:'); + console.log(' ID recibido:', msgContact.id); + console.log(' Número en msgContact:', msgContact.number); + console.log(' Es LID?:', msgContact.esLid ? 'SÍ' : 'NO'); + console.log(' LID original:', msgContact.lid || 'N/A'); + console.log(' JID original:', msgContact.originalJid || 'N/A'); + + // Si tenemos un LID, intentar obtener el número real + if (msgContact.lid) { + try { + console.log('🔍 Contacto tiene LID, verificando número real:', msgContact.lid); + const results = await wbot.onWhatsApp(msgContact.lid); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ LID convertido a número en verifyContact:', phoneNumber); + } + } catch (error) { + console.error('❌ Error obteniendo número de LID:', error); + } + } + + // Si el ID mismo es un LID pero no lo capturamos antes + if (!msgContact.lid && (msgContact.id.includes(':') || (msgContact.id.includes('.') && !msgContact.id.includes('@s.whatsapp.net')))) { + try { + console.log('🔍 ID parece LID, verificando:', msgContact.id); + const results = await wbot.onWhatsApp(msgContact.id); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ ID LID convertido a número:', phoneNumber); + } + } catch (error) { + console.error('❌ Error verificando ID LID:', error); + } + } + + // OBTENER EL companyId DEL WHATSAPP O USAR UN VALOR POR DEFECTO + // Esto depende de cómo tengas configurada tu aplicación + const companyId = wbot.id ? 1 : 1; // Ajusta esta lógica según tu aplicación + + const contactData = { + name: msgContact?.name || phoneNumber, + number: phoneNumber, + profilePicUrl, + isGroup: msgContact.id.includes("g.us"), + companyId: companyId, // ← AÑADIDO + lid: msgContact.lid || null, + originalJid: msgContact.originalJid || null, + whatsappId: wbot.id + }; + + console.log('📝 DATOS FINALES DEL CONTACTO:'); + console.log(' Nombre:', contactData.name); + console.log(' Número:', contactData.number); + console.log(' LID guardado:', contactData.lid); + console.log(' Company ID:', contactData.companyId); + + const contact = await CreateOrUpdateContactService(contactData); + return contact; +}; +// ========== FIN VERIFYCONTACT ACTUALIZADO ========== + +const handleMessage = async ( + msg: proto.IWebMessageInfo, + wbot: Session +): Promise => { + if (!isValidMsg(msg)) return; + + try { + let msgContact: IMe; + let groupContact: Contact | undefined; + + const isGroup = msg.key.remoteJid?.endsWith("@g.us") || false; + + const msgIsGroupBlock = await Setting.findOne({ + where: { key: "CheckMsgIsGroup" } + }); + + const bodyMessage = getBodyMessage(msg); + const msgType = getTypeMessage(msg); + + if (msgType === "protocolMessage") return; + + const hasMedia = + msg.message?.audioMessage || + msg.message?.imageMessage || + msg.message?.videoMessage || + msg.message?.documentMessage || + msg.message?.stickerMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; + + if (msg.key.fromMe) { + if (/\u200e/.test(bodyMessage || "")) return; + + if ( + !hasMedia && + msgType !== "conversation" && + msgType !== "extendedTextMessage" && + msgType !== "vcard" && + msgType !== "reactionMessage" && + msgType !== "ephemeralMessage" && + msgType !== "protocolMessage" && + msgType !== "viewOnceMessage" + ) + return; + + msgContact = await getContactMessage(msg, wbot); + } else { + msgContact = await getContactMessage(msg, wbot); + } + + if (msgIsGroupBlock?.value === "enabled" && isGroup) return; + + if (isGroup) { + try { + const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid!); + const msgGroupContact = { + id: grupoMeta.id, + name: grupoMeta.subject || "" + }; + groupContact = await verifyContact(msgGroupContact, wbot); + } catch (error) { + console.error('Error obteniendo metadatos del grupo:', error); + } + } + + const whatsapp = await ShowWhatsAppService(wbot.id!); + + // Obtener conteo de mensajes no leídos + const count = wbot.store && wbot.store.chats ? + wbot.store.chats.get(msg.key.remoteJid || msg.key.participant) : + undefined; + + const unreadMessages = msg.key.fromMe ? 0 : count?.unreadCount || 1; + + const contact = await verifyContact(msgContact, wbot); + + // ========== DIAGNÓSTICO COMPLETO ========== + console.log('\n📊 DIAGNÓSTICO COMPLETO DEL MENSAJE:'); + console.log('======================================'); + console.log('📱 DATOS CRUDOS:'); + console.log(' RemoteJid:', msg.key.remoteJid); + console.log(' Participant:', msg.key.participant); + console.log(' FromMe:', msg.key.fromMe); + console.log(' PushName:', msg.pushName); + console.log(' Tipo de mensaje:', msgType); + + console.log('\n🔍 ANÁLISIS LID:'); + console.log(' JID analizado:', msg.key.remoteJid); + if (msg.key.remoteJid?.includes(':')) { + const partes = msg.key.remoteJid.split(':'); + console.log(' Parte 1 (posible número):', partes[0]); + console.log(' Parte 2 (identificador):', partes[1]); + } + + console.log('\n👤 DATOS DEL CONTACTO:'); + console.log(' Número en contacto BD:', contact.number); + console.log(' Contact ID:', contact.id); + console.log(' Nombre:', contact.name); + console.log(' LID guardado:', contact.lid); + console.log('======================================\n'); + + const ticket = await FindOrCreateTicketService({ + contact, + whatsappId: wbot.id!, + unreadMessages, + groupContact, + channel: "whatsapp" + }); + + if (hasMedia) { + await verifyMediaMessage(msg, ticket, contact); + } else { + await verifyMessage(msg, ticket, contact); + } + + const checkExpedient = await hourExpedient(); + if (checkExpedient) { + if ( + !ticket.queue && + !isGroup && + !msg.key.fromMe && + !ticket.userId && + whatsapp.queues.length >= 1 + ) { + await verifyQueue(wbot, msg, ticket, contact); + } + + if (ticket.queue && ticket.queueId) { + if (!ticket.user) { + await sayChatbot(ticket.queueId, wbot, ticket, contact, msg); + } + } + } else { + const getLastMessageFromMe = await Message.findOne({ + where: { + ticketId: ticket.id, + fromMe: true + }, + order: [["createdAt", "DESC"]] + }); + + if ( + getLastMessageFromMe?.body === + formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact) + ) + return; + + const body = formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } catch (err) { + console.error('❌ Error en handleMessage:', err); + Sentry.captureException(err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } +}; + +const wbotMessageListener = (wbot: Session): void => { + wbot.ev.on("messages.upsert", async (messageUpsert: ImessageUpsert) => { + const messages = messageUpsert.messages; + + if (messages.length === 0) return; + + const message = messages[0]; + + if (message.key.id.startsWith("BAE5") && message.key.fromMe) return; + + handleMessage(message, wbot); + }); + + wbot.ev.on("messages.update", (messageUpdate: WAMessageUpdate[]) => { + if (messageUpdate.length === 0) return; + + messageUpdate.forEach(async (update) => { + if (!update.key?.id || !update.update) return; + + const message = await Message.findOne({ where: { id: update.key.id } }); + if (!message) return; + + if (update.update?.status) { + await message.update({ status: update.update.status }); + const io = getIO(); + io.to(message.ticketId.toString()).emit("appMessage", { + action: "update", + message + }); + } + }); + }); +}; + + +export { + wbotMessageListener, + handleMessage, + verifyMessage, + getBodyMessage, + verifyMediaMessage, + isValidMsg, // ← ¡AGREGADA! + verifyQueue, + downloadMedia, // ← ¡AGREGAR TAMBIÉN ESTA! + getContactMessage, // ← Y ESTA SI NO ESTÁ + // Agrega cualquier otra función que se use externamente +}; \ No newline at end of file diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts.bak b/backend/src/services/WbotServices/wbotMessageListener.ts.bak new file mode 100644 index 0000000..a0230c5 --- /dev/null +++ b/backend/src/services/WbotServices/wbotMessageListener.ts.bak @@ -0,0 +1,755 @@ +import { join } from "path"; +import { promisify } from "util"; +import { writeFile } from "fs"; +import * as Sentry from "@sentry/node"; + +import { + downloadContentFromMessage, + jidNormalizedUser, + MediaType, + MessageUpsertType, + proto, + WAMessage, + WAMessageUpdate, + WASocket, + getContentType, + extractMessageContent, + WAMessageStubType, + AnyMessageContent +} from "@whiskeysockets/baileys"; + +import Contact from "../../models/Contact"; +import Ticket from "../../models/Ticket"; +import Message from "../../models/Message"; + +import { getIO } from "../../libs/socket"; +import CreateMessageService from "../MessageServices/CreateMessageService"; +import { logger } from "../../utils/logger"; +import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService"; +import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; +import formatBody from "../../helpers/Mustache"; +import { Store } from "../../libs/store"; +import Setting from "../../models/Setting"; +import { debounce } from "../../helpers/Debounce"; +import UpdateTicketService from "../TicketServices/UpdateTicketService"; +import { sayChatbot } from "./ChatBotListener"; +import hourExpedient from "./hourExpedient"; +import { NormalizadorMejorado } from "../../utils/NormalizadorMejorado"; // IMPORTAR EL NUEVO NORMALIZADOR + +type Session = WASocket & { + id?: number; + store?: Store; +}; + +interface ImessageUpsert { + messages: proto.IWebMessageInfo[]; + type: MessageUpsertType; +} + +// ========== INTERFACES ACTUALIZADAS ========== +interface IMe { + name: string; + id: string; + lid?: string; + esLid?: boolean; + esLidValido?: boolean; // NUEVO: indica si el LID pudo convertirse a número real + number?: string; + originalJid?: string; +} + +const writeFileAsync = promisify(writeFile); + +// Instancia global del normalizador +const normalizador = new NormalizadorMejorado(); + +// ========== FUNCIÓN MEJORADA PARA OBTENER NÚMERO REAL ========== + +async function obtenerNumeroRealDeMensaje(wbot: Session, message: proto.IWebMessageInfo): Promise<{ + numeroReal: string; + jidReal: string; + esLid: boolean; + esLidValido: boolean; + lidOriginal?: string; +}> { + let jid = message.key.remoteJid || ''; + + if (message.key.participant) { + jid = message.key.participant; + } + + if (!jid) { + return { + numeroReal: '', + jidReal: '', + esLid: false, + esLidValido: false + }; + } + + console.log('🔍 ANALIZANDO JID:', jid); + console.log(' Tipo:', jid.includes('@lid') ? 'LID' : jid.includes('@s.whatsapp.net') ? 'Tradicional' : 'Desconocido'); + + // DETECCIÓN MEJORADA DE LIDs + const esLid = jid.includes('@lid') || jid.includes(':') || (jid.includes('.') && !jid.includes('@s.whatsapp.net')); + + if (esLid && jid.includes('@lid')) { + console.log('✅ DETECTADO LID CON @lid:', jid); + + try { + // Intentar obtener número real con onWhatsApp + const results = await wbot.onWhatsApp(jid); + + if (results && results.length > 0 && results[0]?.exists) { + // ¡ÉXITO! Tenemos el número real + const numeroReal = results[0].jid.split('@')[0]; + const jidReal = results[0].jid; + + console.log('🎯 LID CONVERTIDO A NÚMERO REAL:'); + console.log(' LID original:', jid); + console.log(' Número real:', numeroReal); + console.log(' JID real:', jidReal); + + return { + numeroReal, + jidReal, + esLid: true, + esLidValido: true, + lidOriginal: jid + }; + } else { + // onWhatsApp falló o el LID no existe + console.log('⚠️ LID NO PUDO CONVERTIRSE:', jid); + console.log(' onWhatsApp no devolvió resultados válidos'); + + // Extraer la parte numérica del LID para placeholder + const lidSinSuffix = jid.split('@')[0]; + const placeholder = `LID_${lidSinSuffix.substring(0, 10)}`; // Limitar longitud + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } catch (error) { + console.error('❌ Error en onWhatsApp para LID:', error); + + // Fallback: usar placeholder seguro + const lidSinSuffix = jid.split('@')[0].replace(/\D/g, ''); + const placeholder = lidSinSuffix ? `LID_${lidSinSuffix.substring(0, 10)}` : 'LID_DESCONOCIDO'; + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } + + // Si NO es LID con @lid, usar el normalizador existente + try { + const numeroReal = await normalizador.obtenerNumeroReal(wbot, jid); + + console.log('✅ RESULTADO (no LID @lid):'); + console.log(' JID original:', jid); + console.log(' Número obtenido:', numeroReal); + console.log(' Es LID?:', esLid ? 'SÍ' : 'NO'); + + return { + numeroReal, + jidReal: numeroReal + '@s.whatsapp.net', + esLid, + esLidValido: false, + lidOriginal: esLid ? jid : undefined + }; + + } catch (error) { + console.error('❌ Error obteniendo número real:', error); + + // Fallback + let numeroFallback = jid.split('@')[0]; + if (jid.includes(':')) { + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + numeroFallback = match[1]; + } + } + + return { + numeroReal: numeroFallback, + jidReal: numeroFallback + '@s.whatsapp.net', + esLid: false, + esLidValido: false + }; + } +} +// ========== FIN FUNCIÓN MEJORADA ========== + +const getTypeMessage = (msg: proto.IWebMessageInfo): string => { + return getContentType(msg.message); +}; + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +const getBodyButton = (message: proto.IWebMessageInfo): string => { + if (message.message?.templateMessage?.hydratedFourRowTemplate) { + return ( + message.message.templateMessage.hydratedFourRowTemplate.hydratedContentText || + "" + ); + } + if (message.message?.templateMessage?.hydratedTemplate) { + return ( + message.message.templateMessage.hydratedTemplate.hydratedContentText || + "" + ); + } + return ""; +}; + + +const msgLocation = (message: proto.IWebMessageInfo): proto.Message.ILocationMessage | null => { + return message.message?.locationMessage || null; +}; + +const getBodyMessage = (msg: proto.IWebMessageInfo): string => { + const extract = extractMessageContent(msg.message); + + if (extract) { + if (extract.conversation) { + return extract.conversation; + } + if (extract.extendedTextMessage?.text) { + return extract.extendedTextMessage.text; + } + if (extract.imageMessage?.caption) { + return extract.imageMessage.caption; + } + if (extract.videoMessage?.caption) { + return extract.videoMessage.caption; + } + if (extract.documentMessage?.caption) { + return extract.documentMessage.caption; + } + } + + const bodyButton = getBodyButton(msg); + if (bodyButton) return bodyButton; + + return ""; +}; +// ========== FIN FUNCIONES FALTANTES ========== + +const getMeSocket = (wbot: Session): IMe => { + return { + id: jidNormalizedUser((wbot as WASocket).user.id), + name: (wbot as WASocket).user.name || "" + }; +}; + +const getSenderMessage = (msg: proto.IWebMessageInfo, wbot: Session): string => { + const me = getMeSocket(wbot); + if (msg.key.fromMe) return me.id; + + const senderId = msg.participant || msg.key.participant || msg.key.remoteJid || undefined; + return senderId ? jidNormalizedUser(senderId) : ""; +}; + +// ========== FUNCIÓN COMPLETAMENTE REESCRITA PARA MANEJAR LIDs ========== +const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session): Promise => { + const isGroup = msg.key.remoteJid?.includes("g.us") || false; + + if (isGroup) { + return { + id: getSenderMessage(msg, wbot), + name: msg.pushName || "" + }; + } + + // Para mensajes individuales, usar nuestro normalizador + try { + const { numeroReal, jidReal, esLid, lidOriginal } = await obtenerNumeroRealDeMensaje(wbot, msg); + + if (!numeroReal) { + throw new Error('No se pudo obtener número real'); + } + + return { + id: jidReal, + name: msg.key.fromMe ? numeroReal : msg.pushName || numeroReal, + number: numeroReal, + lid: lidOriginal, + esLid, + originalJid: msg.key.remoteJid || '' // Guardar el JID original + }; + + } catch (error) { + console.error('❌ Error en getContactMessage:', error); + + // Fallback extremo + const rawNumber = msg.key.remoteJid?.replace(/\D/g, "") || ""; + return { + id: msg.key.remoteJid || "", + name: msg.key.fromMe ? rawNumber : msg.pushName || rawNumber, + number: rawNumber + }; + } +}; +// ========== FIN FUNCIÓN REESCRITA ========== + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +const downloadMedia = async ( + msg: proto.IWebMessageInfo, + path: string +): Promise => { + const type = getTypeMessage(msg); + const messageContent = msg.message?.[type as keyof typeof msg.message]; + + if (!messageContent || typeof messageContent !== "object") { + throw new Error("Invalid message content"); + } + + const stream = await downloadContentFromMessage( + messageContent as any, + type as MediaType + ); + + let buffer = Buffer.from([]); + for await (const chunk of stream) { + buffer = Buffer.concat([buffer, chunk]); + } + + await writeFileAsync(path, buffer); + return path; +}; + +const isValidMsg = (msg: proto.IWebMessageInfo): boolean => { + if (msg.key.remoteJid === "status@broadcast") return false; + + const msgStubType = msg.messageStubType; + const isStubMessage = [ + WAMessageStubType.REVOKE, + WAMessageStubType.E2E_DEVICE_CHANGED, + ].includes(msgStubType as number); + + return !isStubMessage; +}; + +const verifyQuotedMessage = async ( + msg: proto.IWebMessageInfo +): Promise => { + if (!msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + return null; + } + + const quoted = msg.message.extendedTextMessage.contextInfo; + + const quotedMessage = await Message.findOne({ + where: { id: quoted.stanzaId } + }); + + return quotedMessage; +}; + +const verifyMediaMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + try { + const mediaPath = join(__dirname, "..", "..", "..", "public"); + const filename = `${messageData.id}.${getTypeMessage(msg).split("Message")[0]}`; + const path = join(mediaPath, filename); + + await downloadMedia(msg, path); + messageData.mediaUrl = filename; + } catch (err) { + Sentry.captureException(err); + logger.error(err); + } + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +const verifyMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +const verifyQueue = async ( + wbot: Session, + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const { queues } = await ShowWhatsAppService(wbot.id!); + + if (queues.length === 0) { + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { userId: 1 } // Asignar a un usuario por defecto + }); + return; + } + + // Lógica para asignar a una cola + // Aquí puedes implementar tu lógica de asignación + const selectedQueue = queues[0]; // Por ahora toma la primera cola + + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { + queueId: selectedQueue.id, + status: "pending" + } + }); +}; +// ========== FIN FUNCIONES FALTANTES ========== + +// ========== VERIFYCONTACT ACTUALIZADO ========== +const verifyContact = async (msgContact: IMe, wbot: Session): Promise => { + let profilePicUrl: string; + try { + profilePicUrl = await wbot.profilePictureUrl(msgContact.id); + } catch { + profilePicUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/nopicture.png`; + } + + let phoneNumber = msgContact.number || msgContact.id.replace(/\D/g, ""); + + console.log('👤 VERIFICANDO CONTACTO:'); + console.log(' ID recibido:', msgContact.id); + console.log(' Número en msgContact:', msgContact.number); + console.log(' Es LID?:', msgContact.esLid ? 'SÍ' : 'NO'); + console.log(' LID original:', msgContact.lid || 'N/A'); + console.log(' JID original:', msgContact.originalJid || 'N/A'); + + // Si tenemos un LID, intentar obtener el número real + if (msgContact.lid) { + try { + console.log('🔍 Contacto tiene LID, verificando número real:', msgContact.lid); + const results = await wbot.onWhatsApp(msgContact.lid); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ LID convertido a número en verifyContact:', phoneNumber); + } + } catch (error) { + console.error('❌ Error obteniendo número de LID:', error); + } + } + + // Si el ID mismo es un LID pero no lo capturamos antes + if (!msgContact.lid && (msgContact.id.includes(':') || (msgContact.id.includes('.') && !msgContact.id.includes('@s.whatsapp.net')))) { + try { + console.log('🔍 ID parece LID, verificando:', msgContact.id); + const results = await wbot.onWhatsApp(msgContact.id); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ ID LID convertido a número:', phoneNumber); + } + } catch (error) { + console.error('❌ Error verificando ID LID:', error); + } + } + + // OBTENER EL companyId DEL WHATSAPP O USAR UN VALOR POR DEFECTO + // Esto depende de cómo tengas configurada tu aplicación + const companyId = wbot.id ? 1 : 1; // Ajusta esta lógica según tu aplicación + + const contactData = { + name: msgContact?.name || phoneNumber, + number: phoneNumber, + profilePicUrl, + isGroup: msgContact.id.includes("g.us"), + companyId: companyId, // ← AÑADIDO + lid: msgContact.lid || null, + originalJid: msgContact.originalJid || null, + whatsappId: wbot.id + }; + + console.log('📝 DATOS FINALES DEL CONTACTO:'); + console.log(' Nombre:', contactData.name); + console.log(' Número:', contactData.number); + console.log(' LID guardado:', contactData.lid); + console.log(' Company ID:', contactData.companyId); + + const contact = await CreateOrUpdateContactService(contactData); + return contact; +}; +// ========== FIN VERIFYCONTACT ACTUALIZADO ========== + +const handleMessage = async ( + msg: proto.IWebMessageInfo, + wbot: Session +): Promise => { + if (!isValidMsg(msg)) return; + + try { + let msgContact: IMe; + let groupContact: Contact | undefined; + + const isGroup = msg.key.remoteJid?.endsWith("@g.us") || false; + + const msgIsGroupBlock = await Setting.findOne({ + where: { key: "CheckMsgIsGroup" } + }); + + const bodyMessage = getBodyMessage(msg); + const msgType = getTypeMessage(msg); + + if (msgType === "protocolMessage") return; + + const hasMedia = + msg.message?.audioMessage || + msg.message?.imageMessage || + msg.message?.videoMessage || + msg.message?.documentMessage || + msg.message?.stickerMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; + + if (msg.key.fromMe) { + if (/\u200e/.test(bodyMessage || "")) return; + + if ( + !hasMedia && + msgType !== "conversation" && + msgType !== "extendedTextMessage" && + msgType !== "vcard" && + msgType !== "reactionMessage" && + msgType !== "ephemeralMessage" && + msgType !== "protocolMessage" && + msgType !== "viewOnceMessage" + ) + return; + + msgContact = await getContactMessage(msg, wbot); + } else { + msgContact = await getContactMessage(msg, wbot); + } + + if (msgIsGroupBlock?.value === "enabled" && isGroup) return; + + if (isGroup) { + try { + const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid!); + const msgGroupContact = { + id: grupoMeta.id, + name: grupoMeta.subject || "" + }; + groupContact = await verifyContact(msgGroupContact, wbot); + } catch (error) { + console.error('Error obteniendo metadatos del grupo:', error); + } + } + + const whatsapp = await ShowWhatsAppService(wbot.id!); + + // Obtener conteo de mensajes no leídos + const count = wbot.store && wbot.store.chats ? + wbot.store.chats.get(msg.key.remoteJid || msg.key.participant) : + undefined; + + const unreadMessages = msg.key.fromMe ? 0 : count?.unreadCount || 1; + + const contact = await verifyContact(msgContact, wbot); + + // ========== DIAGNÓSTICO COMPLETO ========== + console.log('\n📊 DIAGNÓSTICO COMPLETO DEL MENSAJE:'); + console.log('======================================'); + console.log('📱 DATOS CRUDOS:'); + console.log(' RemoteJid:', msg.key.remoteJid); + console.log(' Participant:', msg.key.participant); + console.log(' FromMe:', msg.key.fromMe); + console.log(' PushName:', msg.pushName); + console.log(' Tipo de mensaje:', msgType); + + console.log('\n🔍 ANÁLISIS LID:'); + console.log(' JID analizado:', msg.key.remoteJid); + if (msg.key.remoteJid?.includes(':')) { + const partes = msg.key.remoteJid.split(':'); + console.log(' Parte 1 (posible número):', partes[0]); + console.log(' Parte 2 (identificador):', partes[1]); + } + + console.log('\n👤 DATOS DEL CONTACTO:'); + console.log(' Número en contacto BD:', contact.number); + console.log(' Contact ID:', contact.id); + console.log(' Nombre:', contact.name); + console.log(' LID guardado:', contact.lid); + console.log('======================================\n'); + + const ticket = await FindOrCreateTicketService({ + contact, + whatsappId: wbot.id!, + unreadMessages, + groupContact, + channel: "whatsapp" + }); + + if (hasMedia) { + await verifyMediaMessage(msg, ticket, contact); + } else { + await verifyMessage(msg, ticket, contact); + } + + const checkExpedient = await hourExpedient(); + if (checkExpedient) { + if ( + !ticket.queue && + !isGroup && + !msg.key.fromMe && + !ticket.userId && + whatsapp.queues.length >= 1 + ) { + await verifyQueue(wbot, msg, ticket, contact); + } + + if (ticket.queue && ticket.queueId) { + if (!ticket.user) { + await sayChatbot(ticket.queueId, wbot, ticket, contact, msg); + } + } + } else { + const getLastMessageFromMe = await Message.findOne({ + where: { + ticketId: ticket.id, + fromMe: true + }, + order: [["createdAt", "DESC"]] + }); + + if ( + getLastMessageFromMe?.body === + formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact) + ) + return; + + const body = formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } catch (err) { + console.error('❌ Error en handleMessage:', err); + Sentry.captureException(err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } +}; + +const wbotMessageListener = (wbot: Session): void => { + wbot.ev.on("messages.upsert", async (messageUpsert: ImessageUpsert) => { + const messages = messageUpsert.messages; + + if (messages.length === 0) return; + + const message = messages[0]; + + if (message.key.id.startsWith("BAE5") && message.key.fromMe) return; + + handleMessage(message, wbot); + }); + + wbot.ev.on("messages.update", (messageUpdate: WAMessageUpdate[]) => { + if (messageUpdate.length === 0) return; + + messageUpdate.forEach(async (update) => { + if (!update.key?.id || !update.update) return; + + const message = await Message.findOne({ where: { id: update.key.id } }); + if (!message) return; + + if (update.update?.status) { + await message.update({ status: update.update.status }); + const io = getIO(); + io.to(message.ticketId.toString()).emit("appMessage", { + action: "update", + message + }); + } + }); + }); +}; + + +export { + wbotMessageListener, + handleMessage, + verifyMessage, + getBodyMessage, + verifyMediaMessage, + isValidMsg, + verifyQueue, + // Agrega cualquier otra función que se use externamente +}; diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts.corrupted b/backend/src/services/WbotServices/wbotMessageListener.ts.corrupted new file mode 100644 index 0000000..d7ca9a3 --- /dev/null +++ b/backend/src/services/WbotServices/wbotMessageListener.ts.corrupted @@ -0,0 +1,423 @@ +import { join } from "path"; +import { promisify } from "util"; +import { writeFile } from "fs"; +import * as Sentry from "@sentry/node"; + +import { + downloadContentFromMessage, + jidNormalizedUser, + MediaType, + MessageUpsertType, + proto, + WAMessage, + WAMessageUpdate, + WASocket, + getContentType, + extractMessageContent, + WAMessageStubType, + AnyMessageContent +} from "@whiskeysockets/baileys"; + +import Contact from "../../models/Contact"; +import Ticket from "../../models/Ticket"; +import Message from "../../models/Message"; + +import { getIO } from "../../libs/socket"; +import CreateMessageService from "../MessageServices/CreateMessageService"; +import { logger } from "../../utils/logger"; +import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService"; +import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; +import formatBody from "../../helpers/Mustache"; +import { Store } from "../../libs/store"; +import Setting from "../../models/Setting"; +import { debounce } from "../../helpers/Debounce"; +import UpdateTicketService from "../TicketServices/UpdateTicketService"; +import { sayChatbot } from "./ChatBotListener"; +import hourExpedient from "./hourExpedient"; +import { NormalizadorMejorado } from "../../utils/NormalizadorMejorado"; // IMPORTAR EL NUEVO NORMALIZADOR + +type Session = WASocket & { + id?: number; + store?: Store; +}; + +interface ImessageUpsert { + messages: proto.IWebMessageInfo[]; + type: MessageUpsertType; +} + +interface IMe { + name: string; + id: string; + lid?: string; + esLid?: boolean; + number?: string; + originalJid?: string; // NUEVO: Guardar el JID original +} + +const writeFileAsync = promisify(writeFile); + +// Instancia global del normalizador +const normalizador = new NormalizadorMejorado(); + +// ========== FUNCIÓN MEJORADA PARA OBTENER NÚMERO REAL ========== +async function obtenerNumeroRealDeMensaje(wbot: Session, message: proto.IWebMessageInfo): Promise<{ + numeroReal: string; + jidReal: string; + esLid: boolean; + lidOriginal?: string; +}> { + let jid = message.key.remoteJid || ''; + + if (message.key.participant) { + jid = message.key.participant; + } + + if (!jid) { + return { + numeroReal: '', + jidReal: '', + esLid: false + }; + } + + console.log('🔍 ANALIZANDO JID:', jid); + console.log(' Tipo:', jid.includes(':') ? 'LID' : jid.includes('@s.whatsapp.net') ? 'Tradicional' : 'Desconocido'); + + // Usar el normalizador para obtener el número real + try { + const numeroReal = await normalizador.obtenerNumeroReal(wbot, jid); + const esLid = jid.includes(':') || (jid.includes('.') && !jid.includes('@s.whatsapp.net')); + + console.log('✅ RESULTADO:'); + console.log(' JID original:', jid); + console.log(' Número real obtenido:', numeroReal); + console.log(' Es LID?:', esLid ? 'SÍ' : 'NO'); + + return { + numeroReal, + jidReal: numeroReal + '@s.whatsapp.net', + esLid, + lidOriginal: esLid ? jid : undefined + }; + + } catch (error) { + console.error('❌ Error obteniendo número real:', error); + + // Fallback: intentar extraer número manualmente + let numeroFallback = jid.split('@')[0]; + if (jid.includes(':')) { + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + numeroFallback = match[1]; + } + } + + return { + numeroReal: numeroFallback, + jidReal: numeroFallback + '@s.whatsapp.net', + esLid: false + }; + } +} +// ========== FIN FUNCIÓN MEJORADA ========== + +const getTypeMessage = (msg: proto.IWebMessageInfo): string => { + return getContentType(msg.message); +}; + +// ... (mantén las funciones getBodyButton, msgLocation, getBodyMessage igual que antes) ... + +const getMeSocket = (wbot: Session): IMe => { + return { + id: jidNormalizedUser((wbot as WASocket).user.id), + name: (wbot as WASocket).user.name || "" + }; +}; + +const getSenderMessage = (msg: proto.IWebMessageInfo, wbot: Session): string => { + const me = getMeSocket(wbot); + if (msg.key.fromMe) return me.id; + + const senderId = msg.participant || msg.key.participant || msg.key.remoteJid || undefined; + return senderId ? jidNormalizedUser(senderId) : ""; +}; + +// ========== FUNCIÓN COMPLETAMENTE REESCRITA PARA MANEJAR LIDs ========== +const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session): Promise => { + const isGroup = msg.key.remoteJid?.includes("g.us") || false; + + if (isGroup) { + return { + id: getSenderMessage(msg, wbot), + name: msg.pushName || "" + }; + } + + // Para mensajes individuales, usar nuestro normalizador + try { + const { numeroReal, jidReal, esLid, lidOriginal } = await obtenerNumeroRealDeMensaje(wbot, msg); + + if (!numeroReal) { + throw new Error('No se pudo obtener número real'); + } + + return { + id: jidReal, + name: msg.key.fromMe ? numeroReal : msg.pushName || numeroReal, + number: numeroReal, + lid: lidOriginal, + esLid, + originalJid: msg.key.remoteJid || '' // Guardar el JID original + }; + + } catch (error) { + console.error('❌ Error en getContactMessage:', error); + + // Fallback extremo + const rawNumber = msg.key.remoteJid?.replace(/\D/g, "") || ""; + return { + id: msg.key.remoteJid || "", + name: msg.key.fromMe ? rawNumber : msg.pushName || rawNumber, + number: rawNumber + }; + } +}; +// ========== FIN FUNCIÓN REESCRITA ========== + +// ... (mantén las funciones downloadMedia, verifyContact, verifyQuotedMessage, etc.) ... + +// ========== VERIFYCONTACT ACTUALIZADO ========== +const verifyContact = async (msgContact: IMe, wbot: Session): Promise => { + let profilePicUrl: string; + try { + profilePicUrl = await wbot.profilePictureUrl(msgContact.id); + } catch { + profilePicUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/nopicture.png`; + } + + let phoneNumber = msgContact.number || msgContact.id.replace(/\D/g, ""); + + console.log('👤 VERIFICANDO CONTACTO:'); + console.log(' ID recibido:', msgContact.id); + console.log(' Número en msgContact:', msgContact.number); + console.log(' Es LID?:', msgContact.esLid ? 'SÍ' : 'NO'); + console.log(' LID original:', msgContact.lid || 'N/A'); + console.log(' JID original:', msgContact.originalJid || 'N/A'); + + // Si tenemos un LID, intentar obtener el número real + if (msgContact.lid) { + try { + console.log('🔍 Contacto tiene LID, verificando número real:', msgContact.lid); + const results = await wbot.onWhatsApp(msgContact.lid); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ LID convertido a número en verifyContact:', phoneNumber); + } + } catch (error) { + console.error('❌ Error obteniendo número de LID:', error); + } + } + + // Si el ID mismo es un LID pero no lo capturamos antes + if (!msgContact.lid && (msgContact.id.includes(':') || (msgContact.id.includes('.') && !msgContact.id.includes('@s.whatsapp.net')))) { + try { + console.log('🔍 ID parece LID, verificando:', msgContact.id); + const results = await wbot.onWhatsApp(msgContact.id); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ ID LID convertido a número:', phoneNumber); + } + } catch (error) { + console.error('❌ Error verificando ID LID:', error); + } + } + + const contactData = { + name: msgContact?.name || phoneNumber, + number: phoneNumber, + profilePicUrl, + isGroup: msgContact.id.includes("g.us"), + lid: msgContact.lid || null, + originalJid: msgContact.originalJid || null + }; + + console.log('📝 DATOS FINALES DEL CONTACTO:'); + console.log(' Nombre:', contactData.name); + console.log(' Número:', contactData.number); + console.log(' LID guardado:', contactData.lid); + + const contact = CreateOrUpdateContactService(contactData); + return contact; +}; +// ========== FIN VERIFYCONTACT ACTUALIZADO ========== + +// ... (mantén el resto de las funciones igual) ... + +const handleMessage = async ( + msg: proto.IWebMessageInfo, + wbot: Session +): Promise => { + if (!isValidMsg(msg)) return; + + try { + let msgContact: IMe; + let groupContact: Contact | undefined; + + const isGroup = msg.key.remoteJid?.endsWith("@g.us") || false; + + const msgIsGroupBlock = await Setting.findOne({ + where: { key: "CheckMsgIsGroup" } + }); + + const bodyMessage = getBodyMessage(msg); + const msgType = getTypeMessage(msg); + + if (msgType === "protocolMessage") return; + + const hasMedia = + msg.message?.audioMessage || + msg.message?.imageMessage || + msg.message?.videoMessage || + msg.message?.documentMessage || + msg.message?.stickerMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; + + if (msg.key.fromMe) { + if (/\u200e/.test(bodyMessage || "")) return; + + if ( + !hasMedia && + msgType !== "conversation" && + msgType !== "extendedTextMessage" && + msgType !== "vcard" && + msgType !== "reactionMessage" && + msgType !== "ephemeralMessage" && + msgType !== "protocolMessage" && + msgType !== "viewOnceMessage" + ) + return; + + msgContact = await getContactMessage(msg, wbot); + } else { + msgContact = await getContactMessage(msg, wbot); + } + + if (msgIsGroupBlock?.value === "enabled" && isGroup) return; + + if (isGroup) { + try { + const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid!); + const msgGroupContact = { + id: grupoMeta.id, + name: grupoMeta.subject || "" + }; + groupContact = await verifyContact(msgGroupContact, wbot); + } catch (error) { + console.error('Error obteniendo metadatos del grupo:', error); + } + } + + const whatsapp = await ShowWhatsAppService(wbot.id!); + + // Obtener conteo de mensajes no leídos + const count = wbot.store && wbot.store.chats ? + wbot.store.chats.get(msg.key.remoteJid || msg.key.participant) : + undefined; + + const unreadMessages = msg.key.fromMe ? 0 : count?.unreadCount || 1; + + const contact = await verifyContact(msgContact, wbot); + + // ========== DIAGNÓSTICO COMPLETO ========== + console.log('\n📊 DIAGNÓSTICO COMPLETO DEL MENSAJE:'); + console.log('======================================'); + console.log('📱 DATOS CRUDOS:'); + console.log(' RemoteJid:', msg.key.remoteJid); + console.log(' Participant:', msg.key.participant); + console.log(' FromMe:', msg.key.fromMe); + console.log(' PushName:', msg.pushName); + console.log(' Tipo de mensaje:', msgType); + + console.log('\n🔍 ANÁLISIS LID:'); + console.log(' JID analizado:', msg.key.remoteJid); + if (msg.key.remoteJid?.includes(':')) { + const partes = msg.key.remoteJid.split(':'); + console.log(' Parte 1 (posible número):', partes[0]); + console.log(' Parte 2 (identificador):', partes[1]); + } + + console.log('\n👤 DATOS DEL CONTACTO:'); + console.log(' Número en contacto BD:', contact.number); + console.log(' Contact ID:', contact.id); + console.log(' Nombre:', contact.name); + console.log(' LID guardado:', contact.lid); + console.log('======================================\n'); + + const ticket = await FindOrCreateTicketService({ + contact, + whatsappId: wbot.id!, + unreadMessages, + groupContact, + channel: "whatsapp" + }); + + if (hasMedia) { + await verifyMediaMessage(msg, ticket, contact); + } else { + await verifyMessage(msg, ticket, contact); + } + + const checkExpedient = await hourExpedient(); + if (checkExpedient) { + if ( + !ticket.queue && + !isGroup && + !msg.key.fromMe && + !ticket.userId && + whatsapp.queues.length >= 1 + ) { + await verifyQueue(wbot, msg, ticket, contact); + } + + if (ticket.queue && ticket.queueId) { + if (!ticket.user) { + await sayChatbot(ticket.queueId, wbot, ticket, contact, msg); + } + } + } else { + const getLastMessageFromMe = await Message.findOne({ + where: { + ticketId: ticket.id, + fromMe: true + }, + order: [["createdAt", "DESC"]] + }); + + if ( + getLastMessageFromMe?.body === + formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact) + ) + return; + + const body = formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } catch (err) { + console.error('❌ Error en handleMessage:', err); + Sentry.captureException(err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } +}; + +// ... (mantén el resto del archivo igual) ... + +export { wbotMessageListener, handleMessage }; \ No newline at end of file diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts.current_with_debug b/backend/src/services/WbotServices/wbotMessageListener.ts.current_with_debug new file mode 100644 index 0000000..ca64347 --- /dev/null +++ b/backend/src/services/WbotServices/wbotMessageListener.ts.current_with_debug @@ -0,0 +1,945 @@ +import { join } from "path"; +import { promisify } from "util"; +import { writeFile } from "fs"; +import * as Sentry from "@sentry/node"; + +import { + downloadContentFromMessage, + jidNormalizedUser, + MediaType, + MessageUpsertType, + proto, + WAMessage, + WAMessageUpdate, + WASocket, + getContentType, + extractMessageContent, + WAMessageStubType, + AnyMessageContent +} from "@whiskeysockets/baileys"; + +import Contact from "../../models/Contact"; +import Ticket from "../../models/Ticket"; +import Message from "../../models/Message"; + +import { getIO } from "../../libs/socket"; +import CreateMessageService from "../MessageServices/CreateMessageService"; +import { logger } from "../../utils/logger"; +import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService"; +import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; +import formatBody from "../../helpers/Mustache"; +import { Store } from "../../libs/store"; +import Setting from "../../models/Setting"; +import { debounce } from "../../helpers/Debounce"; +import UpdateTicketService from "../TicketServices/UpdateTicketService"; +import { sayChatbot } from "./ChatBotListener"; +import hourExpedient from "./hourExpedient"; +import { NormalizadorMejorado } from "../../utils/NormalizadorMejorado"; // IMPORTAR EL NUEVO NORMALIZADOR +const DEBUG_MEDIA = true; // Cambia a false en producción + + +type Session = WASocket & { + id?: number; + store?: Store; +}; + +interface ImessageUpsert { + messages: proto.IWebMessageInfo[]; + type: MessageUpsertType; +} + +// ========== INTERFACES ACTUALIZADAS ========== +interface IMe { + name: string; + id: string; + lid?: string; + esLid?: boolean; + esLidValido?: boolean; // NUEVO: indica si el LID pudo convertirse a número real + number?: string; + originalJid?: string; +} + +const writeFileAsync = promisify(writeFile); + +// Instancia global del normalizador +const normalizador = new NormalizadorMejorado(); + +// ========== FUNCIÓN MEJORADA PARA OBTENER NÚMERO REAL ========== + +async function obtenerNumeroRealDeMensaje(wbot: Session, message: proto.IWebMessageInfo): Promise<{ + numeroReal: string; + jidReal: string; + esLid: boolean; + esLidValido: boolean; + lidOriginal?: string; +}> { + let jid = message.key.remoteJid || ''; + + if (message.key.participant) { + jid = message.key.participant; + } + + if (!jid) { + return { + numeroReal: '', + jidReal: '', + esLid: false, + esLidValido: false + }; + } + + console.log('🔍 ANALIZANDO JID:', jid); + console.log(' Tipo:', jid.includes('@lid') ? 'LID' : jid.includes('@s.whatsapp.net') ? 'Tradicional' : 'Desconocido'); + + // DETECCIÓN MEJORADA DE LIDs + const esLid = jid.includes('@lid') || jid.includes(':') || (jid.includes('.') && !jid.includes('@s.whatsapp.net')); + + if (esLid && jid.includes('@lid')) { + console.log('✅ DETECTADO LID CON @lid:', jid); + + try { + // Intentar obtener número real con onWhatsApp + const results = await wbot.onWhatsApp(jid); + + if (results && results.length > 0 && results[0]?.exists) { + // ¡ÉXITO! Tenemos el número real + const numeroReal = results[0].jid.split('@')[0]; + const jidReal = results[0].jid; + + console.log('🎯 LID CONVERTIDO A NÚMERO REAL:'); + console.log(' LID original:', jid); + console.log(' Número real:', numeroReal); + console.log(' JID real:', jidReal); + + return { + numeroReal, + jidReal, + esLid: true, + esLidValido: true, + lidOriginal: jid + }; + } else { + // onWhatsApp falló o el LID no existe + console.log('⚠️ LID NO PUDO CONVERTIRSE:', jid); + console.log(' onWhatsApp no devolvió resultados válidos'); + + // Extraer la parte numérica del LID para placeholder + const lidSinSuffix = jid.split('@')[0]; + const placeholder = `LID_${lidSinSuffix.substring(0, 10)}`; // Limitar longitud + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } catch (error) { + console.error('❌ Error en onWhatsApp para LID:', error); + + // Fallback: usar placeholder seguro + const lidSinSuffix = jid.split('@')[0].replace(/\D/g, ''); + const placeholder = lidSinSuffix ? `LID_${lidSinSuffix.substring(0, 10)}` : 'LID_DESCONOCIDO'; + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } + + // Si NO es LID con @lid, usar el normalizador existente + try { + const numeroReal = await normalizador.obtenerNumeroReal(wbot, jid); + + console.log('✅ RESULTADO (no LID @lid):'); + console.log(' JID original:', jid); + console.log(' Número obtenido:', numeroReal); + console.log(' Es LID?:', esLid ? 'SÍ' : 'NO'); + + return { + numeroReal, + jidReal: numeroReal + '@s.whatsapp.net', + esLid, + esLidValido: false, + lidOriginal: esLid ? jid : undefined + }; + + } catch (error) { + console.error('❌ Error obteniendo número real:', error); + + // Fallback + let numeroFallback = jid.split('@')[0]; + if (jid.includes(':')) { + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + numeroFallback = match[1]; + } + } + + return { + numeroReal: numeroFallback, + jidReal: numeroFallback + '@s.whatsapp.net', + esLid: false, + esLidValido: false + }; + } +} +// ========== FIN FUNCIÓN MEJORADA ========== + +const getTypeMessage = (msg: proto.IWebMessageInfo): string => { + return getContentType(msg.message); +}; + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +const getBodyButton = (message: proto.IWebMessageInfo): string => { + if (message.message?.templateMessage?.hydratedFourRowTemplate) { + return ( + message.message.templateMessage.hydratedFourRowTemplate.hydratedContentText || + "" + ); + } + if (message.message?.templateMessage?.hydratedTemplate) { + return ( + message.message.templateMessage.hydratedTemplate.hydratedContentText || + "" + ); + } + return ""; +}; + + +const msgLocation = (message: proto.IWebMessageInfo): proto.Message.ILocationMessage | null => { + return message.message?.locationMessage || null; +}; + +const getBodyMessage = (msg: proto.IWebMessageInfo): string => { + const extract = extractMessageContent(msg.message); + + if (extract) { + if (extract.conversation) { + return extract.conversation; + } + if (extract.extendedTextMessage?.text) { + return extract.extendedTextMessage.text; + } + if (extract.imageMessage?.caption) { + return extract.imageMessage.caption; + } + if (extract.videoMessage?.caption) { + return extract.videoMessage.caption; + } + if (extract.documentMessage?.caption) { + return extract.documentMessage.caption; + } + } + + const bodyButton = getBodyButton(msg); + if (bodyButton) return bodyButton; + + return ""; +}; +// ========== FIN FUNCIONES FALTANTES ========== + +const getMeSocket = (wbot: Session): IMe => { + return { + id: jidNormalizedUser((wbot as WASocket).user.id), + name: (wbot as WASocket).user.name || "" + }; +}; + +const getSenderMessage = (msg: proto.IWebMessageInfo, wbot: Session): string => { + const me = getMeSocket(wbot); + if (msg.key.fromMe) return me.id; + + const senderId = msg.participant || msg.key.participant || msg.key.remoteJid || undefined; + return senderId ? jidNormalizedUser(senderId) : ""; +}; + +// ========== FUNCIÓN COMPLETAMENTE REESCRITA PARA MANEJAR LIDs ========== +const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session): Promise => { + const isGroup = msg.key.remoteJid?.includes("g.us") || false; + + if (isGroup) { + return { + id: getSenderMessage(msg, wbot), + name: msg.pushName || "" + }; + } + + // Para mensajes individuales, usar nuestro normalizador + try { + const { numeroReal, jidReal, esLid, lidOriginal } = await obtenerNumeroRealDeMensaje(wbot, msg); + + if (!numeroReal) { + throw new Error('No se pudo obtener número real'); + } + + return { + id: jidReal, + name: msg.key.fromMe ? numeroReal : msg.pushName || numeroReal, + number: numeroReal, + lid: lidOriginal, + esLid, + originalJid: msg.key.remoteJid || '' // Guardar el JID original + }; + + } catch (error) { + console.error('❌ Error en getContactMessage:', error); + + // Fallback extremo + const rawNumber = msg.key.remoteJid?.replace(/\D/g, "") || ""; + return { + id: msg.key.remoteJid || "", + name: msg.key.fromMe ? rawNumber : msg.pushName || rawNumber, + number: rawNumber + }; + } +}; +// ========== FIN FUNCIÓN REESCRITA ========== + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +// ========== FUNCIÓN downloadMedia REESCRITA Y SEGURA ========== +// ========== FUNCIÓN downloadMedia MEJORADA ========== +const downloadMedia = async ( + msg: proto.IWebMessageInfo, + path: string +): Promise => { + console.log('[MEDIA-DEBUG] === INICIANDO DEBUG COMPLETO ==='); + console.log('[MEDIA-DEBUG] Mensaje ID:', msg.key.id); + + try { + const type = getTypeMessage(msg); + const messageContent = msg.message?.[type as keyof typeof msg.message]; + + console.log('[MEDIA-DEBUG] Tipo de mensaje:', type); + + if (!messageContent || typeof messageContent !== "object") { + console.log('[MEDIA-DEBUG] ❌ Contenido inválido'); + throw new Error("Invalid message content"); + } + + // ==================== DEBUG ESTRUCTURAL COMPLETO ==================== + console.log('[MEDIA-DEBUG] === ESTRUCTURA COMPLETA DEL MENSAJE ==='); + + // 1. Verificar si es imageMessage + if (msg.message?.imageMessage) { + const imgMsg = msg.message.imageMessage; + console.log('[MEDIA-DEBUG] ✅ imageMessage encontrado en msg.message'); + console.log('[MEDIA-DEBUG] Propiedades de imageMessage:', Object.keys(imgMsg)); + + // 2. Buscar mediaKey específicamente +if (imgMsg.mediaKey) { + console.log('[MEDIA-DEBUG] 🎯 ¡MEDIAKEY ENCONTRADO EN imageMessage!'); + const mediaKey = imgMsg.mediaKey; + + console.log('[MEDIA-DEBUG] Tipo de mediaKey:', typeof mediaKey); + + if (Buffer.isBuffer(mediaKey)) { + console.log('[MEDIA-DEBUG] MediaKey como Buffer:'); + console.log('[MEDIA-DEBUG] Hex:', mediaKey.toString('hex')); + console.log('[MEDIA-DEBUG] Base64:', mediaKey.toString('base64')); + console.log('[MEDIA-DEBUG] Longitud:', mediaKey.length, 'bytes'); + + // Guardar para referencia + await writeFileAsync('/tmp/mediakey.bin', mediaKey); + console.log('[MEDIA-DEBUG] MediaKey guardado en /tmp/mediakey.bin'); + } else if (mediaKey instanceof Uint8Array) { + const buffer = Buffer.from(mediaKey); + console.log('[MEDIA-DEBUG] MediaKey como Uint8Array:'); + console.log('[MEDIA-DEBUG] Hex:', buffer.toString('hex')); + console.log('[MEDIA-DEBUG] Base64:', buffer.toString('base64')); + console.log('[MEDIA-DEBUG] Longitud:', buffer.length, 'bytes'); +} else if (typeof mediaKey === 'string') { + const mediaKeyStr = mediaKey as string; + console.log('[MEDIA-DEBUG] MediaKey como string:', mediaKeyStr.substring(0, 50) + '...'); + console.log('[MEDIA-DEBUG] Longitud string:', mediaKeyStr.length, 'caracteres'); + + // Intentar decodificar como base64 + try { + const buffer = Buffer.from(mediaKey, 'base64'); + console.log('[MEDIA-DEBUG] Decodificado como base64:'); + console.log('[MEDIA-DEBUG] Hex:', buffer.toString('hex')); + console.log('[MEDIA-DEBUG] Longitud:', buffer.length, 'bytes'); + } catch (e) { + console.log('[MEDIA-DEBUG] No es base64 válido'); + } + } else { + console.log('[MEDIA-DEBUG] Tipo desconocido:', mediaKey); + } +} + + // 3. Mostrar otras propiedades importantes + console.log('[MEDIA-DEBUG] === OTRAS PROPIEDADES IMPORTANTES ==='); + const importantProps = { + url: imgMsg.url, + directPath: imgMsg.directPath, + mimetype: imgMsg.mimetype, + fileSha256: imgMsg.fileSha256, + fileEncSha256: imgMsg.fileEncSha256, + mediaKeyTimestamp: imgMsg.mediaKeyTimestamp, + jpegThumbnail: imgMsg.jpegThumbnail ? 'PRESENTE' : 'AUSENTE' + }; + + Object.entries(importantProps).forEach(([key, value]) => { + if (value) { + if (Buffer.isBuffer(value) || value instanceof Uint8Array) { + const buf = Buffer.isBuffer(value) ? value : Buffer.from(value); + console.log(`[MEDIA-DEBUG] ${key}:`, buf.toString('hex').substring(0, 32) + '...'); + } else { + console.log(`[MEDIA-DEBUG] ${key}:`, value); + } + } else { + console.log(`[MEDIA-DEBUG] ${key}: AUSENTE`); + } + }); + } else { + console.log('[MEDIA-DEBUG] ❌ No es imageMessage'); + console.log('[MEDIA-DEBUG] Tipos en msg.message:', Object.keys(msg.message || {})); + } + + // 4. También verificar messageContent (el enfoque original) + console.log('[MEDIA-DEBUG] === messageContent (enfoque original) ==='); + console.log('[MEDIA-DEBUG] Tipo de messageContent:', typeof messageContent); + console.log('[MEDIA-DEBUG] Keys en messageContent:', Object.keys(messageContent)); + + // ==================== DESCARGA DIRECTA ==================== + console.log('[MEDIA-DEBUG] === DESCARGA DIRECTA ==='); + + // Usar URL directa si existe + const mediaMessage = messageContent as any; + const url = mediaMessage.url; + + if (url) { + console.log('[MEDIA-DEBUG] Descargando desde URL...'); + console.log('[MEDIA-DEBUG] URL:', url.substring(0, 80) + '...'); + + const https = require('https'); + + const buffer = await new Promise((resolve, reject) => { + https.get(url, (response) => { + const chunks: Buffer[] = []; + response.on('data', (chunk) => chunks.push(chunk)); + response.on('end', () => resolve(Buffer.concat(chunks))); + response.on('error', reject); + }).on('error', reject); + }); + + console.log('[MEDIA-DEBUG] ✅ Descarga completada:', buffer.length, 'bytes'); + + // Guardar archivo + await writeFileAsync(path, buffer); + console.log('[MEDIA-DEBUG] Archivo guardado en:', path); + + // Verificar primeros bytes + const firstBytes = buffer.slice(0, 8).toString('hex'); + console.log('[MEDIA-DEBUG] Primeros 8 bytes:', firstBytes); + + if (firstBytes.startsWith('ffd8')) { + console.log('[MEDIA-DEBUG] ✅ ¡Es JPEG!'); + } else if (firstBytes.startsWith('8950')) { + console.log('[MEDIA-DEBUG] ✅ ¡Es PNG!'); + } else { + console.log('[MEDIA-DEBUG] ❌ No es imagen conocida (probablemente cifrado)'); + } + } else { + console.log('[MEDIA-DEBUG] ❌ No hay URL, usando método tradicional'); + + const stream = await downloadContentFromMessage( + messageContent as any, + type as MediaType + ); + + let buffer = Buffer.from([]); + for await (const chunk of stream) { + buffer = Buffer.concat([buffer, chunk]); + } + + console.log('[MEDIA-DEBUG] Stream recibido:', buffer.length, 'bytes'); + await writeFileAsync(path, buffer); + } + + return path; + + } catch (error) { + console.error('[MEDIA-DEBUG] ❌ ERROR CRÍTICO:', error.message); + console.error(error.stack); + + // Guardar error + try { + await writeFileAsync(path, Buffer.from(`ERROR: ${error.message}`)); + } catch (e) { + // Ignorar + } + + return path; + } +}; + +// ========== FUNCIÓN HKDF SIMPLIFICADA ========== +function deriveWhatsAppKeys(mediaKey: Buffer): Buffer { + const crypto = require('crypto'); + + // Parámetros para WhatsApp + const salt = Buffer.alloc(32); // Salt vacío + const info = 'WhatsApp Image Keys'; // Info string + const length = 112; // Longitud total requerida + + // Extraer PRK (Pseudorandom Key) + const prk = crypto.createHmac('sha256', salt).update(mediaKey).digest(); + + // Expandir + const infoBuffer = Buffer.from(info); + const blocks = Math.ceil(length / 32); + + let t = Buffer.alloc(0); + let result = Buffer.alloc(0); + + for (let i = 0; i < blocks; i++) { + const tBuf = Buffer.concat([ + t, + infoBuffer, + Buffer.from([i + 1]) + ]); + t = crypto.createHmac('sha256', prk).update(tBuf).digest(); + result = Buffer.concat([result, t]); + } + + return result.slice(0, length); +} +const verifyQuotedMessage = async ( + msg: proto.IWebMessageInfo +): Promise => { + if (!msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + return null; + } + + const quoted = msg.message.extendedTextMessage.contextInfo; + + const quotedMessage = await Message.findOne({ + where: { id: quoted.stanzaId } + }); + + return quotedMessage; +}; + +const verifyMediaMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + try { + const mediaPath = join(__dirname, "..", "..", "..", "public"); + const filename = `${messageData.id}.${getTypeMessage(msg).split("Message")[0]}`; + const path = join(mediaPath, filename); + + await downloadMedia(msg, path); + messageData.mediaUrl = filename; + } catch (err) { + Sentry.captureException(err); + logger.error(err); + } + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +const verifyMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +// ========== FUNCIÓN isValidMsg ========== +const isValidMsg = (msg: proto.IWebMessageInfo): boolean => { + if (msg.key.remoteJid === "status@broadcast") return false; + + const msgStubType = msg.messageStubType; + const isStubMessage = [ + WAMessageStubType.REVOKE, + WAMessageStubType.E2E_DEVICE_CHANGED, + ].includes(msgStubType as number); + + return !isStubMessage; +}; + + + +const verifyQueue = async ( + wbot: Session, + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const { queues } = await ShowWhatsAppService(wbot.id!); + + if (queues.length === 0) { + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { userId: 1 } // Asignar a un usuario por defecto + }); + return; + } + + // Lógica para asignar a una cola + // Aquí puedes implementar tu lógica de asignación + const selectedQueue = queues[0]; // Por ahora toma la primera cola + + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { + queueId: selectedQueue.id, + status: "pending" + } + }); +}; +// ========== FIN FUNCIONES FALTANTES ========== + +// ========== VERIFYCONTACT ACTUALIZADO ========== +const verifyContact = async (msgContact: IMe, wbot: Session): Promise => { + let profilePicUrl: string; + try { + profilePicUrl = await wbot.profilePictureUrl(msgContact.id); + } catch { + profilePicUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/nopicture.png`; + } + + let phoneNumber = msgContact.number || msgContact.id.replace(/\D/g, ""); + + console.log('👤 VERIFICANDO CONTACTO:'); + console.log(' ID recibido:', msgContact.id); + console.log(' Número en msgContact:', msgContact.number); + console.log(' Es LID?:', msgContact.esLid ? 'SÍ' : 'NO'); + console.log(' LID original:', msgContact.lid || 'N/A'); + console.log(' JID original:', msgContact.originalJid || 'N/A'); + + // Si tenemos un LID, intentar obtener el número real + if (msgContact.lid) { + try { + console.log('🔍 Contacto tiene LID, verificando número real:', msgContact.lid); + const results = await wbot.onWhatsApp(msgContact.lid); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ LID convertido a número en verifyContact:', phoneNumber); + } + } catch (error) { + console.error('❌ Error obteniendo número de LID:', error); + } + } + + // Si el ID mismo es un LID pero no lo capturamos antes + if (!msgContact.lid && (msgContact.id.includes(':') || (msgContact.id.includes('.') && !msgContact.id.includes('@s.whatsapp.net')))) { + try { + console.log('🔍 ID parece LID, verificando:', msgContact.id); + const results = await wbot.onWhatsApp(msgContact.id); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ ID LID convertido a número:', phoneNumber); + } + } catch (error) { + console.error('❌ Error verificando ID LID:', error); + } + } + + // OBTENER EL companyId DEL WHATSAPP O USAR UN VALOR POR DEFECTO + // Esto depende de cómo tengas configurada tu aplicación + const companyId = wbot.id ? 1 : 1; // Ajusta esta lógica según tu aplicación + + const contactData = { + name: msgContact?.name || phoneNumber, + number: phoneNumber, + profilePicUrl, + isGroup: msgContact.id.includes("g.us"), + companyId: companyId, // ← AÑADIDO + lid: msgContact.lid || null, + originalJid: msgContact.originalJid || null, + whatsappId: wbot.id + }; + + console.log('📝 DATOS FINALES DEL CONTACTO:'); + console.log(' Nombre:', contactData.name); + console.log(' Número:', contactData.number); + console.log(' LID guardado:', contactData.lid); + console.log(' Company ID:', contactData.companyId); + + const contact = await CreateOrUpdateContactService(contactData); + return contact; +}; +// ========== FIN VERIFYCONTACT ACTUALIZADO ========== + +const handleMessage = async ( + msg: proto.IWebMessageInfo, + wbot: Session +): Promise => { + if (!isValidMsg(msg)) return; + + try { + let msgContact: IMe; + let groupContact: Contact | undefined; + + const isGroup = msg.key.remoteJid?.endsWith("@g.us") || false; + + const msgIsGroupBlock = await Setting.findOne({ + where: { key: "CheckMsgIsGroup" } + }); + + const bodyMessage = getBodyMessage(msg); + const msgType = getTypeMessage(msg); + + if (msgType === "protocolMessage") return; + + const hasMedia = + msg.message?.audioMessage || + msg.message?.imageMessage || + msg.message?.videoMessage || + msg.message?.documentMessage || + msg.message?.stickerMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; + + if (msg.key.fromMe) { + if (/\u200e/.test(bodyMessage || "")) return; + + if ( + !hasMedia && + msgType !== "conversation" && + msgType !== "extendedTextMessage" && + msgType !== "vcard" && + msgType !== "reactionMessage" && + msgType !== "ephemeralMessage" && + msgType !== "protocolMessage" && + msgType !== "viewOnceMessage" + ) + return; + + msgContact = await getContactMessage(msg, wbot); + } else { + msgContact = await getContactMessage(msg, wbot); + } + + if (msgIsGroupBlock?.value === "enabled" && isGroup) return; + + if (isGroup) { + try { + const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid!); + const msgGroupContact = { + id: grupoMeta.id, + name: grupoMeta.subject || "" + }; + groupContact = await verifyContact(msgGroupContact, wbot); + } catch (error) { + console.error('Error obteniendo metadatos del grupo:', error); + } + } + + const whatsapp = await ShowWhatsAppService(wbot.id!); + + // Obtener conteo de mensajes no leídos + const count = wbot.store && wbot.store.chats ? + wbot.store.chats.get(msg.key.remoteJid || msg.key.participant) : + undefined; + + const unreadMessages = msg.key.fromMe ? 0 : count?.unreadCount || 1; + + const contact = await verifyContact(msgContact, wbot); + + // ========== DIAGNÓSTICO COMPLETO ========== + console.log('\n📊 DIAGNÓSTICO COMPLETO DEL MENSAJE:'); + console.log('======================================'); + console.log('📱 DATOS CRUDOS:'); + console.log(' RemoteJid:', msg.key.remoteJid); + console.log(' Participant:', msg.key.participant); + console.log(' FromMe:', msg.key.fromMe); + console.log(' PushName:', msg.pushName); + console.log(' Tipo de mensaje:', msgType); + + console.log('\n🔍 ANÁLISIS LID:'); + console.log(' JID analizado:', msg.key.remoteJid); + if (msg.key.remoteJid?.includes(':')) { + const partes = msg.key.remoteJid.split(':'); + console.log(' Parte 1 (posible número):', partes[0]); + console.log(' Parte 2 (identificador):', partes[1]); + } + + console.log('\n👤 DATOS DEL CONTACTO:'); + console.log(' Número en contacto BD:', contact.number); + console.log(' Contact ID:', contact.id); + console.log(' Nombre:', contact.name); + console.log(' LID guardado:', contact.lid); + console.log('======================================\n'); + + const ticket = await FindOrCreateTicketService({ + contact, + whatsappId: wbot.id!, + unreadMessages, + groupContact, + channel: "whatsapp" + }); + + if (hasMedia) { + await verifyMediaMessage(msg, ticket, contact); + } else { + await verifyMessage(msg, ticket, contact); + } + + const checkExpedient = await hourExpedient(); + if (checkExpedient) { + if ( + !ticket.queue && + !isGroup && + !msg.key.fromMe && + !ticket.userId && + whatsapp.queues.length >= 1 + ) { + await verifyQueue(wbot, msg, ticket, contact); + } + + if (ticket.queue && ticket.queueId) { + if (!ticket.user) { + await sayChatbot(ticket.queueId, wbot, ticket, contact, msg); + } + } + } else { + const getLastMessageFromMe = await Message.findOne({ + where: { + ticketId: ticket.id, + fromMe: true + }, + order: [["createdAt", "DESC"]] + }); + + if ( + getLastMessageFromMe?.body === + formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact) + ) + return; + + const body = formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } catch (err) { + console.error('❌ Error en handleMessage:', err); + Sentry.captureException(err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } +}; + +const wbotMessageListener = (wbot: Session): void => { + wbot.ev.on("messages.upsert", async (messageUpsert: ImessageUpsert) => { + const messages = messageUpsert.messages; + + if (messages.length === 0) return; + + const message = messages[0]; + + if (message.key.id.startsWith("BAE5") && message.key.fromMe) return; + + handleMessage(message, wbot); + }); + + wbot.ev.on("messages.update", (messageUpdate: WAMessageUpdate[]) => { + if (messageUpdate.length === 0) return; + + messageUpdate.forEach(async (update) => { + if (!update.key?.id || !update.update) return; + + const message = await Message.findOne({ where: { id: update.key.id } }); + if (!message) return; + + if (update.update?.status) { + await message.update({ status: update.update.status }); + const io = getIO(); + io.to(message.ticketId.toString()).emit("appMessage", { + action: "update", + message + }); + } + }); + }); +}; + + +export { + wbotMessageListener, + handleMessage, + verifyMessage, + getBodyMessage, + verifyMediaMessage, + isValidMsg, // ← ¡AGREGADA! + verifyQueue, + downloadMedia, // ← ¡AGREGAR TAMBIÉN ESTA! + getContactMessage, // ← Y ESTA SI NO ESTÁ + // Agrega cualquier otra función que se use externamente +}; \ No newline at end of file diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts.current_with_errors b/backend/src/services/WbotServices/wbotMessageListener.ts.current_with_errors new file mode 100644 index 0000000..1e413ea --- /dev/null +++ b/backend/src/services/WbotServices/wbotMessageListener.ts.current_with_errors @@ -0,0 +1,831 @@ +import { join } from "path"; +import { promisify } from "util"; +import { writeFile } from "fs"; +import * as Sentry from "@sentry/node"; + +import { + downloadMediaMessage, + downloadContentFromMessage, + jidNormalizedUser, + MediaType, + MessageUpsertType, + proto, + WAMessage, + WAMessageUpdate, + WASocket, + getContentType, + extractMessageContent, + WAMessageStubType, + AnyMessageContent +} from "@whiskeysockets/baileys"; + +import Contact from "../../models/Contact"; +import Ticket from "../../models/Ticket"; +import Message from "../../models/Message"; + +import { getIO } from "../../libs/socket"; +import CreateMessageService from "../MessageServices/CreateMessageService"; +import { logger } from "../../utils/logger"; +import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService"; +import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; +import formatBody from "../../helpers/Mustache"; +import { Store } from "../../libs/store"; +import Setting from "../../models/Setting"; +import { debounce } from "../../helpers/Debounce"; +import UpdateTicketService from "../TicketServices/UpdateTicketService"; +import { sayChatbot } from "./ChatBotListener"; +import hourExpedient from "./hourExpedient"; +import { NormalizadorMejorado } from "../../utils/NormalizadorMejorado"; // IMPORTAR EL NUEVO NORMALIZADOR +const DEBUG_MEDIA = true; // Cambia a false en producción + + +type Session = WASocket & { + id?: number; + store?: Store; +}; + +interface ImessageUpsert { + messages: proto.IWebMessageInfo[]; + type: MessageUpsertType; +} + +// ========== INTERFACES ACTUALIZADAS ========== +interface IMe { + name: string; + id: string; + lid?: string; + esLid?: boolean; + esLidValido?: boolean; // NUEVO: indica si el LID pudo convertirse a número real + number?: string; + originalJid?: string; +} + +const writeFileAsync = promisify(writeFile); + +// Instancia global del normalizador +const normalizador = new NormalizadorMejorado(); + +// ========== FUNCIÓN MEJORADA PARA OBTENER NÚMERO REAL ========== + +async function obtenerNumeroRealDeMensaje(wbot: Session, message: proto.IWebMessageInfo): Promise<{ + numeroReal: string; + jidReal: string; + esLid: boolean; + esLidValido: boolean; + lidOriginal?: string; +}> { + let jid = message.key.remoteJid || ''; + + if (message.key.participant) { + jid = message.key.participant; + } + + if (!jid) { + return { + numeroReal: '', + jidReal: '', + esLid: false, + esLidValido: false + }; + } + + // console.log('🔍 ANALIZANDO JID:', jid); + // console.log(' Tipo:', jid.includes('@lid') ? 'LID' : jid.includes('@s.whatsapp.net') ? 'Tradicional' : 'Desconocido'); + + // DETECCIÓN MEJORADA DE LIDs + const esLid = jid.includes('@lid') || jid.includes(':') || (jid.includes('.') && !jid.includes('@s.whatsapp.net')); + + if (esLid && jid.includes('@lid')) { + console.log('✅ DETECTADO LID CON @lid:', jid); + + try { + // Intentar obtener número real con onWhatsApp + const results = await wbot.onWhatsApp(jid); + + if (results && results.length > 0 && results[0]?.exists) { + // ¡ÉXITO! Tenemos el número real + const numeroReal = results[0].jid.split('@')[0]; + const jidReal = results[0].jid; + + console.log('🎯 LID CONVERTIDO A NÚMERO REAL:'); + console.log(' LID original:', jid); + console.log(' Número real:', numeroReal); + console.log(' JID real:', jidReal); + + return { + numeroReal, + jidReal, + esLid: true, + esLidValido: true, + lidOriginal: jid + }; + } else { + // onWhatsApp falló o el LID no existe + console.log('⚠️ LID NO PUDO CONVERTIRSE:', jid); + console.log(' onWhatsApp no devolvió resultados válidos'); + + // Extraer la parte numérica del LID para placeholder + const lidSinSuffix = jid.split('@')[0]; + const placeholder = `LID_${lidSinSuffix.substring(0, 10)}`; // Limitar longitud + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } catch (error) { + console.error('❌ Error en onWhatsApp para LID:', error); + + // Fallback: usar placeholder seguro + const lidSinSuffix = jid.split('@')[0].replace(/\D/g, ''); + const placeholder = lidSinSuffix ? `LID_${lidSinSuffix.substring(0, 10)}` : 'LID_DESCONOCIDO'; + + return { + numeroReal: placeholder, + jidReal: `${placeholder}@s.whatsapp.net`, + esLid: true, + esLidValido: false, + lidOriginal: jid + }; + } + } + + // Si NO es LID con @lid, usar el normalizador existente + try { + const numeroReal = await normalizador.obtenerNumeroReal(wbot, jid); + + console.log('✅ RESULTADO (no LID @lid):'); + console.log(' JID original:', jid); + console.log(' Número obtenido:', numeroReal); + console.log(' Es LID?:', esLid ? 'SÍ' : 'NO'); + + return { + numeroReal, + jidReal: numeroReal + '@s.whatsapp.net', + esLid, + esLidValido: false, + lidOriginal: esLid ? jid : undefined + }; + + } catch (error) { + console.error('❌ Error obteniendo número real:', error); + + // Fallback + let numeroFallback = jid.split('@')[0]; + if (jid.includes(':')) { + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + numeroFallback = match[1]; + } + } + + return { + numeroReal: numeroFallback, + jidReal: numeroFallback + '@s.whatsapp.net', + esLid: false, + esLidValido: false + }; + } +} +// ========== FIN FUNCIÓN MEJORADA ========== + +const getTypeMessage = (msg: proto.IWebMessageInfo): string => { + return getContentType(msg.message); +}; + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +const getBodyButton = (message: proto.IWebMessageInfo): string => { + if (message.message?.templateMessage?.hydratedFourRowTemplate) { + return ( + message.message.templateMessage.hydratedFourRowTemplate.hydratedContentText || + "" + ); + } + if (message.message?.templateMessage?.hydratedTemplate) { + return ( + message.message.templateMessage.hydratedTemplate.hydratedContentText || + "" + ); + } + return ""; +}; + + +const msgLocation = (message: proto.IWebMessageInfo): proto.Message.ILocationMessage | null => { + return message.message?.locationMessage || null; +}; + +const getBodyMessage = (msg: proto.IWebMessageInfo): string => { + const extract = extractMessageContent(msg.message); + + if (extract) { + if (extract.conversation) { + return extract.conversation; + } + if (extract.extendedTextMessage?.text) { + return extract.extendedTextMessage.text; + } + if (extract.imageMessage?.caption) { + return extract.imageMessage.caption; + } + if (extract.videoMessage?.caption) { + return extract.videoMessage.caption; + } + if (extract.documentMessage?.caption) { + return extract.documentMessage.caption; + } + } + + const bodyButton = getBodyButton(msg); + if (bodyButton) return bodyButton; + + return ""; +}; +// ========== FIN FUNCIONES FALTANTES ========== + +const getMeSocket = (wbot: Session): IMe => { + return { + id: jidNormalizedUser((wbot as WASocket).user.id), + name: (wbot as WASocket).user.name || "" + }; +}; + +const getSenderMessage = (msg: proto.IWebMessageInfo, wbot: Session): string => { + const me = getMeSocket(wbot); + if (msg.key.fromMe) return me.id; + + const senderId = msg.participant || msg.key.participant || msg.key.remoteJid || undefined; + return senderId ? jidNormalizedUser(senderId) : ""; +}; + +// ========== FUNCIÓN COMPLETAMENTE REESCRITA PARA MANEJAR LIDs ========== +const getContactMessage = async (msg: proto.IWebMessageInfo, wbot: Session): Promise => { + const isGroup = msg.key.remoteJid?.includes("g.us") || false; + + if (isGroup) { + return { + id: getSenderMessage(msg, wbot), + name: msg.pushName || "" + }; + } + + // Para mensajes individuales, usar nuestro normalizador + try { + const { numeroReal, jidReal, esLid, lidOriginal } = await obtenerNumeroRealDeMensaje(wbot, msg); + + if (!numeroReal) { + throw new Error('No se pudo obtener número real'); + } + + return { + id: jidReal, + name: msg.key.fromMe ? numeroReal : msg.pushName || numeroReal, + number: numeroReal, + lid: lidOriginal, + esLid, + originalJid: msg.key.remoteJid || '' // Guardar el JID original + }; + + } catch (error) { + console.error('❌ Error en getContactMessage:', error); + + // Fallback extremo + const rawNumber = msg.key.remoteJid?.replace(/\D/g, "") || ""; + return { + id: msg.key.remoteJid || "", + name: msg.key.fromMe ? rawNumber : msg.pushName || rawNumber, + number: rawNumber + }; + } +}; +// ========== FIN FUNCIÓN REESCRITA ========== + +// ========== FUNCIONES FALTANTES AÑADIDAS ========== +// ========== FUNCIÓN downloadMedia REESCRITA Y SEGURA ========== +// ========== FUNCIÓN downloadMedia MEJORADA ========== +const downloadMedia = async (msg: proto.IWebMessageInfo) => { + let buffer; + try { + // Logger válido para Baileys 6.7.19 + const logger: any = { + level: 'error', + debug: () => {}, + info: () => {}, + warn: () => {}, + error: console.error, + trace: () => {}, + child: () => logger + }; + + buffer = await downloadMediaMessage( + msg, + 'buffer', + {}, + { + logger, + reuploadRequest: async (msgToReupload: proto.IWebMessageInfo) => { + return msgToReupload; + } + } + ); + } catch (err: any) { + console.error('Erro ao baixar mídia:', err.message || err); + return null; + } + + if (!buffer) return null; + + let filename = msg.message?.documentMessage?.fileName || ""; + + const mineType = + msg.message?.imageMessage || + msg.message?.audioMessage || + msg.message?.videoMessage || + msg.message?.stickerMessage || + msg.message?.documentMessage || + msg.message?.documentWithCaptionMessage?.message?.documentMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.videoMessage; + + if (!mineType) return null; + + if (!filename) { + const ext = mineType.mimetype.split("/")[1].split(";")[0]; + filename = `${new Date().getTime()}.${ext}`; + } else { + filename = `${new Date().getTime()}_${filename}`; + } + + return { + data: buffer, + mimetype: mineType.mimetype, + filename + }; +}; + +// ========== FUNCIÓN HKDF SIMPLIFICADA ========== +function deriveWhatsAppKeys(mediaKey: Buffer): Buffer { + const crypto = require('crypto'); + + // Parámetros para WhatsApp + const salt = Buffer.alloc(32); // Salt vacío + const info = 'WhatsApp Image Keys'; // Info string + const length = 112; // Longitud total requerida + + // Extraer PRK (Pseudorandom Key) + const prk = crypto.createHmac('sha256', salt).update(mediaKey).digest(); + + // Expandir + const infoBuffer = Buffer.from(info); + const blocks = Math.ceil(length / 32); + + let t = Buffer.alloc(0); + let result = Buffer.alloc(0); + + for (let i = 0; i < blocks; i++) { + const tBuf = Buffer.concat([ + t, + infoBuffer, + Buffer.from([i + 1]) + ]); + t = crypto.createHmac('sha256', prk).update(tBuf).digest(); + result = Buffer.concat([result, t]); + } + + return result.slice(0, length); +} +const verifyQuotedMessage = async ( + msg: proto.IWebMessageInfo +): Promise => { + if (!msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + return null; + } + + const quoted = msg.message.extendedTextMessage.contextInfo; + + const quotedMessage = await Message.findOne({ + where: { id: quoted.stanzaId } + }); + + return quotedMessage; +}; + +const verifyMediaMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + try { + const mediaPath = join(__dirname, "..", "..", "..", "public"); + const filename = `${messageData.id}.${getTypeMessage(msg).split("Message")[0]}`; + const path = join(mediaPath, filename); + + await downloadMedia(msg); + messageData.mediaUrl = filename; + } catch (err) { + Sentry.captureException(err); + logger.error(err); + } + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +const verifyMessage = async ( + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const io = getIO(); + let quotedMsg: Message | null = null; + + // Verificar si hay mensaje citado + if (msg.message?.extendedTextMessage?.contextInfo?.quotedMessage) { + quotedMsg = await verifyQuotedMessage(msg); + } + + const messageData = { + id: msg.key.id, + ticketId: ticket.id, + contactId: contact.id, + body: getBodyMessage(msg), + fromMe: msg.key.fromMe, + read: msg.key.fromMe, + mediaUrl: null as string | null, + mediaType: getTypeMessage(msg), + quotedMsgId: quotedMsg?.id || null, + timestamp: Number(msg.messageTimestamp) * 1000, + }; + + const newMessage = await CreateMessageService({ messageData }); + + io.to(ticket.id.toString()).emit("appMessage", { + action: "create", + message: newMessage, + ticket, + contact, + }); + + return newMessage; +}; + +// ========== FUNCIÓN isValidMsg ========== +const isValidMsg = (msg: proto.IWebMessageInfo): boolean => { + if (msg.key.remoteJid === "status@broadcast") return false; + + const msgStubType = msg.messageStubType; + const isStubMessage = [ + WAMessageStubType.REVOKE, + WAMessageStubType.E2E_DEVICE_CHANGED, + ].includes(msgStubType as number); + + return !isStubMessage; +}; + + + +const verifyQueue = async ( + wbot: Session, + msg: proto.IWebMessageInfo, + ticket: Ticket, + contact: Contact +): Promise => { + const { queues } = await ShowWhatsAppService(wbot.id!); + + if (queues.length === 0) { + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { userId: 1 } // Asignar a un usuario por defecto + }); + return; + } + + // Lógica para asignar a una cola + // Aquí puedes implementar tu lógica de asignación + const selectedQueue = queues[0]; // Por ahora toma la primera cola + + await UpdateTicketService({ + ticketId: ticket.id, + ticketData: { + queueId: selectedQueue.id, + status: "pending" + } + }); +}; +// ========== FIN FUNCIONES FALTANTES ========== + +// ========== VERIFYCONTACT ACTUALIZADO ========== +const verifyContact = async (msgContact: IMe, wbot: Session): Promise => { + let profilePicUrl: string; + try { + profilePicUrl = await wbot.profilePictureUrl(msgContact.id); + } catch { + profilePicUrl = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/nopicture.png`; + } + + let phoneNumber = msgContact.number || msgContact.id.replace(/\D/g, ""); + + // console.log('👤 VERIFICANDO CONTACTO:'); + // console.log(' ID recibido:', msgContact.id); + // console.log(' Número en msgContact:', msgContact.number); + console.log(' Es LID?:', msgContact.esLid ? 'SÍ' : 'NO'); + console.log(' LID original:', msgContact.lid || 'N/A'); + console.log(' JID original:', msgContact.originalJid || 'N/A'); + + // Si tenemos un LID, intentar obtener el número real + if (msgContact.lid) { + try { + console.log('🔍 Contacto tiene LID, verificando número real:', msgContact.lid); + const results = await wbot.onWhatsApp(msgContact.lid); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ LID convertido a número en verifyContact:', phoneNumber); + } + } catch (error) { + console.error('❌ Error obteniendo número de LID:', error); + } + } + + // Si el ID mismo es un LID pero no lo capturamos antes + if (!msgContact.lid && (msgContact.id.includes(':') || (msgContact.id.includes('.') && !msgContact.id.includes('@s.whatsapp.net')))) { + try { + console.log('🔍 ID parece LID, verificando:', msgContact.id); + const results = await wbot.onWhatsApp(msgContact.id); + if (results && results.length > 0 && results[0]?.exists) { + phoneNumber = results[0].jid.split('@')[0]; + console.log('✅ ID LID convertido a número:', phoneNumber); + } + } catch (error) { + console.error('❌ Error verificando ID LID:', error); + } + } + + // OBTENER EL companyId DEL WHATSAPP O USAR UN VALOR POR DEFECTO + // Esto depende de cómo tengas configurada tu aplicación + const companyId = wbot.id ? 1 : 1; // Ajusta esta lógica según tu aplicación + + const contactData = { + name: msgContact?.name || phoneNumber, + number: phoneNumber, + profilePicUrl, + isGroup: msgContact.id.includes("g.us"), + companyId: companyId, // ← AÑADIDO + lid: msgContact.lid || null, + originalJid: msgContact.originalJid || null, + whatsappId: wbot.id + }; + + // console.log('📝 DATOS FINALES DEL CONTACTO:'); + // console.log(' Nombre:', contactData.name); + // console.log(' Número:', contactData.number); + // console.log(' LID guardado:', contactData.lid); + console.log(' Company ID:', contactData.companyId); + + const contact = await CreateOrUpdateContactService(contactData); + return contact; +}; +// ========== FIN VERIFYCONTACT ACTUALIZADO ========== + +const handleMessage = async ( + msg: proto.IWebMessageInfo, + wbot: Session +): Promise => { + if (!isValidMsg(msg)) return; + + try { + let msgContact: IMe; + let groupContact: Contact | undefined; + + const isGroup = msg.key.remoteJid?.endsWith("@g.us") || false; + + const msgIsGroupBlock = await Setting.findOne({ + where: { key: "CheckMsgIsGroup" } + }); + + const bodyMessage = getBodyMessage(msg); + const msgType = getTypeMessage(msg); + + if (msgType === "protocolMessage") return; + + const hasMedia = + msg.message?.audioMessage || + msg.message?.imageMessage || + msg.message?.videoMessage || + msg.message?.documentMessage || + msg.message?.stickerMessage || + msg.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage; + + if (msg.key.fromMe) { + if (/\u200e/.test(bodyMessage || "")) return; + + if ( + !hasMedia && + msgType !== "conversation" && + msgType !== "extendedTextMessage" && + msgType !== "vcard" && + msgType !== "reactionMessage" && + msgType !== "ephemeralMessage" && + msgType !== "protocolMessage" && + msgType !== "viewOnceMessage" + ) + return; + + msgContact = await getContactMessage(msg, wbot); + } else { + msgContact = await getContactMessage(msg, wbot); + } + + if (msgIsGroupBlock?.value === "enabled" && isGroup) return; + + if (isGroup) { + try { + const grupoMeta = await wbot.groupMetadata(msg.key.remoteJid!); + const msgGroupContact = { + id: grupoMeta.id, + name: grupoMeta.subject || "" + }; + groupContact = await verifyContact(msgGroupContact, wbot); + } catch (error) { + console.error('Error obteniendo metadatos del grupo:', error); + } + } + + const whatsapp = await ShowWhatsAppService(wbot.id!); + + // Obtener conteo de mensajes no leídos + const count = wbot.store && wbot.store.chats ? + wbot.store.chats.get(msg.key.remoteJid || msg.key.participant) : + undefined; + + const unreadMessages = msg.key.fromMe ? 0 : count?.unreadCount || 1; + + const contact = await verifyContact(msgContact, wbot); + + /** ========== DIAGNÓSTICO COMPLETO ========== + console.log('\n📊 DIAGNÓSTICO COMPLETO DEL MENSAJE:'); + console.log('======================================'); + console.log('📱 DATOS CRUDOS:'); + console.log(' RemoteJid:', msg.key.remoteJid); + console.log(' Participant:', msg.key.participant); + console.log(' FromMe:', msg.key.fromMe); + console.log(' PushName:', msg.pushName); + console.log(' Tipo de mensaje:', msgType); + + console.log('\n🔍 ANÁLISIS LID:'); + console.log(' JID analizado:', msg.key.remoteJid); + if (msg.key.remoteJid?.includes(':')) { + const partes = msg.key.remoteJid.split(':'); + console.log(' Parte 1 (posible número):', partes[0]); + console.log(' Parte 2 (identificador):', partes[1]); + } + + console.log('\n👤 DATOS DEL CONTACTO:'); + console.log(' Número en contacto BD:', contact.number); + console.log(' Contact ID:', contact.id); + console.log(' Nombre:', contact.name); + // console.log(' LID guardado:', contact.lid); + console.log('======================================\n');*/ + + const ticket = await FindOrCreateTicketService({ + contact, + whatsappId: wbot.id!, + unreadMessages, + groupContact, + channel: "whatsapp" + }); + + if (hasMedia) { + await verifyMediaMessage(msg, ticket, contact); + } else { + await verifyMessage(msg, ticket, contact); + } + + const checkExpedient = await hourExpedient(); + if (checkExpedient) { + if ( + !ticket.queue && + !isGroup && + !msg.key.fromMe && + !ticket.userId && + whatsapp.queues.length >= 1 + ) { + await verifyQueue(wbot, msg, ticket, contact); + } + + if (ticket.queue && ticket.queueId) { + if (!ticket.user) { + await sayChatbot(ticket.queueId, wbot, ticket, contact, msg); + } + } + } else { + const getLastMessageFromMe = await Message.findOne({ + where: { + ticketId: ticket.id, + fromMe: true + }, + order: [["createdAt", "DESC"]] + }); + + if ( + getLastMessageFromMe?.body === + formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact) + ) + return; + + const body = formatBody(`\u200e${whatsapp.outOfWorkMessage}`, contact); + const sentMessage = await wbot.sendMessage( + `${contact.number}@${ticket.isGroup ? "g.us" : "s.whatsapp.net"}`, + { + text: body + } + ); + + await verifyMessage(sentMessage, ticket, contact); + } + } catch (err) { + console.error('❌ Error en handleMessage:', err); + Sentry.captureException(err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } +}; + +const wbotMessageListener = (wbot: Session): void => { + wbot.ev.on("messages.upsert", async (messageUpsert: ImessageUpsert) => { + const messages = messageUpsert.messages; + + if (messages.length === 0) return; + + const message = messages[0]; + + if (message.key.id.startsWith("BAE5") && message.key.fromMe) return; + + handleMessage(message, wbot); + }); + + wbot.ev.on("messages.update", (messageUpdate: WAMessageUpdate[]) => { + if (messageUpdate.length === 0) return; + + messageUpdate.forEach(async (update) => { + if (!update.key?.id || !update.update) return; + + const message = await Message.findOne({ where: { id: update.key.id } }); + if (!message) return; + + if (update.update?.status) { + await message.update({ status: update.update.status }); + const io = getIO(); + io.to(message.ticketId.toString()).emit("appMessage", { + action: "update", + message + }); + } + }); + }); +}; + + +export { + wbotMessageListener, + handleMessage, + verifyMessage, + getBodyMessage, + verifyMediaMessage, + isValidMsg, // ← ¡AGREGADA! + verifyQueue, + downloadMedia, // ← ¡AGREGAR TAMBIÉN ESTA! + getContactMessage, // ← Y ESTA SI NO ESTÁ + // Agrega cualquier otra función que se use externamente +}; \ No newline at end of file diff --git a/backend/src/services/WbotServices/wbotMonitor.ts b/backend/src/services/WbotServices/wbotMonitor.ts index 8148f7b..fc25bba 100644 --- a/backend/src/services/WbotServices/wbotMonitor.ts +++ b/backend/src/services/WbotServices/wbotMonitor.ts @@ -2,7 +2,7 @@ import { WASocket, BinaryNode, Contact as BContact -} from "@adiwajshing/baileys"; +} from "@whiskeysockets/baileys"; import * as Sentry from "@sentry/node"; import { Op } from "sequelize"; diff --git a/backend/src/utils/NormalizadorMejorado.ts b/backend/src/utils/NormalizadorMejorado.ts new file mode 100644 index 0000000..4875a8f --- /dev/null +++ b/backend/src/utils/NormalizadorMejorado.ts @@ -0,0 +1,171 @@ +// src/utils/NormalizadorMejorado.ts +import { WASocket } from "@whiskeysockets/baileys"; +import { logger } from "../utils/logger"; + +export class NormalizadorMejorado { + + /** + * Extrae el número de un JID en cualquier formato + */ + private extraerNumeroDeJID(jid: string): string { + // Si tiene @lid, extraer lo que está antes del @ + if (jid.includes('@lid')) { + const parte = jid.split('@')[0]; + // Extraer solo dígitos por seguridad + return parte.replace(/\D/g, ''); + } + + // Si tiene formato con : (ej: 5492478409220:0@lid) + const match = jid.match(/^(\d+)[:\-]/); + if (match && match[1]) { + return match[1]; + } + + // Si tiene @s.whatsapp.net + if (jid.includes('@s.whatsapp.net')) { + return jid.split('@')[0]; + } + + // Cualquier otro caso: solo números + return jid.replace(/\D/g, ''); + } + + /** + * Normaliza un JID para envío, convirtiendo LIDs a números reales + */ + async normalizarParaEnvio(sock: WASocket, jidDestino: string, companyId: number): Promise { + try { + console.log('🔄 Normalizando JID para envío:', jidDestino); + + // Si ya es un número tradicional con @s.whatsapp.net, retornar tal cual + if (jidDestino.includes('@s.whatsapp.net')) { + console.log('✅ JID ya normalizado (tradicional):', jidDestino); + return jidDestino; + } + + // Si es un LID (contiene :, @lid o . y no tiene @s.whatsapp.net) + const esLid = jidDestino.includes(':') || jidDestino.includes('@lid') || (jidDestino.includes('.') && !jidDestino.includes('@s.whatsapp.net')); + + if (esLid) { + console.log('🔍 Detectado LID, buscando número real...'); + + // Intentar usar onWhatsApp para obtener el número real + try { + const results = await sock.onWhatsApp(jidDestino); + + if (results && results.length > 0 && results[0]?.exists) { + const numeroReal = results[0].jid; + console.log('✅ LID convertido a número real:', jidDestino, '->', numeroReal); + return numeroReal; + } + } catch (error) { + console.error('❌ Error usando onWhatsApp:', error); + } + + // Si onWhatsApp falla, extraer el número del JID + const posibleNumero = this.extraerNumeroDeJID(jidDestino); + console.log('🔧 Extrayendo número de JID:', jidDestino, '->', posibleNumero); + + // Verificar si este número existe en WhatsApp + try { + const results = await sock.onWhatsApp(posibleNumero + '@s.whatsapp.net'); + if (results && results.length > 0 && results[0]?.exists) { + const jidCompleto = results[0].jid; + console.log('✅ Número extraído válido:', jidCompleto); + return jidCompleto; + } + } catch (error) { + console.error('❌ Error verificando número extraído:', error); + } + + // Si no podemos verificar, al menos formatear como número tradicional + return posibleNumero + '@s.whatsapp.net'; + } + + // Si no es LID pero no tiene @s.whatsapp.net, agregarlo + if (!jidDestino.includes('@')) { + const jidNormalizado = jidDestino + '@s.whatsapp.net'; + console.log('🔧 Agregando dominio a JID:', jidDestino, '->', jidNormalizado); + return jidNormalizado; + } + + // Si llegamos aquí, retornar el JID original + console.log('⚠️ No se pudo normalizar, usando original:', jidDestino); + return jidDestino; + + } catch (error) { + console.error('❌ Error en normalizador:', error); + return jidDestino; + } + } + + /** + * Obtiene el número real de un JID (LID o tradicional) + */ + async obtenerNumeroReal(sock: WASocket, jid: string): Promise { + try { + console.log('🔍 obtenerNumeroReal - JID recibido:', jid); + + // Si ya es un número tradicional + if (jid.includes('@s.whatsapp.net')) { + const numero = jid.split('@')[0]; + console.log('✅ Número tradicional extraído:', numero); + return numero; + } + + // Si es un LID (incluye @lid) + const esLid = jid.includes(':') || jid.includes('@lid') || (jid.includes('.') && !jid.includes('@s.whatsapp.net')); + + if (esLid) { + console.log('🔍 Detectado como LID:', jid); + + // Primero intentar con onWhatsApp + try { + const results = await sock.onWhatsApp(jid); + if (results && results.length > 0 && results[0]?.exists) { + const numero = results[0].jid.split('@')[0]; + console.log('✅ LID convertido con onWhatsApp:', jid, '->', numero); + return numero; + } + } catch (error) { + console.error('Error en onWhatsApp:', error); + } + + // Si onWhatsApp falla, extraer número usando nuestro método + const numeroExtraido = this.extraerNumeroDeJID(jid); + console.log('🔧 Número extraído de JID:', jid, '->', numeroExtraido); + + // Para LIDs con @lid, si extraemos el mismo número (ej: 28308196585718) + // es probable que sea incorrecto. Podemos marcar como placeholder + if (jid.includes('@lid') && numeroExtraido.length > 12) { + console.log('⚠️ LID largo detectado, posiblemente incorrecto'); + // Podríamos retornar un placeholder en lugar del LID crudo + return `LID_${numeroExtraido.substring(0, 10)}`; + } + + // Verificar si este número existe + try { + const results = await sock.onWhatsApp(numeroExtraido + '@s.whatsapp.net'); + if (results && results.length > 0 && results[0]?.exists) { + const numeroVerificado = results[0].jid.split('@')[0]; + console.log('✅ Número extraído verificado:', numeroVerificado); + return numeroVerificado; + } + } catch (error) { + console.error('Error verificando número extraído:', error); + } + + return numeroExtraido; + } + + // Fallback final + const numeroFinal = jid.replace(/\D/g, ''); + console.log('⚠️ Usando fallback final:', jid, '->', numeroFinal); + return numeroFinal; + + } catch (error) { + console.error('❌ Error en obtenerNumeroReal:', error); + return jid.replace(/\D/g, ''); + } + } +} \ No newline at end of file diff --git a/backend/src/utils/webhookSender.ts b/backend/src/utils/webhookSender.ts new file mode 100644 index 0000000..62153b2 --- /dev/null +++ b/backend/src/utils/webhookSender.ts @@ -0,0 +1,82 @@ +import axios from "axios"; + +export interface WebhookMessageData { + event: string; + timestamp: string; + data: { + messageId?: number; + ticketId?: number; + contactId?: number; + body?: string; + fromMe?: boolean; + read?: boolean; + createdAt?: Date; + status?: string; + userId?: number; + unreadMessages?: number; + lastMessageAt?: Date; + updatedAt?: Date; + }; +} + +export class WebhookSender { + private static n8nWebhookUrl = "http://localhost:5678/webhook/wati-ticket"; + static async sendToN8N(data: WebhookMessageData): Promise { + try { + console.log(`Enviando webhook a n8n: ${data.event}`); + + const response = await axios.post(this.n8nWebhookUrl, data, { + timeout: 5000, + headers: { + 'Content-Type': 'application/json', + } + }); + + console.log(`Webhook enviado exitosamente: ${response.status}`); + return response.status >= 200 && response.status < 300; + } catch (error: any) { + console.error('Error enviando webhook a n8n:', error.message); + if (error.response) { + console.error('Respuesta de error:', error.response.status, error.response.data); + } + return false; + } + } + + static async sendNewMessageWebhook(message: any, ticket: any): Promise { + const webhookData: WebhookMessageData = { + event: 'new_message', + timestamp: new Date().toISOString(), + data: { + messageId: message.id, + ticketId: ticket.id, + contactId: ticket.contactId, + body: message.body, + fromMe: message.fromMe, + read: message.read, + createdAt: message.createdAt + } + }; + + await this.sendToN8N(webhookData); + } + + static async sendTicketUpdateWebhook(ticket: any, oldStatus?: string): Promise { + const webhookData: WebhookMessageData = { + event: 'ticket_update', + timestamp: new Date().toISOString(), + data: { + ticketId: ticket.id, + status: ticket.status, + userId: ticket.userId, + unreadMessages: ticket.unreadMessages, + lastMessageAt: ticket.lastMessageAt, + updatedAt: ticket.updatedAt + } + }; + + await this.sendToN8N(webhookData); + } +} + +export default WebhookSender; diff --git a/backend/test-socket-simple.js b/backend/test-socket-simple.js new file mode 100644 index 0000000..4b934aa --- /dev/null +++ b/backend/test-socket-simple.js @@ -0,0 +1,50 @@ +const http = require('http'); +const { Server } = require('socket.io'); + +const server = http.createServer(); +const io = new Server(server, { + cors: { + origin: "*", + credentials: true + }, + transports: ["polling", "websocket"] +}); + +console.log("=== TEST SOCKET.IO FROM BACKEND DIR ==="); + +// Middleware +io.use((socket, next) => { + console.log("✅ MIDDLEWARE EXECUTED! Socket ID:", socket.id); + console.log("Query:", socket.handshake.query); + next(); +}); + +// Event listener +io.on("connection", (socket) => { + console.log("🎯 CONNECTION EVENT! Socket ID:", socket.id); + + socket.on("disconnect", () => { + console.log("Client disconnected:", socket.id); + }); + + socket.emit("test", { message: "Hello from test" }); +}); + +server.listen(3003, () => { + console.log("Test server listening on port 3003"); + + // Auto-test + setTimeout(() => { + const { exec } = require('child_process'); + console.log("\n=== TESTING WITH CURL ==="); + exec('curl -s "http://localhost:3003/socket.io/?token=TEST&EIO=4&transport=polling"', (error, stdout) => { + console.log("Curl response:", stdout); + console.log("\n=== TESTING WEBSOCKET ==="); + exec('curl -i -H "Connection: Upgrade" -H "Upgrade: websocket" "http://localhost:3003/socket.io/?token=TEST&EIO=4&transport=websocket" 2>&1 | head -20', (error, stdout) => { + console.log("WebSocket test:", stdout); + console.log("\n=== END TEST ==="); + process.exit(0); + }); + }); + }, 1000); +}); diff --git a/backend/test_decrypt_direct.js b/backend/test_decrypt_direct.js new file mode 100644 index 0000000..78b9c6e --- /dev/null +++ b/backend/test_decrypt_direct.js @@ -0,0 +1,185 @@ +const { + getMediaKeys, + decryptMediaRetryData, + MEDIA_KEYS, + MEDIA_HKDF_KEY_MAPPING +} = require('@whiskeysockets/baileys'); +const fs = require('fs').promises; +const crypto = require('crypto'); + +async function testDecryptDirect() { + console.log("=== Test de Descifrado Directo (Funciones Internas) ===\n"); + + // Tus datos + const mediaKeyHex = 'a9df526e6aad7c034c49de368fcc57c8d00d29430754ca6a1b5586a835556f3c'; + const encryptedFilePath = '/home/whaticketapp/whaticket-free/backend/public/3EB0F9701B052C8DECC12C.image'; + + try { + // 1. Leer archivo + const encryptedData = await fs.readFile(encryptedFilePath); + console.log(`📊 Archivo cifrado: ${encryptedData.length} bytes`); + + // 2. Verificar estructura + const first16 = encryptedData.slice(0, 16); + const last32 = encryptedData.slice(-32); + console.log(` Primeros 16 bytes (IV esperado): ${first16.toString('hex')}`); + console.log(` Últimos 32 bytes (SHA256 esperado): ${last32.toString('hex')}`); + + // 3. Verificar Media Keys de WhatsApp + console.log(`\n🔑 Media Keys de WhatsApp:`); + console.log(` IMAGE: ${MEDIA_KEYS.IMAGE}`); + console.log(` VIDEO: ${MEDIA_KEYS.VIDEO}`); + console.log(` AUDIO: ${MEDIA_KEYS.AUDIO}`); + console.log(` DOCUMENT: ${MEDIA_KEYS.DOCUMENT}`); + + // 4. Usar getMediaKeys para derivar claves (forma CORRECTA en Baileys v6) + const mediaKeyBuffer = Buffer.from(mediaKeyHex, 'hex'); + console.log(`\n🔐 Derivando claves con getMediaKeys...`); + + // Probar con cada tipo de media + const mediaTypes = ['image', 'video', 'audio', 'document']; + + for (const mediaType of mediaTypes) { + console.log(`\n🔄 Probando tipo: ${mediaType.toUpperCase()}`); + + try { + // Obtener claves derivadas para este tipo + const derivedKeys = getMediaKeys(mediaKeyBuffer, mediaType); + + console.log(` ✅ Claves derivadas obtenidas:`); + console.log(` - encKey (32 bytes): ${derivedKeys.encKey.length === 32 ? '✅' : '❌'} ${derivedKeys.encKey.length}`); + console.log(` - macKey (32 bytes): ${derivedKeys.macKey.length === 32 ? '✅' : '❌'} ${derivedKeys.macKey.length}`); + console.log(` - iv (32 bytes): ${derivedKeys.iv ? derivedKeys.iv.length : 'N/A'}`); + + // 5. Separar partes del archivo cifrado + // En Baileys v6: IV(16) + ciphertext + mac(10) ??? + // Tu archivo: 68090 bytes total + // Si IV son primeros 16: ciphertext = 68090 - 16 - ??? (mac/hash) + + // Hipótesis 1: IV(16) + ciphertext + sha256(32) + const ciphertextWithHash = encryptedData.slice(16); // Remover IV + const possibleHash = ciphertextWithHash.slice(-32); + const ciphertextOnly = ciphertextWithHash.slice(0, -32); + + console.log(` 🔍 Estructura hipótesis 1 (IV+data+SHA256):`); + console.log(` - IV: 16 bytes`); + console.log(` - Ciphertext: ${ciphertextOnly.length} bytes`); + console.log(` - Hash (SHA256?): ${possibleHash.length} bytes`); + console.log(` - ¿Múltiplo de 16? ${ciphertextOnly.length % 16 === 0 ? '✅' : '❌'} (resto: ${ciphertextOnly.length % 16})`); + + // 6. Intentar descifrar con aesDecryptWithIV + const { aesDecryptWithIV } = require('@whiskeysockets/baileys'); + + console.log(` 🔓 Intentando descifrar con aesDecryptWithIV...`); + + try { + // aesDecryptWithIV espera: ciphertext, IV, key + const decrypted = aesDecryptWithIV( + ciphertextOnly, + first16, // IV + derivedKeys.encKey + ); + + console.log(` ✅ Descifrado exitoso: ${decrypted.length} bytes`); + + // Verificar si es JPEG + if (decrypted.length >= 3) { + const firstBytes = decrypted.slice(0, 3).toString('hex').toUpperCase(); + console.log(` Primeros bytes: ${firstBytes}`); + + if (firstBytes === 'FFD8FF') { + console.log(` 🎉 ¡JPEG VÁLIDO DETECTADO!`); + + // Guardar + const outputPath = `/tmp/descifrado_${mediaType}.jpg`; + await fs.writeFile(outputPath, decrypted); + console.log(` 💾 Guardado en: ${outputPath}`); + + return { + success: true, + mediaType: mediaType, + buffer: decrypted, + path: outputPath + }; + } + } + + // Si no es JPEG, guardar igual para análisis + const outputPath = `/tmp/descifrado_${mediaType}.bin`; + await fs.writeFile(outputPath, decrypted); + console.log(` 💾 Guardado como binario: ${outputPath}`); + + } catch (decryptError) { + console.log(` ❌ Error en descifrado: ${decryptError.message}`); + } + + // Hipótesis 2: IV(16) + ciphertext + mac(10) + padding(?) + // 68090 - 16 = 68074 + // Si mac son 10 bytes: 68074 - 10 = 68064 + const ciphertextWithMac = encryptedData.slice(16); + const possibleMac = ciphertextWithMac.slice(-10); + const ciphertextForMac = ciphertextWithMac.slice(0, -10); + + console.log(`\n 🔍 Estructura hipótesis 2 (IV+data+MAC10):`); + console.log(` - IV: 16 bytes`); + console.log(` - Ciphertext: ${ciphertextForMac.length} bytes`); + console.log(` - MAC (10 bytes?): ${possibleMac.length} bytes`); + console.log(` - ¿Múltiplo de 16? ${ciphertextForMac.length % 16 === 0 ? '✅' : '❌'} (resto: ${ciphertextForMac.length % 16})`); + + // Intentar descifrar esta estructura + try { + const { aesDecryptWithIV } = require('@whiskeysockets/baileys'); + const decrypted = aesDecryptWithIV( + ciphertextForMac, + first16, + derivedKeys.encKey + ); + + console.log(` 🔓 Descifrado 2: ${decrypted.length} bytes`); + + if (decrypted.length >= 3) { + const firstBytes = decrypted.slice(0, 3).toString('hex').toUpperCase(); + console.log(` Primeros bytes: ${firstBytes}`); + + if (firstBytes === 'FFD8FF') { + console.log(` 🎉 ¡JPEG VÁLIDO EN HIPÓTESIS 2!`); + + const outputPath = `/tmp/descifrado_${mediaType}_hyp2.jpg`; + await fs.writeFile(outputPath, decrypted); + console.log(` 💾 Guardado: ${outputPath}`); + + return { + success: true, + mediaType: mediaType, + buffer: decrypted, + path: outputPath + }; + } + } + } catch (e) { + // Continuar con siguiente tipo + } + + } catch (keyError) { + console.log(` ❌ Error con ${mediaType}: ${keyError.message}`); + } + } + + console.log(`\n⚠️ Ninguna combinación produjo un JPEG válido.`); + console.log(`💡 Prueba manualmente con:`); + console.log(` node -e "const {getMediaKeys, aesDecryptWithIV} = require('@whiskeysockets/baileys'); const fs = require('fs'); const data = fs.readFileSync('${encryptedFilePath}'); const keys = getMediaKeys(Buffer.from('${mediaKeyHex}', 'hex'), 'image'); const iv = data.slice(0,16); const cipher = data.slice(16, -32); const dec = aesDecryptWithIV(cipher, iv, keys.encKey); console.log(dec.slice(0,4).toString('hex'));"`); + + return { success: false, error: "No JPEG found" }; + + } catch (error) { + console.error(`\n💥 Error general: ${error.message}`); + console.error(error.stack); + return { success: false, error: error.message }; + } +} + +// Ejecutar +testDecryptDirect().then(result => { + console.log("\n=== Test finalizado ==="); + process.exit(result.success ? 0 : 1); +}); diff --git a/backend/test_decrypt_lowlevel.js b/backend/test_decrypt_lowlevel.js new file mode 100644 index 0000000..dbcee6f --- /dev/null +++ b/backend/test_decrypt_lowlevel.js @@ -0,0 +1,142 @@ +const { downloadMediaMessage, getUrlInfo } = require('@whiskeysockets/baileys'); +const fs = require('fs').promises; +const path = require('path'); + +async function testDecryptLowLevel() { + console.log("=== Test de Descifrado (Bajo Nivel) ===\n"); + + // Tus datos conocidos + const mediaKeyHex = 'a9df526e6aad7c034c49de368fcc57c8d00d29430754ca6a1b5586a835556f3c'; + const encryptedFilePath = '/home/whaticketapp/whaticket-free/backend/public/3EB0F9701B052C8DECC12C.image'; + + try { + // 1. Leer el archivo cifrado completo + const encryptedData = await fs.readFile(encryptedFilePath); + console.log(`📊 Archivo cifrado: ${encryptedData.length} bytes`); + + // 2. Mostrar estructura del archivo + console.log(` Primeros 16 bytes (posible IV): ${encryptedData.slice(0, 16).toString('hex')}`); + console.log(` Últimos 16 bytes: ${encryptedData.slice(-16).toString('hex')}`); + + // 3. Intentar usar la función decryptMediaRef directamente + // Buscamos funciones de descifrado en Baileys + const baileysModule = require('@whiskeysockets/baileys'); + console.log(`\n🔍 Funciones disponibles en Baileys:`); + Object.keys(baileysModule) + .filter(key => key.toLowerCase().includes('decrypt') || key.toLowerCase().includes('media')) + .forEach(key => console.log(` - ${key}`)); + + // 4. Intentar con WAProto si está disponible + let protoAvailable = false; + try { + const { proto } = require('@whiskeysockets/baileys'); + if (proto && proto.Message) { + protoAvailable = true; + console.log(`\n✅ WAProto disponible para construir mensaje`); + } + } catch (e) { + console.log(`\n⚠️ WAProto no disponible: ${e.message}`); + } + + // 5. Enfoque alternativo: Simular un mensaje completo + console.log(`\n🔧 Construyendo mensaje simulado...`); + + const fakeMessage = { + key: { + remoteJid: '1234567890@s.whatsapp.net', + id: '3EB0F9701B052C8DECC12C' + }, + message: { + imageMessage: { + url: `file://${encryptedFilePath}`, + mediaKey: Buffer.from(mediaKeyHex, 'hex').toString('base64'), + mimetype: 'image/jpeg', + fileSha256: encryptedData.slice(-32), // Últimos 32 bytes podrían ser hash + fileLength: encryptedData.length, + height: 800, + width: 600, + mediaKeyTimestamp: Date.now().toString(), + directPath: '/v/t62.7118-24/3EB0F9701B052C8DECC12C' + } + }, + messageTimestamp: Date.now(), + status: 'SERVER_ACK' + }; + + console.log(`📨 Mensaje simulado construido con:`); + console.log(` - mediaKey: ${fakeMessage.message.imageMessage.mediaKey.substring(0, 30)}...`); + console.log(` - fileLength: ${fakeMessage.message.imageMessage.fileLength}`); + + // 6. Intentar descifrar con el mensaje simulado + console.log(`\n🔐 Intentando descifrar con mensaje simulado...`); + + try { + const decryptedBuffer = await downloadMediaMessage( + fakeMessage, + 'buffer', + {}, + { + logger: console, + reuploadRequest: async () => { + console.log(` 📞 Reupload request simulada`); + return { + mediaKey: Buffer.from(mediaKeyHex, 'hex'), + directPath: fakeMessage.message.imageMessage.directPath + }; + } + } + ); + + console.log(`\n✅ ¡DESCIFRADO EXITOSO!`); + console.log(` Tamaño: ${decryptedBuffer.length} bytes`); + + // Verificar si es JPEG + const firstBytes = decryptedBuffer.slice(0, 3).toString('hex').toUpperCase(); + console.log(` Primeros bytes: ${firstBytes}`); + + if (firstBytes === 'FFD8FF') { + console.log(` ✅ Formato JPEG válido`); + + const outputPath = '/tmp/descifrado_final.jpg'; + await fs.writeFile(outputPath, decryptedBuffer); + console.log(` 💾 Guardado en: ${outputPath}`); + + // Mostrar información del archivo + const stats = await fs.stat(outputPath); + console.log(` 📁 Tamaño final: ${stats.size} bytes`); + } + + return { success: true, buffer: decryptedBuffer }; + + } catch (innerError) { + console.error(`\n❌ Error en downloadMediaMessage: ${innerError.message}`); + console.error(` Detalles: ${innerError.stack.split('\n')[0]}`); + + // 7. Último recurso: Buscar funciones internas de descifrado + console.log(`\n🔍 Buscando funciones internas de descifrado...`); + + // Intentar acceder a funciones internas + const baileysPath = require.resolve('@whiskeysockets/baileys'); + const baileysDir = path.dirname(baileysPath); + + console.log(` Ruta de Baileys: ${baileysDir}`); + + // Listar archivos en el directorio de Baileys + const files = await fs.readdir(baileysDir); + const decryptFiles = files.filter(f => f.toLowerCase().includes('decrypt') || f.toLowerCase().includes('crypt')); + console.log(` Archivos relacionados: ${decryptFiles.join(', ') || 'ninguno'}`); + + throw innerError; + } + + } catch (error) { + console.error(`\n💥 Error general: ${error.message}`); + return { success: false, error: error.message }; + } +} + +// Ejecutar +testDecryptLowLevel().then(result => { + console.log("\n=== Test finalizado ==="); + process.exit(result.success ? 0 : 1); +}); diff --git a/backend/test_decrypt_v6.js b/backend/test_decrypt_v6.js new file mode 100644 index 0000000..3afcaee --- /dev/null +++ b/backend/test_decrypt_v6.js @@ -0,0 +1,108 @@ +const { downloadMediaMessage } = require('@whiskeysockets/baileys'); +const fs = require('fs').promises; +const path = require('path'); +const crypto = require('crypto'); + +async function testDecrypt() { + console.log("=== Test de Descifrado Baileys 6.7.19 ===\n"); + + // 1. Tu MediaKey en Base64 (la original era hex) + const mediaKeyHex = 'a9df526e6aad7c034c49de368fcc57c8d00d29430754ca6a1b5586a835556f3c'; + const mediaKeyBuffer = Buffer.from(mediaKeyHex, 'hex'); + + // 2. Ruta del archivo cifrado + const encryptedFilePath = '/home/whaticketapp/whaticket-free/backend/public/3EB0F9701B052C8DECC12C.image'; + + try { + // 3. Leer el archivo cifrado + const encryptedData = await fs.readFile(encryptedFilePath); + console.log(`📊 Archivo cifrado leído: ${encryptedData.length} bytes`); + + // 4. Crear estructura de mensaje fake para Baileys + const fakeMessage = { + directPath: '/v/t62.7118-24/3EB0F9701B052C8DECC12C', + url: `file://${encryptedFilePath}`, + mediaKey: mediaKeyBuffer, + mediaKeyTimestamp: Date.now(), + fileLength: encryptedData.length, + mediaType: 'image' + }; + + console.log("🔐 Intentando descifrar con downloadMediaMessage..."); + + // 5. Usar la función de Baileys 6.7.19 + const decryptedBuffer = await downloadMediaMessage( + fakeMessage, + 'buffer', + {}, + { + logger: { + level: 'silent' // Silenciamos logs para claridad + }, + reuploadRequest: async () => { + return { mediaKey: mediaKeyBuffer }; + } + } + ); + + console.log(`\n✅ ¡DESCIFRADO EXITOSO!`); + console.log(` Tamaño del resultado: ${decryptedBuffer.length} bytes`); + + // 6. Verificar si es un JPEG válido + const firstBytes = decryptedBuffer.slice(0, 3).toString('hex').toUpperCase(); + console.log(` Primeros bytes (hex): ${firstBytes}`); + + if (firstBytes === 'FFD8FF') { + console.log(` ✅ Formato JPEG válido detectado (FF D8 FF = JPEG SOI marker)`); + + // 7. Guardar el resultado + const outputPath = '/tmp/3EB0F9701B052C8DECC12C_DECRYPTED.jpg'; + await fs.writeFile(outputPath, decryptedBuffer); + console.log(` 💾 Archivo guardado en: ${outputPath}`); + + // 8. Información adicional del JPEG + try { + // Buscar el marcador de fin de imagen + const soiIndex = decryptedBuffer.indexOf(Buffer.from([0xFF, 0xD8])); + const eoiIndex = decryptedBuffer.indexOf(Buffer.from([0xFF, 0xD9])); + + if (soiIndex !== -1 && eoiIndex !== -1) { + console.log(` 📸 Estructura JPEG: SOI en byte ${soiIndex}, EOF en byte ${eoiIndex + 2}`); + } + } catch (e) { + // Ignorar errores de análisis + } + } else { + console.log(` ⚠️ Los primeros bytes (${firstBytes}) no corresponden a un JPEG estándar.`); + console.log(` ℹ️ Podría ser otro formato o el descifrado falló.`); + + // Guardar de todos modos para análisis + const outputPath = '/tmp/3EB0F9701B052C8DECC12C_DECRYPTED.bin'; + await fs.writeFile(outputPath, decryptedBuffer); + console.log(` 💾 Datos guardados en: ${outputPath} para inspección.`); + } + + return { success: true, buffer: decryptedBuffer }; + + } catch (error) { + console.error(`\n❌ Error durante el descifrado:`); + console.error(` Mensaje: ${error.message}`); + console.error(` Stack: ${error.stack.split('\n')[0]}`); + + // Análisis de error más detallado + if (error.message.includes('padding')) { + console.error(`\n🔍 Posible problema de padding/PKCS7.`); + } + if (error.message.includes('mac') || error.message.includes('auth')) { + console.error(`🔍 Posible problema de autenticación MAC.`); + } + + return { success: false, error: error.message }; + } +} + +// Ejecutar la prueba +testDecrypt().then(result => { + console.log("\n=== Test finalizado ==="); + process.exit(result.success ? 0 : 1); +}); diff --git a/backend/test_getmediakeys_correct.js b/backend/test_getmediakeys_correct.js new file mode 100644 index 0000000..7529ec6 --- /dev/null +++ b/backend/test_getmediakeys_correct.js @@ -0,0 +1,124 @@ +console.log("=== Test getMediaKeys con parámetros correctos ===\n"); + +const { getMediaKeys, MEDIA_HKDF_KEY_MAPPING, hkdfInfoKey } = require('@whiskeysockets/baileys'); + +// 1. Tu mediaKey +const mediaKeyHex = 'a9df526e6aad7c034c49de368fcc57c8d00d29430754ca6a1b5586a835556f3c'; +const mediaKeyBuffer = Buffer.from(mediaKeyHex, 'hex'); + +console.log(`📦 MediaKey original: ${mediaKeyHex.substring(0, 32)}...`); +console.log(` Longitud: ${mediaKeyBuffer.length} bytes (esperado: 32)\n`); + +// 2. Probar diferentes enfoques +console.log("🔍 Probando diferentes llamadas a getMediaKeys:"); + +// Enfoque 1: Como antes (probablemente incorrecto) +console.log("\n1. getMediaKeys(mediaKey, 'image'):"); +try { + const result1 = getMediaKeys(mediaKeyBuffer, 'image'); + console.log(` Resultado: ${JSON.stringify(result1)}`); + console.log(` Tipo: ${typeof result1}`); + if (result1 && typeof result1 === 'object') { + console.log(` Propiedades: ${Object.keys(result1).join(', ') || 'ninguna'}`); + if (Object.keys(result1).length > 0) { + Object.keys(result1).forEach(k => { + const v = result1[k]; + console.log(` ${k}: ${v ? `Buffer[${v.length}]` : 'null'}`); + }); + } + } +} catch (e) { + console.log(` ❌ Error: ${e.message}`); +} + +// Enfoque 2: Usando MEDIA_HKDF_KEY_MAPPING +console.log("\n2. Usando MEDIA_HKDF_KEY_MAPPING:"); +console.log(` MEDIA_HKDF_KEY_MAPPING['image'] = "${MEDIA_HKDF_KEY_MAPPING['image']}"`); + +// Enfoque 3: Probando con hkdfInfoKey +console.log("\n3. Probando con hkdfInfoKey:"); +if (hkdfInfoKey) { + console.log(` hkdfInfoKey tipo: ${typeof hkdfInfoKey}`); + console.log(` hkdfInfoKey valor: ${hkdfInfoKey}`); +} else { + console.log(` hkdfInfoKey es undefined`); +} + +// Enfoque 4: Mirar el código fuente de getMediaKeys +console.log("\n4. Inspeccionando función getMediaKeys:"); +try { + const fs = require('fs'); + const path = require('path'); + + const baileysPath = require.resolve('@whiskeysockets/baileys'); + const baileysDir = path.dirname(baileysPath); + + // Buscar el archivo que contiene getMediaKeys + const libFiles = fs.readdirSync(baileysDir).filter(f => f.endsWith('.js')); + + for (const file of libFiles) { + const content = fs.readFileSync(path.join(baileysDir, file), 'utf8'); + if (content.includes('getMediaKeys') && content.includes('function')) { + console.log(` Encontrado en: ${file}`); + + // Extraer la función + const lines = content.split('\n'); + let inFunction = false; + let functionLines = []; + + for (const line of lines) { + if (line.includes('getMediaKeys') && (line.includes('function') || line.includes('='))) { + inFunction = true; + } + if (inFunction) { + functionLines.push(line); + if (line.trim().endsWith('}') && functionLines.length > 5) { + break; + } + } + } + + if (functionLines.length > 0) { + console.log(` 📝 Definición (primeras líneas):`); + functionLines.slice(0, 10).forEach(l => console.log(` ${l}`)); + } + break; + } + } +} catch (e) { + console.log(` ❌ Error inspeccionando: ${e.message}`); +} + +// Enfoque 5: Probar decryptMediaRetryData (que es probablemente lo que Whaticket usa) +console.log("\n5. Probando decryptMediaRetryData:"); +const { decryptMediaRetryData } = require('@whiskeysockets/baileys'); + +// Leer archivo cifrado +const fs = require('fs'); +const encryptedPath = '/home/whaticketapp/whaticket-free/backend/public/3EB0F9701B052C8DECC12C.image'; +const encryptedData = fs.readFileSync(encryptedPath); + +console.log(` Archivo: ${encryptedData.length} bytes`); + +try { + // decryptMediaRetryData probablemente espera una estructura específica + const result = decryptMediaRetryData({ + mediaKey: mediaKeyBuffer, + ciphertext: encryptedData, + mediaType: MEDIA_HKDF_KEY_MAPPING['image'] || 'Image' + }); + + console.log(` ✅ decryptMediaRetryData OK:`); + console.log(` Tipo resultado: ${typeof result}`); + if (Buffer.isBuffer(result)) { + console.log(` Buffer: ${result.length} bytes`); + console.log(` Primeros bytes: ${result.slice(0, 4).toString('hex')}`); + } else if (result && typeof result === 'object') { + console.log(` Objeto con keys: ${Object.keys(result).join(', ')}`); + } +} catch (e) { + console.log(` ❌ Error decryptMediaRetryData: ${e.message}`); + console.log(` Stack: ${e.stack.split('\n')[0]}`); +} + +console.log("\n=== Test completado ==="); diff --git a/backend/test_import_local.js b/backend/test_import_local.js new file mode 100644 index 0000000..fddbf23 --- /dev/null +++ b/backend/test_import_local.js @@ -0,0 +1,14 @@ +console.log("=== Test de importación Baileys 6.7.19 (LOCAL) ==="); +try { + const baileys = require('@whiskeysockets/baileys'); + console.log("✅ Módulo importado con éxito."); + console.log("✅ Función 'downloadMediaMessage' disponible:", typeof baileys.downloadMediaMessage === 'function'); + + // Verificar la ruta y versión del paquete + const path = require('path'); + const baileysPath = require.resolve('@whiskeysockets/baileys'); + const pkg = require(path.join(baileysPath, '../../package.json')); + console.log(`✅ Versión confirmada: ${pkg.version}`); +} catch (error) { + console.error("❌ Error en la importación:", error.message); +} diff --git a/backend/tsc b/backend/tsc new file mode 100644 index 0000000..e69de29 diff --git a/backend/tsconfig.json b/backend/tsconfig.json index ebda714..d81728b 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,14 +1,20 @@ { "compilerOptions": { - "target": "es6", + "target": "es2017", "module": "commonjs", - "outDir": "./dist", - "strict": false, - "strictPropertyInitialization": false, + "lib": ["es6"], + "allowJs": true, + "outDir": "dist", + "rootDir": "src", + "strict": false, // <-- false temporalmente para compilar + "noImplicitAny": false, // <-- false temporalmente "esModuleInterop": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, + "resolveJsonModule": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - } + "forceConsistentCasingInFileNames": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] } diff --git a/backend/whaticket_diagnostico.txt b/backend/whaticket_diagnostico.txt new file mode 100644 index 0000000..ee94c71 --- /dev/null +++ b/backend/whaticket_diagnostico.txt @@ -0,0 +1,80 @@ +=== DIAGNÓSTICO WHATICKET - Sun Dec 28 01:04:06 AM UTC 2025 === +=== Generado en: /home/whaticketapp/whaticket-free/backend +====================================== + + +1. ESTADO DE PM2: +┌────┬───────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ +│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ +├────┼───────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ +│ 2 │ whaticket-backend │ default │ 1.0.0 │ fork │ 28460 │ 47m │ 0 │ online │ 0% │ 151.5mb │ whatick… │ disabled │ +│ 1 │ whaticket-frontend │ default │ 0.1.0 │ fork │ 24511 │ 7h │ 0 │ online │ 0% │ 71.8mb │ whatick… │ disabled │ +└────┴───────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘ + + +2. LOGS DEL BACKEND (últimas 30 líneas): +[TAILING] Tailing last 30 lines for [whaticket-backend] process (change the value with --lines option) +/home/whaticketapp/.pm2/logs/whaticket-backend-error.log last 30 lines: +2|whaticke | ^ +2|whaticke | +2|whaticke | SyntaxError: Unexpected token ';' +2|whaticke | at wrapSafe (node:internal/modules/cjs/loader:1638:18) +2|whaticke | at Module._compile (node:internal/modules/cjs/loader:1680:20) +2|whaticke | at Object..js (node:internal/modules/cjs/loader:1839:10) +2|whaticke | at Module.load (node:internal/modules/cjs/loader:1441:32) +2|whaticke | at Function._load (node:internal/modules/cjs/loader:1263:12) +2|whaticke | at TracingChannel.traceSync (node:diagnostics_channel:328:14) +2|whaticke | at wrapModuleLoad (node:internal/modules/cjs/loader:237:24) +2|whaticke | at Module. (node:internal/modules/cjs/loader:1463:12) +2|whaticke | at Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:101:39) +2|whaticke | at require (node:internal/modules/helpers:147:16) +2|whaticke | at Object. (/home/whaticketapp/whaticket-free/backend/dist/helpers/GetWhatsappWbot.js:12:16) +2|whaticke | at Module._compile (node:internal/modules/cjs/loader:1706:14) +2|whaticke | at Object..js (node:internal/modules/cjs/loader:1839:10) +2|whaticke | at Module.load (node:internal/modules/cjs/loader:1441:32) +2|whaticke | at Function._load (node:internal/modules/cjs/loader:1263:12) +2|whaticke | at TracingChannel.traceSync (node:diagnostics_channel:328:14) +2|whaticke | at wrapModuleLoad (node:internal/modules/cjs/loader:237:24) +2|whaticke | at Module. (node:internal/modules/cjs/loader:1463:12) +2|whaticke | at Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:101:39) +2|whaticke | at require (node:internal/modules/helpers:147:16) +2|whaticke | at Object. (/home/whaticketapp/whaticket-free/backend/dist/helpers/SendMessage.js:18:43) +2|whaticke | at Module._compile (node:internal/modules/cjs/loader:1706:14) +2|whaticke | ReferenceError: state is not defined +2|whaticke | at /home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js:110:27 +2|whaticke | at Generator.next () +2|whaticke | at fulfilled (/home/whaticketapp/whaticket-free/backend/dist/libs/wbot.js:28:58) +2|whaticke | at process.processTicksAndRejections (node:internal/process/task_queues:105:5) + +/home/whaticketapp/.pm2/logs/whaticket-backend-out.log last 30 lines: +2|whaticke | } +2|whaticke | } +2|whaticke | Closing session: SessionEntry { +2|whaticke | _chains: { +2|whaticke | 'BX0dR1ShdDYk9zVcoDU0oAXVx+BR5VGn724H0XpxMh80': { chainKey: [Object], chainType: 1, messageKeys: {} } +2|whaticke | }, +2|whaticke | registrationId: 2050396948, +2|whaticke | currentRatchet: { +2|whaticke | ephemeralKeyPair: { +2|whaticke | pubKey: , +2|whaticke | privKey: +2|whaticke | }, +2|whaticke | lastRemoteEphemeralKey: , +2|whaticke | previousCounter: 0, +2|whaticke | rootKey: +2|whaticke | }, +2|whaticke | indexInfo: { +2|whaticke | baseKey: , +2|whaticke | baseKeyType: 1, +2|whaticke | closed: -1, +2|whaticke | used: 1766880303589, +2|whaticke | created: 1766880303589, +2|whaticke | remoteIdentityKey: +2|whaticke | }, +2|whaticke | pendingPreKey: { +2|whaticke | signedKeyId: 16709982, +2|whaticke | baseKey: , +2|whaticke | preKeyId: 3038053 +2|whaticke | } +2|whaticke | } + diff --git a/backend/whaticket_full.log b/backend/whaticket_full.log new file mode 100644 index 0000000..5b1263d --- /dev/null +++ b/backend/whaticket_full.log @@ -0,0 +1,3531 @@ +[TAILING] Tailing last 1000 lines for [whaticket-backend] process (change the value with --lines option) +/home/whaticketapp/.pm2/logs/whaticket-backend-error-0.log last 1000 lines: +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:07:11.816 -03:00', +0|whaticke | '2026-01-24 17:07:11.816 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } +0|whaticke | Error creando contacto: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error +0|whaticke | at Query.formatError (/home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:324:18) +0|whaticke | at /home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:72:18 +0|whaticke | at tryCatcher (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/util.js:16:23) +0|whaticke | at Promise._settlePromiseFromHandler (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:547:31) +0|whaticke | at Promise._settlePromise (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:604:18) +0|whaticke | at Promise._settlePromise0 (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:649:10) +0|whaticke | at Promise._settlePromises (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:725:18) +0|whaticke | at _drainQueueStep (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:93:12) +0|whaticke | at _drainQueue (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:86:9) +0|whaticke | at Async._drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:102:5) +0|whaticke | at Async.drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:15:14) +0|whaticke | at process.processImmediate (node:internal/timers:485:21) +0|whaticke | at process.topLevelDomainCallback (node:domain:161:15) +0|whaticke | at process.callbackTrampoline (node:internal/async_hooks:128:24) { +0|whaticke | errors: [ +0|whaticke | ValidationErrorItem { +0|whaticke | message: 'number must be unique', +0|whaticke | type: 'unique violation', +0|whaticke | path: 'number', +0|whaticke | value: '5492478610999', +0|whaticke | origin: 'DB', +0|whaticke | instance: [Contact], +0|whaticke | validatorKey: 'not_unique', +0|whaticke | validatorName: null, +0|whaticke | validatorArgs: [] +0|whaticke | } +0|whaticke | ], +0|whaticke | fields: { number: '5492478610999' }, +0|whaticke | parent: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:26:38.724 -03:00', +0|whaticke | '2026-01-24 17:26:38.724 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | original: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:26:38.724 -03:00', +0|whaticke | '2026-01-24 17:26:38.724 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } +0|whaticke | ❌ ERROR CRÍTICO: No se encontró contacto existente a pesar del error de duplicado +0|whaticke | Número: 5492478610999 +0|whaticke | Company ID: 1 +0|whaticke | ❌ Error en handleMessage: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error +0|whaticke | at Query.formatError (/home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:324:18) +0|whaticke | at /home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:72:18 +0|whaticke | at tryCatcher (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/util.js:16:23) +0|whaticke | at Promise._settlePromiseFromHandler (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:547:31) +0|whaticke | at Promise._settlePromise (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:604:18) +0|whaticke | at Promise._settlePromise0 (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:649:10) +0|whaticke | at Promise._settlePromises (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:725:18) +0|whaticke | at _drainQueueStep (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:93:12) +0|whaticke | at _drainQueue (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:86:9) +0|whaticke | at Async._drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:102:5) +0|whaticke | at Async.drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:15:14) +0|whaticke | at process.processImmediate (node:internal/timers:485:21) +0|whaticke | at process.topLevelDomainCallback (node:domain:161:15) +0|whaticke | at process.callbackTrampoline (node:internal/async_hooks:128:24) { +0|whaticke | errors: [ +0|whaticke | ValidationErrorItem { +0|whaticke | message: 'number must be unique', +0|whaticke | type: 'unique violation', +0|whaticke | path: 'number', +0|whaticke | value: '5492478610999', +0|whaticke | origin: 'DB', +0|whaticke | instance: [Contact], +0|whaticke | validatorKey: 'not_unique', +0|whaticke | validatorName: null, +0|whaticke | validatorArgs: [] +0|whaticke | } +0|whaticke | ], +0|whaticke | fields: { number: '5492478610999' }, +0|whaticke | parent: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:26:38.724 -03:00', +0|whaticke | '2026-01-24 17:26:38.724 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | original: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:26:38.724 -03:00', +0|whaticke | '2026-01-24 17:26:38.724 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } +0|whaticke | Error creando contacto: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error +0|whaticke | at Query.formatError (/home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:324:18) +0|whaticke | at /home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:72:18 +0|whaticke | at tryCatcher (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/util.js:16:23) +0|whaticke | at Promise._settlePromiseFromHandler (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:547:31) +0|whaticke | at Promise._settlePromise (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:604:18) +0|whaticke | at Promise._settlePromise0 (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:649:10) +0|whaticke | at Promise._settlePromises (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:725:18) +0|whaticke | at _drainQueueStep (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:93:12) +0|whaticke | at _drainQueue (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:86:9) +0|whaticke | at Async._drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:102:5) +0|whaticke | at Async.drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:15:14) +0|whaticke | at process.processImmediate (node:internal/timers:485:21) +0|whaticke | at process.topLevelDomainCallback (node:domain:161:15) +0|whaticke | at process.callbackTrampoline (node:internal/async_hooks:128:24) { +0|whaticke | errors: [ +0|whaticke | ValidationErrorItem { +0|whaticke | message: 'number must be unique', +0|whaticke | type: 'unique violation', +0|whaticke | path: 'number', +0|whaticke | value: '5492478610999', +0|whaticke | origin: 'DB', +0|whaticke | instance: [Contact], +0|whaticke | validatorKey: 'not_unique', +0|whaticke | validatorName: null, +0|whaticke | validatorArgs: [] +0|whaticke | } +0|whaticke | ], +0|whaticke | fields: { number: '5492478610999' }, +0|whaticke | parent: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:552:15) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:28:25.069 -03:00', +0|whaticke | '2026-01-24 17:28:25.069 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | original: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:552:15) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:28:25.069 -03:00', +0|whaticke | '2026-01-24 17:28:25.069 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } +0|whaticke | ❌ ERROR CRÍTICO: No se encontró contacto existente a pesar del error de duplicado +0|whaticke | Número: 5492478610999 +0|whaticke | Company ID: 1 +0|whaticke | ❌ Error en handleMessage: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error +0|whaticke | at Query.formatError (/home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:324:18) +0|whaticke | at /home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:72:18 +0|whaticke | at tryCatcher (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/util.js:16:23) +0|whaticke | at Promise._settlePromiseFromHandler (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:547:31) +0|whaticke | at Promise._settlePromise (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:604:18) +0|whaticke | at Promise._settlePromise0 (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:649:10) +0|whaticke | at Promise._settlePromises (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:725:18) +0|whaticke | at _drainQueueStep (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:93:12) +0|whaticke | at _drainQueue (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:86:9) +0|whaticke | at Async._drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:102:5) +0|whaticke | at Async.drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:15:14) +0|whaticke | at process.processImmediate (node:internal/timers:485:21) +0|whaticke | at process.topLevelDomainCallback (node:domain:161:15) +0|whaticke | at process.callbackTrampoline (node:internal/async_hooks:128:24) { +0|whaticke | errors: [ +0|whaticke | ValidationErrorItem { +0|whaticke | message: 'number must be unique', +0|whaticke | type: 'unique violation', +0|whaticke | path: 'number', +0|whaticke | value: '5492478610999', +0|whaticke | origin: 'DB', +0|whaticke | instance: [Contact], +0|whaticke | validatorKey: 'not_unique', +0|whaticke | validatorName: null, +0|whaticke | validatorArgs: [] +0|whaticke | } +0|whaticke | ], +0|whaticke | fields: { number: '5492478610999' }, +0|whaticke | parent: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:552:15) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:28:25.069 -03:00', +0|whaticke | '2026-01-24 17:28:25.069 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | original: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:552:15) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478610999) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'secundaria1arrecifes', +0|whaticke | '5492478610999', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/560552491_866249843049949_494321501343227340_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gFETp3gEVboU8RODqvVwFt8fxy9Ak27S0OW2tUUfmAa8A&oe=69823922&_nc_sid=5e03e0&_nc_cat=102', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '80715924414582@lid', +0|whaticke | '2026-01-24 17:28:25.069 -03:00', +0|whaticke | '2026-01-24 17:28:25.069 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } +0|whaticke | Error creando contacto: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error +0|whaticke | at Query.formatError (/home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:324:18) +0|whaticke | at /home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:72:18 +0|whaticke | at tryCatcher (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/util.js:16:23) +0|whaticke | at Promise._settlePromiseFromHandler (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:547:31) +0|whaticke | at Promise._settlePromise (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:604:18) +0|whaticke | at Promise._settlePromise0 (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:649:10) +0|whaticke | at Promise._settlePromises (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:725:18) +0|whaticke | at _drainQueueStep (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:93:12) +0|whaticke | at _drainQueue (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:86:9) +0|whaticke | at Async._drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:102:5) +0|whaticke | at Async.drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:15:14) +0|whaticke | at process.processImmediate (node:internal/timers:485:21) +0|whaticke | at process.topLevelDomainCallback (node:domain:161:15) +0|whaticke | at process.callbackTrampoline (node:internal/async_hooks:128:24) { +0|whaticke | errors: [ +0|whaticke | ValidationErrorItem { +0|whaticke | message: 'number must be unique', +0|whaticke | type: 'unique violation', +0|whaticke | path: 'number', +0|whaticke | value: '5492478577503', +0|whaticke | origin: 'DB', +0|whaticke | instance: [Contact], +0|whaticke | validatorKey: 'not_unique', +0|whaticke | validatorName: null, +0|whaticke | validatorArgs: [] +0|whaticke | } +0|whaticke | ], +0|whaticke | fields: { number: '5492478577503' }, +0|whaticke | parent: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478577503) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'Agustín Navarro', +0|whaticke | '5492478577503', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/609063473_1101333805338792_6890609188563188958_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gH4VxYMzjqB8-9BGCy5YVrE1bazdI_zyof9gXyoAuJmzA&oe=69824577&_nc_sid=5e03e0&_nc_cat=105', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '124777406664808@lid', +0|whaticke | '2026-01-24 17:47:06.975 -03:00', +0|whaticke | '2026-01-24 17:47:06.975 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | original: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478577503) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'Agustín Navarro', +0|whaticke | '5492478577503', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/609063473_1101333805338792_6890609188563188958_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gH4VxYMzjqB8-9BGCy5YVrE1bazdI_zyof9gXyoAuJmzA&oe=69824577&_nc_sid=5e03e0&_nc_cat=105', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '124777406664808@lid', +0|whaticke | '2026-01-24 17:47:06.975 -03:00', +0|whaticke | '2026-01-24 17:47:06.975 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } +0|whaticke | ❌ ERROR CRÍTICO: No se encontró contacto existente a pesar del error de duplicado +0|whaticke | Número: 5492478577503 +0|whaticke | Company ID: 1 +0|whaticke | ❌ Error en handleMessage: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error +0|whaticke | at Query.formatError (/home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:324:18) +0|whaticke | at /home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:72:18 +0|whaticke | at tryCatcher (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/util.js:16:23) +0|whaticke | at Promise._settlePromiseFromHandler (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:547:31) +0|whaticke | at Promise._settlePromise (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:604:18) +0|whaticke | at Promise._settlePromise0 (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:649:10) +0|whaticke | at Promise._settlePromises (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:725:18) +0|whaticke | at _drainQueueStep (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:93:12) +0|whaticke | at _drainQueue (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:86:9) +0|whaticke | at Async._drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:102:5) +0|whaticke | at Async.drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:15:14) +0|whaticke | at process.processImmediate (node:internal/timers:485:21) +0|whaticke | at process.topLevelDomainCallback (node:domain:161:15) +0|whaticke | at process.callbackTrampoline (node:internal/async_hooks:128:24) { +0|whaticke | errors: [ +0|whaticke | ValidationErrorItem { +0|whaticke | message: 'number must be unique', +0|whaticke | type: 'unique violation', +0|whaticke | path: 'number', +0|whaticke | value: '5492478577503', +0|whaticke | origin: 'DB', +0|whaticke | instance: [Contact], +0|whaticke | validatorKey: 'not_unique', +0|whaticke | validatorName: null, +0|whaticke | validatorArgs: [] +0|whaticke | } +0|whaticke | ], +0|whaticke | fields: { number: '5492478577503' }, +0|whaticke | parent: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478577503) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'Agustín Navarro', +0|whaticke | '5492478577503', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/609063473_1101333805338792_6890609188563188958_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gH4VxYMzjqB8-9BGCy5YVrE1bazdI_zyof9gXyoAuJmzA&oe=69824577&_nc_sid=5e03e0&_nc_cat=105', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '124777406664808@lid', +0|whaticke | '2026-01-24 17:47:06.975 -03:00', +0|whaticke | '2026-01-24 17:47:06.975 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | original: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:489:12) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478577503) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'Agustín Navarro', +0|whaticke | '5492478577503', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/609063473_1101333805338792_6890609188563188958_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gH4VxYMzjqB8-9BGCy5YVrE1bazdI_zyof9gXyoAuJmzA&oe=69824577&_nc_sid=5e03e0&_nc_cat=105', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '124777406664808@lid', +0|whaticke | '2026-01-24 17:47:06.975 -03:00', +0|whaticke | '2026-01-24 17:47:06.975 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } +0|whaticke | Error creando contacto: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error +0|whaticke | at Query.formatError (/home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:324:18) +0|whaticke | at /home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:72:18 +0|whaticke | at tryCatcher (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/util.js:16:23) +0|whaticke | at Promise._settlePromiseFromHandler (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:547:31) +0|whaticke | at Promise._settlePromise (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:604:18) +0|whaticke | at Promise._settlePromise0 (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:649:10) +0|whaticke | at Promise._settlePromises (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:725:18) +0|whaticke | at _drainQueueStep (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:93:12) +0|whaticke | at _drainQueue (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:86:9) +0|whaticke | at Async._drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:102:5) +0|whaticke | at Async.drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:15:14) +0|whaticke | at process.processImmediate (node:internal/timers:485:21) +0|whaticke | at process.topLevelDomainCallback (node:domain:161:15) +0|whaticke | at process.callbackTrampoline (node:internal/async_hooks:128:24) { +0|whaticke | errors: [ +0|whaticke | ValidationErrorItem { +0|whaticke | message: 'number must be unique', +0|whaticke | type: 'unique violation', +0|whaticke | path: 'number', +0|whaticke | value: '5492478577503', +0|whaticke | origin: 'DB', +0|whaticke | instance: [Contact], +0|whaticke | validatorKey: 'not_unique', +0|whaticke | validatorName: null, +0|whaticke | validatorArgs: [] +0|whaticke | } +0|whaticke | ], +0|whaticke | fields: { number: '5492478577503' }, +0|whaticke | parent: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:552:15) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478577503) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'Agustín Navarro', +0|whaticke | '5492478577503', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/609063473_1101333805338792_6890609188563188958_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gH4VxYMzjqB8-9BGCy5YVrE1bazdI_zyof9gXyoAuJmzA&oe=69824577&_nc_sid=5e03e0&_nc_cat=105', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '124777406664808@lid', +0|whaticke | '2026-01-24 19:26:47.950 -03:00', +0|whaticke | '2026-01-24 19:26:47.950 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | original: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:552:15) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478577503) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'Agustín Navarro', +0|whaticke | '5492478577503', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/609063473_1101333805338792_6890609188563188958_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gH4VxYMzjqB8-9BGCy5YVrE1bazdI_zyof9gXyoAuJmzA&oe=69824577&_nc_sid=5e03e0&_nc_cat=105', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '124777406664808@lid', +0|whaticke | '2026-01-24 19:26:47.950 -03:00', +0|whaticke | '2026-01-24 19:26:47.950 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } +0|whaticke | ❌ ERROR CRÍTICO: No se encontró contacto existente a pesar del error de duplicado +0|whaticke | Número: 5492478577503 +0|whaticke | Company ID: 1 +0|whaticke | ❌ Error en handleMessage: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error +0|whaticke | at Query.formatError (/home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:324:18) +0|whaticke | at /home/whaticketapp/whaticket-free/backend/node_modules/sequelize/lib/dialects/postgres/query.js:72:18 +0|whaticke | at tryCatcher (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/util.js:16:23) +0|whaticke | at Promise._settlePromiseFromHandler (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:547:31) +0|whaticke | at Promise._settlePromise (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:604:18) +0|whaticke | at Promise._settlePromise0 (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:649:10) +0|whaticke | at Promise._settlePromises (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/promise.js:725:18) +0|whaticke | at _drainQueueStep (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:93:12) +0|whaticke | at _drainQueue (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:86:9) +0|whaticke | at Async._drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:102:5) +0|whaticke | at Async.drainQueues (/home/whaticketapp/whaticket-free/backend/node_modules/bluebird/js/release/async.js:15:14) +0|whaticke | at process.processImmediate (node:internal/timers:485:21) +0|whaticke | at process.topLevelDomainCallback (node:domain:161:15) +0|whaticke | at process.callbackTrampoline (node:internal/async_hooks:128:24) { +0|whaticke | errors: [ +0|whaticke | ValidationErrorItem { +0|whaticke | message: 'number must be unique', +0|whaticke | type: 'unique violation', +0|whaticke | path: 'number', +0|whaticke | value: '5492478577503', +0|whaticke | origin: 'DB', +0|whaticke | instance: [Contact], +0|whaticke | validatorKey: 'not_unique', +0|whaticke | validatorName: null, +0|whaticke | validatorArgs: [] +0|whaticke | } +0|whaticke | ], +0|whaticke | fields: { number: '5492478577503' }, +0|whaticke | parent: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:552:15) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478577503) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'Agustín Navarro', +0|whaticke | '5492478577503', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/609063473_1101333805338792_6890609188563188958_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gH4VxYMzjqB8-9BGCy5YVrE1bazdI_zyof9gXyoAuJmzA&oe=69824577&_nc_sid=5e03e0&_nc_cat=105', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '124777406664808@lid', +0|whaticke | '2026-01-24 19:26:47.950 -03:00', +0|whaticke | '2026-01-24 19:26:47.950 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | original: error: duplicate key value violates unique constraint "Contacts_number_key" +0|whaticke | at Parser.parseErrorMessage (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:285:98) +0|whaticke | at Parser.handlePacket (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:122:29) +0|whaticke | at Parser.parse (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/parser.js:35:38) +0|whaticke | at Socket. (/home/whaticketapp/whaticket-free/backend/node_modules/pg-protocol/dist/index.js:11:42) +0|whaticke | at Socket.emit (node:events:519:28) +0|whaticke | at Socket.emit (node:domain:552:15) +0|whaticke | at addChunk (node:internal/streams/readable:561:12) +0|whaticke | at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) +0|whaticke | at Readable.push (node:internal/streams/readable:392:5) +0|whaticke | at TCP.onStreamRead (node:internal/stream_base_commons:189:23) +0|whaticke | at TCP.callbackTrampoline (node:internal/async_hooks:130:17) { +0|whaticke | length: 217, +0|whaticke | severity: 'ERROR', +0|whaticke | code: '23505', +0|whaticke | detail: 'Key (number)=(5492478577503) already exists.', +0|whaticke | hint: undefined, +0|whaticke | position: undefined, +0|whaticke | internalPosition: undefined, +0|whaticke | internalQuery: undefined, +0|whaticke | where: undefined, +0|whaticke | schema: 'public', +0|whaticke | table: 'Contacts', +0|whaticke | column: undefined, +0|whaticke | dataType: undefined, +0|whaticke | constraint: 'Contacts_number_key', +0|whaticke | file: 'nbtinsert.c', +0|whaticke | line: '663', +0|whaticke | routine: '_bt_check_unique', +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;', +0|whaticke | parameters: [ +0|whaticke | 'Agustín Navarro', +0|whaticke | '5492478577503', +0|whaticke | '', +0|whaticke | 'https://pps.whatsapp.net/v/t61.24694-24/609063473_1101333805338792_6890609188563188958_n.jpg?stp=dst-jpg_s96x96_tt6&ccb=11-4&oh=01_Q5Aa3gH4VxYMzjqB8-9BGCy5YVrE1bazdI_zyof9gXyoAuJmzA&oe=69824577&_nc_sid=5e03e0&_nc_cat=105', +0|whaticke | false, +0|whaticke | '[]', +0|whaticke | 1, +0|whaticke | 11, +0|whaticke | '124777406664808@lid', +0|whaticke | '2026-01-24 19:26:47.950 -03:00', +0|whaticke | '2026-01-24 19:26:47.950 -03:00' +0|whaticke | ] +0|whaticke | }, +0|whaticke | sql: 'INSERT INTO "Contacts" ("id","name","number","email","profilePicUrl","isGroup","extrainfo","companyId","whatsappId","lid","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *;' +0|whaticke | } + +/home/whaticketapp/.pm2/logs/whaticket-backend-out-0.log last 1000 lines: +0|whaticke | [22:25:03.704] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:04.282] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:04.282] INFO: Socket ID: +0|whaticke | [22:25:04.282] INFO: Handshake query: +0|whaticke | [22:25:04.282] INFO: Token param: +0|whaticke | [22:25:04.283] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:04.408] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:04.408] INFO: Socket ID: +0|whaticke | [22:25:04.408] INFO: Handshake query: +0|whaticke | [22:25:04.408] INFO: Token param: +0|whaticke | [22:25:04.409] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:06.087] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:06.088] INFO: Socket ID: +0|whaticke | [22:25:06.088] INFO: Handshake query: +0|whaticke | [22:25:06.088] INFO: Token param: +0|whaticke | [22:25:06.089] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:06.440] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:06.440] INFO: Socket ID: +0|whaticke | [22:25:06.440] INFO: Handshake query: +0|whaticke | [22:25:06.440] INFO: Token param: +0|whaticke | [22:25:06.441] ERROR: Middleware authentication FAILED: +0|whaticke | [22:25:15.241] INFO: Client disconnected: +0|whaticke | [22:25:15.242] INFO: Client disconnected: +0|whaticke | [22:25:15.242] INFO: Client disconnected: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:16.812] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:16.812] INFO: Socket ID: +0|whaticke | [22:25:16.812] INFO: Handshake query: +0|whaticke | [22:25:16.813] INFO: Token param: +0|whaticke | [22:25:16.813] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:16.813] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:16.813] INFO: Socket ID: +0|whaticke | [22:25:16.813] INFO: Attached user ID: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:16.816] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:16.816] INFO: Socket ID: +0|whaticke | [22:25:16.817] INFO: Handshake query: +0|whaticke | [22:25:16.818] INFO: Token param: +0|whaticke | [22:25:16.818] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:16.819] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:16.819] INFO: Socket ID: +0|whaticke | [22:25:16.820] INFO: Attached user ID: +0|whaticke | [22:25:16.825] INFO: Client Connected - User: +0|whaticke | [22:25:16.827] INFO: Client Connected - User: +0|whaticke | [22:25:17.372] INFO: Ticket 71 messages read +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:17.585] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:17.585] INFO: Socket ID: +0|whaticke | [22:25:17.585] INFO: Handshake query: +0|whaticke | [22:25:17.586] INFO: Token param: +0|whaticke | [22:25:17.586] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:17.587] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:17.587] INFO: Socket ID: +0|whaticke | [22:25:17.587] INFO: Attached user ID: +0|whaticke | [22:25:17.597] INFO: Client Connected - User: +0|whaticke | [22:25:18.370] INFO: Client disconnected: +0|whaticke | [22:25:18.371] INFO: Client disconnected: +0|whaticke | [22:25:18.371] INFO: Client disconnected: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:18.427] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:18.428] INFO: Socket ID: +0|whaticke | [22:25:18.428] INFO: Handshake query: +0|whaticke | [22:25:18.428] INFO: Token param: +0|whaticke | [22:25:18.429] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:18.429] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:18.430] INFO: Socket ID: +0|whaticke | [22:25:18.430] INFO: Attached user ID: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:18.433] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:18.433] INFO: Socket ID: +0|whaticke | [22:25:18.433] INFO: Handshake query: +0|whaticke | [22:25:18.433] INFO: Token param: +0|whaticke | [22:25:18.433] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:18.434] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:18.434] INFO: Socket ID: +0|whaticke | [22:25:18.434] INFO: Attached user ID: +0|whaticke | [22:25:18.461] INFO: Client Connected - User: +0|whaticke | [22:25:18.465] INFO: Client Connected - User: +0|whaticke | [22:25:18.863] INFO: Ticket 72 messages read +0|whaticke | [22:25:19.061] INFO: Client disconnected: +0|whaticke | [22:25:19.061] INFO: Client disconnected: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:19.062] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:19.063] INFO: Socket ID: +0|whaticke | [22:25:19.063] INFO: Handshake query: +0|whaticke | [22:25:19.063] INFO: Token param: +0|whaticke | [22:25:19.064] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:19.065] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:19.065] INFO: Socket ID: +0|whaticke | [22:25:19.065] INFO: Attached user ID: +0|whaticke | [22:25:19.075] INFO: Client Connected - User: +0|whaticke | [22:25:19.169] INFO: Client disconnected: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:19.179] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:19.179] INFO: Socket ID: +0|whaticke | [22:25:19.179] INFO: Handshake query: +0|whaticke | [22:25:19.179] INFO: Token param: +0|whaticke | [22:25:19.180] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:19.180] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:19.180] INFO: Socket ID: +0|whaticke | [22:25:19.180] INFO: Attached user ID: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:19.260] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:19.260] INFO: Socket ID: +0|whaticke | [22:25:19.260] INFO: Handshake query: +0|whaticke | [22:25:19.260] INFO: Token param: +0|whaticke | [22:25:19.261] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:19.261] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:19.261] INFO: Socket ID: +0|whaticke | [22:25:19.261] INFO: Attached user ID: +0|whaticke | [22:25:19.265] INFO: Client Connected - User: +0|whaticke | [22:25:19.268] INFO: Client Connected - User: +0|whaticke | [22:25:19.662] INFO: Ticket 70 messages read +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:19.766] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:19.767] INFO: Socket ID: +0|whaticke | [22:25:19.767] INFO: Handshake query: +0|whaticke | [22:25:19.767] INFO: Token param: +0|whaticke | [22:25:19.767] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:19.768] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:19.768] INFO: Socket ID: +0|whaticke | [22:25:19.768] INFO: Attached user ID: +0|whaticke | [22:25:19.776] INFO: Client Connected - User: +0|whaticke | [22:25:20.025] INFO: Client disconnected: +0|whaticke | [22:25:20.025] INFO: Client disconnected: +0|whaticke | [22:25:20.028] INFO: Client disconnected: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:20.087] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:20.087] INFO: Socket ID: +0|whaticke | [22:25:20.088] INFO: Handshake query: +0|whaticke | [22:25:20.088] INFO: Token param: +0|whaticke | [22:25:20.088] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:20.090] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:20.090] INFO: Socket ID: +0|whaticke | [22:25:20.090] INFO: Attached user ID: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:20.095] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:20.095] INFO: Socket ID: +0|whaticke | [22:25:20.095] INFO: Handshake query: +0|whaticke | [22:25:20.096] INFO: Token param: +0|whaticke | [22:25:20.096] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:20.097] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:20.097] INFO: Socket ID: +0|whaticke | [22:25:20.097] INFO: Attached user ID: +0|whaticke | [22:25:20.101] INFO: Client Connected - User: +0|whaticke | [22:25:20.102] INFO: Client Connected - User: +0|whaticke | [22:25:20.617] INFO: Ticket 73 messages read +0|whaticke | [22:25:20.719] INFO: Client disconnected: +0|whaticke | [22:25:20.719] INFO: Client disconnected: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:20.777] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:20.777] INFO: Socket ID: +0|whaticke | [22:25:20.777] INFO: Handshake query: +0|whaticke | [22:25:20.777] INFO: Token param: +0|whaticke | [22:25:20.777] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:20.778] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:20.778] INFO: Socket ID: +0|whaticke | [22:25:20.778] INFO: Attached user ID: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:20.780] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:20.780] INFO: Socket ID: +0|whaticke | [22:25:20.780] INFO: Handshake query: +0|whaticke | [22:25:20.780] INFO: Token param: +0|whaticke | [22:25:20.780] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:20.781] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:20.781] INFO: Socket ID: +0|whaticke | [22:25:20.781] INFO: Attached user ID: +0|whaticke | [22:25:20.786] INFO: Client Connected - User: +0|whaticke | [22:25:20.789] INFO: Client Connected - User: +0|whaticke | [22:25:21.244] INFO: Ticket 71 messages read +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:21.352] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:21.358] INFO: Socket ID: +0|whaticke | [22:25:21.359] INFO: Handshake query: +0|whaticke | [22:25:21.359] INFO: Token param: +0|whaticke | [22:25:21.359] INFO: Middleware SUCCESS - User ID: +0|whaticke | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticke | [22:25:21.360] INFO: === CONNECTION EVENT === +0|whaticke | [22:25:21.360] INFO: Socket ID: +0|whaticke | [22:25:21.360] INFO: Attached user ID: +0|whaticke | [22:25:21.366] INFO: Client Connected - User: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:49.298] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:49.298] INFO: Socket ID: +0|whaticke | [22:25:49.298] INFO: Handshake query: +0|whaticke | [22:25:49.298] INFO: Token param: +0|whaticke | [22:25:49.298] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:50.309] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:50.309] INFO: Socket ID: +0|whaticke | [22:25:50.309] INFO: Handshake query: +0|whaticke | [22:25:50.309] INFO: Token param: +0|whaticke | [22:25:50.310] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:51.389] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:51.389] INFO: Socket ID: +0|whaticke | [22:25:51.389] INFO: Handshake query: +0|whaticke | [22:25:51.389] INFO: Token param: +0|whaticke | [22:25:51.392] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:51.394] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:51.394] INFO: Socket ID: +0|whaticke | [22:25:51.394] INFO: Handshake query: +0|whaticke | [22:25:51.394] INFO: Token param: +0|whaticke | [22:25:51.394] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:52.301] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:52.302] INFO: Socket ID: +0|whaticke | [22:25:52.302] INFO: Handshake query: +0|whaticke | [22:25:52.302] INFO: Token param: +0|whaticke | [22:25:52.302] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:25:53.362] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:25:53.362] INFO: Socket ID: +0|whaticke | [22:25:53.362] INFO: Handshake query: +0|whaticke | [22:25:53.362] INFO: Token param: +0|whaticke | [22:25:53.362] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:26:35.032] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:26:35.033] INFO: Socket ID: +0|whaticke | [22:26:35.033] INFO: Handshake query: +0|whaticke | [22:26:35.033] INFO: Token param: +0|whaticke | [22:26:35.033] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:26:36.457] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:26:36.458] INFO: Socket ID: +0|whaticke | [22:26:36.458] INFO: Handshake query: +0|whaticke | [22:26:36.458] INFO: Token param: +0|whaticke | [22:26:36.458] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:26:37.881] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:26:37.881] INFO: Socket ID: +0|whaticke | [22:26:37.881] INFO: Handshake query: +0|whaticke | [22:26:37.881] INFO: Token param: +0|whaticke | [22:26:37.882] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:26:38.021] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:26:38.021] INFO: Socket ID: +0|whaticke | [22:26:38.021] INFO: Handshake query: +0|whaticke | [22:26:38.021] INFO: Token param: +0|whaticke | [22:26:38.021] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:26:38.023] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:26:38.024] INFO: Socket ID: +0|whaticke | [22:26:38.024] INFO: Handshake query: +0|whaticke | [22:26:38.024] INFO: Token param: +0|whaticke | [22:26:38.024] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:26:39.548] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:26:39.549] INFO: Socket ID: +0|whaticke | [22:26:39.549] INFO: Handshake query: +0|whaticke | [22:26:39.549] INFO: Token param: +0|whaticke | [22:26:39.549] ERROR: Middleware authentication FAILED: +0|whaticke | +0|whaticke | 📨 ========== NUEVO MENSAJE ========== +0|whaticke | 🆔 ID: AC9712F9CF... +0|whaticke | 📞 RemoteJid: 124777406664808@lid +0|whaticke | 👤 FromMe: NO +0|whaticke | 👥 Participant: N/A +0|whaticke | 📛 PushName: Agustín Navarro +0|whaticke | ⏰ Timestamp: 10:26:46 PM +0|whaticke | 🔍 ANALIZANDO JID: 124777406664808@lid +0|whaticke | Tipo: LID +0|whaticke | +0|whaticke | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticke | Input: "124777406664808@lid" +0|whaticke | SessionId: "11" +0|whaticke | Clean input: "124777406664808" (15 dígitos) +0|whaticke | Tipo: Número normal +0|whaticke | +0|whaticke | 🔍 Buscando mapping exacto: lid-mapping-124777406664808_reverse +0|whaticke | ✅ Encontrado 1 mapping(s) exacto(s) +0|whaticke | whatsappId: 11 +0|whaticke | value: "5492478577503" +0|whaticke | 🎯 ÉXITO: 124777406664808 -> 5492478577503 +0|whaticke | 🎯 [BAILEYS_SESSIONS] CONVERSIÓN EXITOSA: +0|whaticke | Input original: 124777406664808@lid +0|whaticke | Número real: 5492478577503 +0|whaticke | JID real: 5492478577503@s.whatsapp.net +0|whaticke | Es LID? true +0|whaticke | ✅ FIN obtenerNumeroRealDeMensaje - BAILEYS_SESSIONS +0|whaticke | +0|whaticke | 📞 === ANTES DE verifyContact === +0|whaticke | msgContact recibido: +0|whaticke | id: 5492478577503@s.whatsapp.net +0|whaticke | name: Agustín Navarro +0|whaticke | number: 5492478577503 +0|whaticke | esLid: SÍ +0|whaticke | lid: 124777406664808@lid +0|whaticke | originalJid: 124777406664808@lid +0|whaticke | =============================== +0|whaticke | +0|whaticke | 🔴🔴🔴 VERIFYCONTACT - NUEVA VERSIÓN EJECUTÁNDOSE 🔴🔴🔴 +0|whaticke | +0|whaticke | 🔍 === verifyContact INICIO === +0|whaticke | 📥 DATOS DE ENTRADA: +0|whaticke | ID: 5492478577503@s.whatsapp.net +0|whaticke | Nombre: Agustín Navarro +0|whaticke | Número recibido: 5492478577503 +0|whaticke | Es LID?: SÍ +0|whaticke | LID: 124777406664808@lid +0|whaticke | JID original: 124777406664808@lid +0|whaticke | 🖼️ Foto de perfil obtenida +0|whaticke | 👤 VERIFICANDO CONTACTO: +0|whaticke | ID recibido: 5492478577503@s.whatsapp.net +0|whaticke | Número en msgContact: 5492478577503 +0|whaticke | Es LID?: SÍ +0|whaticke | LID original: 124777406664808@lid +0|whaticke | JID original: 124777406664808@lid +0|whaticke | 🔍 Contacto tiene LID, verificando número real: 124777406664808@lid +0|whaticke | +0|whaticke | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticke | Input: "124777406664808@lid" +0|whaticke | SessionId: "11" +0|whaticke | Clean input: "124777406664808" (15 dígitos) +0|whaticke | Tipo: Número normal +0|whaticke | +0|whaticke | 🔍 Buscando mapping exacto: lid-mapping-124777406664808_reverse +0|whaticke | ✅ Encontrado 1 mapping(s) exacto(s) +0|whaticke | whatsappId: 11 +0|whaticke | value: "5492478577503" +0|whaticke | 🎯 ÉXITO: 124777406664808 -> 5492478577503 +0|whaticke | ✅ [BAILEYS_SESSIONS] LID convertido a número en verifyContact: 5492478577503 +0|whaticke | 📝 DATOS FINALES DEL CONTACTO: +0|whaticke | Nombre: Agustín Navarro +0|whaticke | Número: 5492478577503 +0|whaticke | LID guardado: 124777406664808@lid +0|whaticke | Company ID: 1 +0|whaticke | WhatsApp ID: 11 +0|whaticke | originaljid: 124777406664808@lid +0|whaticke | ⚠️ ERROR DE DUPLICADO DETECTADO +0|whaticke | Buscando contacto existente para: 5492478577503 +0|whaticke | Company ID: 1 +0|whaticke | [22:26:47.968] ERROR: Error handling whatsapp message: Err: SequelizeUniqueConstraintError: Validation error +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:27:21.345] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:27:21.346] INFO: Socket ID: +0|whaticke | [22:27:21.346] INFO: Handshake query: +0|whaticke | [22:27:21.346] INFO: Token param: +0|whaticke | [22:27:21.346] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:27:22.986] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:27:22.986] INFO: Socket ID: +0|whaticke | [22:27:22.986] INFO: Handshake query: +0|whaticke | [22:27:22.986] INFO: Token param: +0|whaticke | [22:27:22.986] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:27:23.973] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:27:23.973] INFO: Socket ID: +0|whaticke | [22:27:23.973] INFO: Handshake query: +0|whaticke | [22:27:23.973] INFO: Token param: +0|whaticke | [22:27:23.974] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:27:24.092] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:27:24.092] INFO: Socket ID: +0|whaticke | [22:27:24.092] INFO: Handshake query: +0|whaticke | [22:27:24.093] INFO: Token param: +0|whaticke | [22:27:24.093] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:27:24.223] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:27:24.223] INFO: Socket ID: +0|whaticke | [22:27:24.223] INFO: Handshake query: +0|whaticke | [22:27:24.223] INFO: Token param: +0|whaticke | [22:27:24.223] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:27:25.679] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:27:25.680] INFO: Socket ID: +0|whaticke | [22:27:25.680] INFO: Handshake query: +0|whaticke | [22:27:25.680] INFO: Token param: +0|whaticke | [22:27:25.680] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:07.957] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:07.957] INFO: Socket ID: +0|whaticke | [22:28:07.957] INFO: Handshake query: +0|whaticke | [22:28:07.957] INFO: Token param: +0|whaticke | [22:28:07.958] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:09.565] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:09.566] INFO: Socket ID: +0|whaticke | [22:28:09.566] INFO: Handshake query: +0|whaticke | [22:28:09.566] INFO: Token param: +0|whaticke | [22:28:09.566] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:10.149] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:10.149] INFO: Socket ID: +0|whaticke | [22:28:10.150] INFO: Handshake query: +0|whaticke | [22:28:10.150] INFO: Token param: +0|whaticke | [22:28:10.151] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:10.156] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:10.156] INFO: Socket ID: +0|whaticke | [22:28:10.157] INFO: Handshake query: +0|whaticke | [22:28:10.157] INFO: Token param: +0|whaticke | [22:28:10.157] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:10.425] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:10.425] INFO: Socket ID: +0|whaticke | [22:28:10.425] INFO: Handshake query: +0|whaticke | [22:28:10.425] INFO: Token param: +0|whaticke | [22:28:10.426] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:12.237] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:12.237] INFO: Socket ID: +0|whaticke | [22:28:12.237] INFO: Handshake query: +0|whaticke | [22:28:12.237] INFO: Token param: +0|whaticke | [22:28:12.238] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:54.287] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:54.287] INFO: Socket ID: +0|whaticke | [22:28:54.288] INFO: Handshake query: +0|whaticke | [22:28:54.288] INFO: Token param: +0|whaticke | [22:28:54.288] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:55.641] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:55.642] INFO: Socket ID: +0|whaticke | [22:28:55.642] INFO: Handshake query: +0|whaticke | [22:28:55.642] INFO: Token param: +0|whaticke | [22:28:55.642] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:56.348] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:56.348] INFO: Socket ID: +0|whaticke | [22:28:56.349] INFO: Handshake query: +0|whaticke | [22:28:56.349] INFO: Token param: +0|whaticke | [22:28:56.349] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:56.352] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:56.353] INFO: Socket ID: +0|whaticke | [22:28:56.353] INFO: Handshake query: +0|whaticke | [22:28:56.353] INFO: Token param: +0|whaticke | [22:28:56.353] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:56.944] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:56.945] INFO: Socket ID: +0|whaticke | [22:28:56.945] INFO: Handshake query: +0|whaticke | [22:28:56.945] INFO: Token param: +0|whaticke | [22:28:56.945] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:28:58.743] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:28:58.743] INFO: Socket ID: +0|whaticke | [22:28:58.743] INFO: Handshake query: +0|whaticke | [22:28:58.743] INFO: Token param: +0|whaticke | [22:28:58.743] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:29:40.352] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:29:40.353] INFO: Socket ID: +0|whaticke | [22:29:40.353] INFO: Handshake query: +0|whaticke | [22:29:40.353] INFO: Token param: +0|whaticke | [22:29:40.354] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:29:42.320] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:29:42.321] INFO: Socket ID: +0|whaticke | [22:29:42.322] INFO: Handshake query: +0|whaticke | [22:29:42.322] INFO: Token param: +0|whaticke | [22:29:42.323] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:29:43.311] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:29:43.312] INFO: Socket ID: +0|whaticke | [22:29:43.312] INFO: Handshake query: +0|whaticke | [22:29:43.313] INFO: Token param: +0|whaticke | [22:29:43.313] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:29:43.332] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:29:43.332] INFO: Socket ID: +0|whaticke | [22:29:43.333] INFO: Handshake query: +0|whaticke | [22:29:43.333] INFO: Token param: +0|whaticke | [22:29:43.334] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:29:43.350] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:29:43.351] INFO: Socket ID: +0|whaticke | [22:29:43.351] INFO: Handshake query: +0|whaticke | [22:29:43.351] INFO: Token param: +0|whaticke | [22:29:43.352] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:29:45.298] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:29:45.298] INFO: Socket ID: +0|whaticke | [22:29:45.298] INFO: Handshake query: +0|whaticke | [22:29:45.298] INFO: Token param: +0|whaticke | [22:29:45.298] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:30:27.306] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:30:27.307] INFO: Socket ID: +0|whaticke | [22:30:27.307] INFO: Handshake query: +0|whaticke | [22:30:27.307] INFO: Token param: +0|whaticke | [22:30:27.307] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:30:29.696] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:30:29.696] INFO: Socket ID: +0|whaticke | [22:30:29.696] INFO: Handshake query: +0|whaticke | [22:30:29.696] INFO: Token param: +0|whaticke | [22:30:29.697] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:30:30.355] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:30:30.355] INFO: Socket ID: +0|whaticke | [22:30:30.356] INFO: Handshake query: +0|whaticke | [22:30:30.356] INFO: Token param: +0|whaticke | [22:30:30.356] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:30:30.360] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:30:30.360] INFO: Socket ID: +0|whaticke | [22:30:30.360] INFO: Handshake query: +0|whaticke | [22:30:30.360] INFO: Token param: +0|whaticke | [22:30:30.361] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:30:30.365] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:30:30.366] INFO: Socket ID: +0|whaticke | [22:30:30.366] INFO: Handshake query: +0|whaticke | [22:30:30.366] INFO: Token param: +0|whaticke | [22:30:30.366] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:30:32.437] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:30:32.437] INFO: Socket ID: +0|whaticke | [22:30:32.437] INFO: Handshake query: +0|whaticke | [22:30:32.437] INFO: Token param: +0|whaticke | [22:30:32.438] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:31:13.304] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:31:13.304] INFO: Socket ID: +0|whaticke | [22:31:13.305] INFO: Handshake query: +0|whaticke | [22:31:13.305] INFO: Token param: +0|whaticke | [22:31:13.305] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:31:15.289] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:31:15.290] INFO: Socket ID: +0|whaticke | [22:31:15.290] INFO: Handshake query: +0|whaticke | [22:31:15.290] INFO: Token param: +0|whaticke | [22:31:15.290] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:31:16.344] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:31:16.345] INFO: Socket ID: +0|whaticke | [22:31:16.345] INFO: Handshake query: +0|whaticke | [22:31:16.345] INFO: Token param: +0|whaticke | [22:31:16.345] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:31:16.347] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:31:16.347] INFO: Socket ID: +0|whaticke | [22:31:16.347] INFO: Handshake query: +0|whaticke | [22:31:16.347] INFO: Token param: +0|whaticke | [22:31:16.347] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:31:17.328] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:31:17.328] INFO: Socket ID: +0|whaticke | [22:31:17.328] INFO: Handshake query: +0|whaticke | [22:31:17.329] INFO: Token param: +0|whaticke | [22:31:17.329] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:31:18.307] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:31:18.307] INFO: Socket ID: +0|whaticke | [22:31:18.307] INFO: Handshake query: +0|whaticke | [22:31:18.307] INFO: Token param: +0|whaticke | [22:31:18.308] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:31:59.471] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:31:59.471] INFO: Socket ID: +0|whaticke | [22:31:59.472] INFO: Handshake query: +0|whaticke | [22:31:59.472] INFO: Token param: +0|whaticke | [22:31:59.472] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:02.427] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:02.427] INFO: Socket ID: +0|whaticke | [22:32:02.427] INFO: Handshake query: +0|whaticke | [22:32:02.427] INFO: Token param: +0|whaticke | [22:32:02.427] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:02.431] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:02.431] INFO: Socket ID: +0|whaticke | [22:32:02.431] INFO: Handshake query: +0|whaticke | [22:32:02.431] INFO: Token param: +0|whaticke | [22:32:02.432] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:03.370] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:03.370] INFO: Socket ID: +0|whaticke | [22:32:03.371] INFO: Handshake query: +0|whaticke | [22:32:03.371] INFO: Token param: +0|whaticke | [22:32:03.371] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:03.663] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:03.663] INFO: Socket ID: +0|whaticke | [22:32:03.664] INFO: Handshake query: +0|whaticke | [22:32:03.664] INFO: Token param: +0|whaticke | [22:32:03.664] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:05.362] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:05.362] INFO: Socket ID: +0|whaticke | [22:32:05.363] INFO: Handshake query: +0|whaticke | [22:32:05.363] INFO: Token param: +0|whaticke | [22:32:05.363] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:45.306] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:45.306] INFO: Socket ID: +0|whaticke | [22:32:45.306] INFO: Handshake query: +0|whaticke | [22:32:45.306] INFO: Token param: +0|whaticke | [22:32:45.307] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:48.329] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:48.329] INFO: Socket ID: +0|whaticke | [22:32:48.329] INFO: Handshake query: +0|whaticke | [22:32:48.329] INFO: Token param: +0|whaticke | [22:32:48.330] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:49.296] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:49.297] INFO: Socket ID: +0|whaticke | [22:32:49.297] INFO: Handshake query: +0|whaticke | [22:32:49.297] INFO: Token param: +0|whaticke | [22:32:49.297] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:49.299] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:49.299] INFO: Socket ID: +0|whaticke | [22:32:49.299] INFO: Handshake query: +0|whaticke | [22:32:49.299] INFO: Token param: +0|whaticke | [22:32:49.300] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:50.308] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:50.308] INFO: Socket ID: +0|whaticke | [22:32:50.308] INFO: Handshake query: +0|whaticke | [22:32:50.308] INFO: Token param: +0|whaticke | [22:32:50.309] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:32:51.294] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:32:51.294] INFO: Socket ID: +0|whaticke | [22:32:51.294] INFO: Handshake query: +0|whaticke | [22:32:51.294] INFO: Token param: +0|whaticke | [22:32:51.295] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:33:31.305] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:33:31.305] INFO: Socket ID: +0|whaticke | [22:33:31.306] INFO: Handshake query: +0|whaticke | [22:33:31.306] INFO: Token param: +0|whaticke | [22:33:31.306] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:33:35.334] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:33:35.334] INFO: Socket ID: +0|whaticke | [22:33:35.334] INFO: Handshake query: +0|whaticke | [22:33:35.334] INFO: Token param: +0|whaticke | [22:33:35.334] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:33:35.335] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:33:35.335] INFO: Socket ID: +0|whaticke | [22:33:35.335] INFO: Handshake query: +0|whaticke | [22:33:35.335] INFO: Token param: +0|whaticke | [22:33:35.336] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:33:36.299] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:33:36.299] INFO: Socket ID: +0|whaticke | [22:33:36.299] INFO: Handshake query: +0|whaticke | [22:33:36.299] INFO: Token param: +0|whaticke | [22:33:36.299] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:33:36.302] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:33:36.302] INFO: Socket ID: +0|whaticke | [22:33:36.302] INFO: Handshake query: +0|whaticke | [22:33:36.302] INFO: Token param: +0|whaticke | [22:33:36.302] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:33:38.388] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:33:38.388] INFO: Socket ID: +0|whaticke | [22:33:38.388] INFO: Handshake query: +0|whaticke | [22:33:38.388] INFO: Token param: +0|whaticke | [22:33:38.389] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:34:17.325] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:34:17.325] INFO: Socket ID: +0|whaticke | [22:34:17.325] INFO: Handshake query: +0|whaticke | [22:34:17.325] INFO: Token param: +0|whaticke | [22:34:17.325] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:34:22.295] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:34:22.295] INFO: Socket ID: +0|whaticke | [22:34:22.295] INFO: Handshake query: +0|whaticke | [22:34:22.295] INFO: Token param: +0|whaticke | [22:34:22.295] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:34:22.296] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:34:22.296] INFO: Socket ID: +0|whaticke | [22:34:22.296] INFO: Handshake query: +0|whaticke | [22:34:22.296] INFO: Token param: +0|whaticke | [22:34:22.297] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:34:23.340] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:34:23.340] INFO: Socket ID: +0|whaticke | [22:34:23.340] INFO: Handshake query: +0|whaticke | [22:34:23.340] INFO: Token param: +0|whaticke | [22:34:23.341] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:34:23.342] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:34:23.342] INFO: Socket ID: +0|whaticke | [22:34:23.342] INFO: Handshake query: +0|whaticke | [22:34:23.342] INFO: Token param: +0|whaticke | [22:34:23.342] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:34:25.302] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:34:25.302] INFO: Socket ID: +0|whaticke | [22:34:25.302] INFO: Handshake query: +0|whaticke | [22:34:25.302] INFO: Token param: +0|whaticke | [22:34:25.303] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:04.365] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:04.366] INFO: Socket ID: +0|whaticke | [22:35:04.366] INFO: Handshake query: +0|whaticke | [22:35:04.366] INFO: Token param: +0|whaticke | [22:35:04.366] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:08.306] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:08.306] INFO: Socket ID: +0|whaticke | [22:35:08.306] INFO: Handshake query: +0|whaticke | [22:35:08.306] INFO: Token param: +0|whaticke | [22:35:08.306] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:08.308] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:08.308] INFO: Socket ID: +0|whaticke | [22:35:08.308] INFO: Handshake query: +0|whaticke | [22:35:08.308] INFO: Token param: +0|whaticke | [22:35:08.308] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:09.297] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:09.297] INFO: Socket ID: +0|whaticke | [22:35:09.297] INFO: Handshake query: +0|whaticke | [22:35:09.298] INFO: Token param: +0|whaticke | [22:35:09.298] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:10.377] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:10.377] INFO: Socket ID: +0|whaticke | [22:35:10.377] INFO: Handshake query: +0|whaticke | [22:35:10.377] INFO: Token param: +0|whaticke | [22:35:10.378] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:12.281] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:12.281] INFO: Socket ID: +0|whaticke | [22:35:12.281] INFO: Handshake query: +0|whaticke | [22:35:12.282] INFO: Token param: +0|whaticke | [22:35:12.282] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:51.561] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:51.561] INFO: Socket ID: +0|whaticke | [22:35:51.561] INFO: Handshake query: +0|whaticke | [22:35:51.561] INFO: Token param: +0|whaticke | [22:35:51.562] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:55.311] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:55.311] INFO: Socket ID: +0|whaticke | [22:35:55.311] INFO: Handshake query: +0|whaticke | [22:35:55.311] INFO: Token param: +0|whaticke | [22:35:55.312] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:55.313] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:55.314] INFO: Socket ID: +0|whaticke | [22:35:55.314] INFO: Handshake query: +0|whaticke | [22:35:55.314] INFO: Token param: +0|whaticke | [22:35:55.314] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:56.356] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:56.357] INFO: Socket ID: +0|whaticke | [22:35:56.357] INFO: Handshake query: +0|whaticke | [22:35:56.357] INFO: Token param: +0|whaticke | [22:35:56.357] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:57.308] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:57.308] INFO: Socket ID: +0|whaticke | [22:35:57.308] INFO: Handshake query: +0|whaticke | [22:35:57.308] INFO: Token param: +0|whaticke | [22:35:57.308] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:35:59.312] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:35:59.312] INFO: Socket ID: +0|whaticke | [22:35:59.312] INFO: Handshake query: +0|whaticke | [22:35:59.312] INFO: Token param: +0|whaticke | [22:35:59.313] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:36:38.307] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:36:38.307] INFO: Socket ID: +0|whaticke | [22:36:38.307] INFO: Handshake query: +0|whaticke | [22:36:38.307] INFO: Token param: +0|whaticke | [22:36:38.308] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:36:42.398] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:36:42.398] INFO: Socket ID: +0|whaticke | [22:36:42.398] INFO: Handshake query: +0|whaticke | [22:36:42.398] INFO: Token param: +0|whaticke | [22:36:42.398] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:36:42.400] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:36:42.401] INFO: Socket ID: +0|whaticke | [22:36:42.401] INFO: Handshake query: +0|whaticke | [22:36:42.401] INFO: Token param: +0|whaticke | [22:36:42.401] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:36:43.479] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:36:43.479] INFO: Socket ID: +0|whaticke | [22:36:43.480] INFO: Handshake query: +0|whaticke | [22:36:43.480] INFO: Token param: +0|whaticke | [22:36:43.480] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:36:44.554] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:36:44.554] INFO: Socket ID: +0|whaticke | [22:36:44.555] INFO: Handshake query: +0|whaticke | [22:36:44.555] INFO: Token param: +0|whaticke | [22:36:44.555] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:36:46.296] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:36:46.296] INFO: Socket ID: +0|whaticke | [22:36:46.296] INFO: Handshake query: +0|whaticke | [22:36:46.296] INFO: Token param: +0|whaticke | [22:36:46.297] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:37:25.372] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:37:25.372] INFO: Socket ID: +0|whaticke | [22:37:25.372] INFO: Handshake query: +0|whaticke | [22:37:25.372] INFO: Token param: +0|whaticke | [22:37:25.373] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:37:29.361] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:37:29.361] INFO: Socket ID: +0|whaticke | [22:37:29.361] INFO: Handshake query: +0|whaticke | [22:37:29.361] INFO: Token param: +0|whaticke | [22:37:29.361] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:37:29.363] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:37:29.363] INFO: Socket ID: +0|whaticke | [22:37:29.363] INFO: Handshake query: +0|whaticke | [22:37:29.363] INFO: Token param: +0|whaticke | [22:37:29.363] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:37:30.296] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:37:30.296] INFO: Socket ID: +0|whaticke | [22:37:30.296] INFO: Handshake query: +0|whaticke | [22:37:30.296] INFO: Token param: +0|whaticke | [22:37:30.297] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:37:31.660] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:37:31.660] INFO: Socket ID: +0|whaticke | [22:37:31.661] INFO: Handshake query: +0|whaticke | [22:37:31.661] INFO: Token param: +0|whaticke | [22:37:31.661] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:37:33.686] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:37:33.686] INFO: Socket ID: +0|whaticke | [22:37:33.686] INFO: Handshake query: +0|whaticke | [22:37:33.686] INFO: Token param: +0|whaticke | [22:37:33.686] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:38:12.399] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:38:12.399] INFO: Socket ID: +0|whaticke | [22:38:12.399] INFO: Handshake query: +0|whaticke | [22:38:12.399] INFO: Token param: +0|whaticke | [22:38:12.400] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:38:15.363] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:38:15.363] INFO: Socket ID: +0|whaticke | [22:38:15.363] INFO: Handshake query: +0|whaticke | [22:38:15.363] INFO: Token param: +0|whaticke | [22:38:15.364] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:38:15.365] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:38:15.365] INFO: Socket ID: +0|whaticke | [22:38:15.365] INFO: Handshake query: +0|whaticke | [22:38:15.365] INFO: Token param: +0|whaticke | [22:38:15.365] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:38:17.304] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:38:17.304] INFO: Socket ID: +0|whaticke | [22:38:17.304] INFO: Handshake query: +0|whaticke | [22:38:17.304] INFO: Token param: +0|whaticke | [22:38:17.304] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:38:18.312] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:38:18.312] INFO: Socket ID: +0|whaticke | [22:38:18.312] INFO: Handshake query: +0|whaticke | [22:38:18.312] INFO: Token param: +0|whaticke | [22:38:18.313] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:38:20.316] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:38:20.316] INFO: Socket ID: +0|whaticke | [22:38:20.316] INFO: Handshake query: +0|whaticke | [22:38:20.316] INFO: Token param: +0|whaticke | [22:38:20.316] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:38:59.326] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:38:59.326] INFO: Socket ID: +0|whaticke | [22:38:59.326] INFO: Handshake query: +0|whaticke | [22:38:59.326] INFO: Token param: +0|whaticke | [22:38:59.326] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:01.331] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:01.332] INFO: Socket ID: +0|whaticke | [22:39:01.332] INFO: Handshake query: +0|whaticke | [22:39:01.332] INFO: Token param: +0|whaticke | [22:39:01.332] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:02.313] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:02.313] INFO: Socket ID: +0|whaticke | [22:39:02.313] INFO: Handshake query: +0|whaticke | [22:39:02.313] INFO: Token param: +0|whaticke | [22:39:02.314] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:04.302] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:04.302] INFO: Socket ID: +0|whaticke | [22:39:04.302] INFO: Handshake query: +0|whaticke | [22:39:04.302] INFO: Token param: +0|whaticke | [22:39:04.303] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:05.297] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:05.297] INFO: Socket ID: +0|whaticke | [22:39:05.297] INFO: Handshake query: +0|whaticke | [22:39:05.297] INFO: Token param: +0|whaticke | [22:39:05.297] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:07.288] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:07.288] INFO: Socket ID: +0|whaticke | [22:39:07.288] INFO: Handshake query: +0|whaticke | [22:39:07.288] INFO: Token param: +0|whaticke | [22:39:07.288] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:46.334] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:46.334] INFO: Socket ID: +0|whaticke | [22:39:46.334] INFO: Handshake query: +0|whaticke | [22:39:46.334] INFO: Token param: +0|whaticke | [22:39:46.334] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:47.301] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:47.301] INFO: Socket ID: +0|whaticke | [22:39:47.301] INFO: Handshake query: +0|whaticke | [22:39:47.301] INFO: Token param: +0|whaticke | [22:39:47.301] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:49.299] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:49.299] INFO: Socket ID: +0|whaticke | [22:39:49.299] INFO: Handshake query: +0|whaticke | [22:39:49.300] INFO: Token param: +0|whaticke | [22:39:49.300] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:51.313] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:51.313] INFO: Socket ID: +0|whaticke | [22:39:51.313] INFO: Handshake query: +0|whaticke | [22:39:51.313] INFO: Token param: +0|whaticke | [22:39:51.314] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:52.496] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:52.496] INFO: Socket ID: +0|whaticke | [22:39:52.496] INFO: Handshake query: +0|whaticke | [22:39:52.496] INFO: Token param: +0|whaticke | [22:39:52.496] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:39:53.312] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:39:53.312] INFO: Socket ID: +0|whaticke | [22:39:53.312] INFO: Handshake query: +0|whaticke | [22:39:53.312] INFO: Token param: +0|whaticke | [22:39:53.313] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:40:32.319] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:40:32.319] INFO: Socket ID: +0|whaticke | [22:40:32.319] INFO: Handshake query: +0|whaticke | [22:40:32.319] INFO: Token param: +0|whaticke | [22:40:32.320] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:40:34.315] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:40:34.315] INFO: Socket ID: +0|whaticke | [22:40:34.315] INFO: Handshake query: +0|whaticke | [22:40:34.315] INFO: Token param: +0|whaticke | [22:40:34.316] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:40:35.322] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:40:35.322] INFO: Socket ID: +0|whaticke | [22:40:35.322] INFO: Handshake query: +0|whaticke | [22:40:35.323] INFO: Token param: +0|whaticke | [22:40:35.323] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:40:38.376] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:40:38.376] INFO: Socket ID: +0|whaticke | [22:40:38.376] INFO: Handshake query: +0|whaticke | [22:40:38.377] INFO: Token param: +0|whaticke | [22:40:38.377] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:40:38.422] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:40:38.423] INFO: Socket ID: +0|whaticke | [22:40:38.423] INFO: Handshake query: +0|whaticke | [22:40:38.423] INFO: Token param: +0|whaticke | [22:40:38.423] ERROR: Middleware authentication FAILED: +0|whaticke | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticke | [22:40:40.311] INFO: === MIDDLEWARE EXECUTED === +0|whaticke | [22:40:40.311] INFO: Socket ID: +0|whaticke | [22:40:40.311] INFO: Handshake query: +0|whaticke | [22:40:40.311] INFO: Token param: +0|whaticke | [22:40:40.311] ERROR: Middleware authentication FAILED: +0|whaticke | [22:40:48.162] INFO: Client disconnected: + +0|whaticket-backend | [22:40:54.670] INFO: === INITIO CALLED - WITH MIDDLEWARE === +0|whaticket-backend | [22:40:54.960] INFO: Server started on port: 8080 +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:40:55.430] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:40:55.431] INFO: Socket ID: +0|whaticket-backend | [22:40:55.431] INFO: Handshake query: +0|whaticket-backend | [22:40:55.431] INFO: Token param: +0|whaticket-backend | [22:40:55.433] ERROR: Middleware authentication FAILED: +0|whaticket-backend | [22:40:55.743] INFO: using WA v2.3000.1032141294, isLatest: true +0|whaticket-backend | [22:40:55.744] INFO: isMultidevice: true +0|whaticket-backend | [22:40:55.745] INFO: Starting session Ecommerce +0|whaticket-backend | 🔧 makeInMemoryStore() llamado +0|whaticket-backend | 🔧 store.bind() llamado, eventos disponibles: [ +0|whaticket-backend | 'process', +0|whaticket-backend | 'emit', +0|whaticket-backend | 'isBuffering', +0|whaticket-backend | 'buffer', +0|whaticket-backend | 'flush', +0|whaticket-backend | 'createBufferedFunction', +0|whaticket-backend | 'on', +0|whaticket-backend | 'off', +0|whaticket-backend | 'removeAllListeners' +0|whaticket-backend | ] +0|whaticket-backend | 🔧 store.bind() llamado +0|whaticket-backend | [22:40:55.774] INFO: Socket Ecommerce Connection Update connecting +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:40:56.307] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:40:56.307] INFO: Socket ID: +0|whaticket-backend | [22:40:56.307] INFO: Handshake query: +0|whaticket-backend | [22:40:56.308] INFO: Token param: +0|whaticket-backend | [22:40:56.308] ERROR: Middleware authentication FAILED: +0|whaticket-backend | [22:40:56.596] INFO: Socket Ecommerce Connection Update +0|whaticket-backend | [22:40:56.762] INFO: Socket Ecommerce Connection Update +0|whaticket-backend | [22:40:56.763] INFO: Socket Ecommerce Connection Update open +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:40:57.439] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:40:57.439] INFO: Socket ID: +0|whaticket-backend | [22:40:57.439] INFO: Handshake query: +0|whaticket-backend | [22:40:57.440] INFO: Token param: +0|whaticket-backend | [22:40:57.440] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:40:57.443] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:40:57.443] INFO: Socket ID: +0|whaticket-backend | [22:40:57.443] INFO: Handshake query: +0|whaticket-backend | [22:40:57.444] INFO: Token param: +0|whaticket-backend | [22:40:57.444] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:40:57.448] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:40:57.448] INFO: Socket ID: +0|whaticket-backend | [22:40:57.448] INFO: Handshake query: +0|whaticket-backend | [22:40:57.449] INFO: Token param: +0|whaticket-backend | [22:40:57.449] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:40:57.625] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:40:57.625] INFO: Socket ID: +0|whaticket-backend | [22:40:57.625] INFO: Handshake query: +0|whaticket-backend | [22:40:57.626] INFO: Token param: +0|whaticket-backend | [22:40:57.626] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:40:57.628] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:40:57.628] INFO: Socket ID: +0|whaticket-backend | [22:40:57.628] INFO: Handshake query: +0|whaticket-backend | [22:40:57.628] INFO: Token param: +0|whaticket-backend | [22:40:57.628] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:40:58.297] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:40:58.298] INFO: Socket ID: +0|whaticket-backend | [22:40:58.298] INFO: Handshake query: +0|whaticket-backend | [22:40:58.298] INFO: Token param: +0|whaticket-backend | [22:40:58.298] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:40:58.301] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:40:58.301] INFO: Socket ID: +0|whaticket-backend | [22:40:58.301] INFO: Handshake query: +0|whaticket-backend | [22:40:58.301] INFO: Token param: +0|whaticket-backend | [22:40:58.301] ERROR: Middleware authentication FAILED: +0|whaticket-backend | [22:41:00.179] INFO: Iniciando processamento de filas +0|whaticket-backend | [22:41:25.186] INFO: Total de conexao Prontas para envio!: 1 +0|whaticket-backend | [22:41:25.189] INFO: Configuração de envio não encontrada da conexão +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:41:42.490] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:41:42.490] INFO: Socket ID: +0|whaticket-backend | [22:41:42.490] INFO: Handshake query: +0|whaticket-backend | [22:41:42.490] INFO: Token param: +0|whaticket-backend | [22:41:42.491] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:41:43.288] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:41:43.288] INFO: Socket ID: +0|whaticket-backend | [22:41:43.288] INFO: Handshake query: +0|whaticket-backend | [22:41:43.288] INFO: Token param: +0|whaticket-backend | [22:41:43.288] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:41:43.300] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:41:43.301] INFO: Socket ID: +0|whaticket-backend | [22:41:43.301] INFO: Handshake query: +0|whaticket-backend | [22:41:43.301] INFO: Token param: +0|whaticket-backend | [22:41:43.301] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:41:43.304] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:41:43.304] INFO: Socket ID: +0|whaticket-backend | [22:41:43.304] INFO: Handshake query: +0|whaticket-backend | [22:41:43.304] INFO: Token param: +0|whaticket-backend | [22:41:43.305] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:41:44.290] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:41:44.290] INFO: Socket ID: +0|whaticket-backend | [22:41:44.290] INFO: Handshake query: +0|whaticket-backend | [22:41:44.290] INFO: Token param: +0|whaticket-backend | [22:41:44.291] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:41:44.294] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:41:44.294] INFO: Socket ID: +0|whaticket-backend | [22:41:44.294] INFO: Handshake query: +0|whaticket-backend | [22:41:44.294] INFO: Token param: +0|whaticket-backend | [22:41:44.295] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:41:44.322] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:41:44.323] INFO: Socket ID: +0|whaticket-backend | [22:41:44.323] INFO: Handshake query: +0|whaticket-backend | [22:41:44.323] INFO: Token param: +0|whaticket-backend | [22:41:44.323] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:41:45.681] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:41:45.681] INFO: Socket ID: +0|whaticket-backend | [22:41:45.681] INFO: Handshake query: +0|whaticket-backend | [22:41:45.681] INFO: Token param: +0|whaticket-backend | [22:41:45.682] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:41:45.683] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:41:45.683] INFO: Socket ID: +0|whaticket-backend | [22:41:45.683] INFO: Handshake query: +0|whaticket-backend | [22:41:45.684] INFO: Token param: +0|whaticket-backend | [22:41:45.684] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:42:29.300] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:42:29.300] INFO: Socket ID: +0|whaticket-backend | [22:42:29.300] INFO: Handshake query: +0|whaticket-backend | [22:42:29.300] INFO: Token param: +0|whaticket-backend | [22:42:29.301] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:42:30.337] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:42:30.337] INFO: Socket ID: +0|whaticket-backend | [22:42:30.338] INFO: Handshake query: +0|whaticket-backend | [22:42:30.338] INFO: Token param: +0|whaticket-backend | [22:42:30.338] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:42:30.339] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:42:30.339] INFO: Socket ID: +0|whaticket-backend | [22:42:30.340] INFO: Handshake query: +0|whaticket-backend | [22:42:30.340] INFO: Token param: +0|whaticket-backend | [22:42:30.340] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:42:30.342] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:42:30.342] INFO: Socket ID: +0|whaticket-backend | [22:42:30.342] INFO: Handshake query: +0|whaticket-backend | [22:42:30.342] INFO: Token param: +0|whaticket-backend | [22:42:30.342] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:42:30.383] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:42:30.383] INFO: Socket ID: +0|whaticket-backend | [22:42:30.383] INFO: Handshake query: +0|whaticket-backend | [22:42:30.384] INFO: Token param: +0|whaticket-backend | [22:42:30.384] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:42:30.386] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:42:30.387] INFO: Socket ID: +0|whaticket-backend | [22:42:30.387] INFO: Handshake query: +0|whaticket-backend | [22:42:30.387] INFO: Token param: +0|whaticket-backend | [22:42:30.387] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:42:31.325] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:42:31.326] INFO: Socket ID: +0|whaticket-backend | [22:42:31.326] INFO: Handshake query: +0|whaticket-backend | [22:42:31.326] INFO: Token param: +0|whaticket-backend | [22:42:31.326] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:42:32.289] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:42:32.289] INFO: Socket ID: +0|whaticket-backend | [22:42:32.289] INFO: Handshake query: +0|whaticket-backend | [22:42:32.289] INFO: Token param: +0|whaticket-backend | [22:42:32.290] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:42:32.292] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:42:32.292] INFO: Socket ID: +0|whaticket-backend | [22:42:32.292] INFO: Handshake query: +0|whaticket-backend | [22:42:32.293] INFO: Token param: +0|whaticket-backend | [22:42:32.293] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:15.469] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:15.470] INFO: Socket ID: +0|whaticket-backend | [22:43:15.470] INFO: Handshake query: +0|whaticket-backend | [22:43:15.471] INFO: Token param: +0|whaticket-backend | [22:43:15.471] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:16.171] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:16.171] INFO: Socket ID: +0|whaticket-backend | [22:43:16.171] INFO: Handshake query: +0|whaticket-backend | [22:43:16.171] INFO: Token param: +0|whaticket-backend | [22:43:16.172] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:16.468] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:16.469] INFO: Socket ID: +0|whaticket-backend | [22:43:16.469] INFO: Handshake query: +0|whaticket-backend | [22:43:16.469] INFO: Token param: +0|whaticket-backend | [22:43:16.469] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:16.537] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:16.537] INFO: Socket ID: +0|whaticket-backend | [22:43:16.538] INFO: Handshake query: +0|whaticket-backend | [22:43:16.538] INFO: Token param: +0|whaticket-backend | [22:43:16.538] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:16.858] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:16.858] INFO: Socket ID: +0|whaticket-backend | [22:43:16.858] INFO: Handshake query: +0|whaticket-backend | [22:43:16.858] INFO: Token param: +0|whaticket-backend | [22:43:16.859] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:16.878] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:16.878] INFO: Socket ID: +0|whaticket-backend | [22:43:16.878] INFO: Handshake query: +0|whaticket-backend | [22:43:16.878] INFO: Token param: +0|whaticket-backend | [22:43:16.879] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:17.840] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:17.841] INFO: Socket ID: +0|whaticket-backend | [22:43:17.841] INFO: Handshake query: +0|whaticket-backend | [22:43:17.841] INFO: Token param: +0|whaticket-backend | [22:43:17.841] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:18.144] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:18.145] INFO: Socket ID: +0|whaticket-backend | [22:43:18.145] INFO: Handshake query: +0|whaticket-backend | [22:43:18.145] INFO: Token param: +0|whaticket-backend | [22:43:18.145] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:18.955] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:18.955] INFO: Socket ID: +0|whaticket-backend | [22:43:18.955] INFO: Handshake query: +0|whaticket-backend | [22:43:18.955] INFO: Token param: +0|whaticket-backend | [22:43:18.955] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:54.087] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:54.087] INFO: Socket ID: +0|whaticket-backend | [22:43:54.087] INFO: Handshake query: +0|whaticket-backend | [22:43:54.087] INFO: Token param: +0|whaticket-backend | [22:43:54.087] ERROR: Middleware authentication FAILED: +0|whaticket-backend | [22:43:54.799] WARN: +0|whaticket-backend | message: "Invalid token. We'll try to assign a new one on next request" +0|whaticket-backend | statusCode: 403 +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:56.101] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:56.102] INFO: Socket ID: +0|whaticket-backend | [22:43:56.102] INFO: Handshake query: +0|whaticket-backend | [22:43:56.103] INFO: Token param: +0|whaticket-backend | [22:43:56.104] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:43:56.104] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:43:56.105] INFO: Socket ID: +0|whaticket-backend | [22:43:56.105] INFO: Attached user ID: +0|whaticket-backend | [22:43:56.113] INFO: Client Connected - User: +0|whaticket-backend | [22:43:57.213] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:57.266] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:57.266] INFO: Socket ID: +0|whaticket-backend | [22:43:57.266] INFO: Handshake query: +0|whaticket-backend | [22:43:57.266] INFO: Token param: +0|whaticket-backend | [22:43:57.267] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:43:57.267] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:43:57.268] INFO: Socket ID: +0|whaticket-backend | [22:43:57.268] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:43:57.274] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:43:57.274] INFO: Socket ID: +0|whaticket-backend | [22:43:57.274] INFO: Handshake query: +0|whaticket-backend | [22:43:57.274] INFO: Token param: +0|whaticket-backend | [22:43:57.275] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:43:57.275] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:43:57.275] INFO: Socket ID: +0|whaticket-backend | [22:43:57.275] INFO: Attached user ID: +0|whaticket-backend | [22:43:57.277] INFO: Client Connected - User: +0|whaticket-backend | [22:43:57.280] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:00.073] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:00.074] INFO: Socket ID: +0|whaticket-backend | [22:44:00.074] INFO: Handshake query: +0|whaticket-backend | [22:44:00.075] INFO: Token param: +0|whaticket-backend | [22:44:00.075] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:00.076] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:00.076] INFO: Socket ID: +0|whaticket-backend | [22:44:00.077] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:00.091] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:00.091] INFO: Socket ID: +0|whaticket-backend | [22:44:00.091] INFO: Handshake query: +0|whaticket-backend | [22:44:00.092] INFO: Token param: +0|whaticket-backend | [22:44:00.092] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:00.093] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:00.093] INFO: Socket ID: +0|whaticket-backend | [22:44:00.094] INFO: Attached user ID: +0|whaticket-backend | [22:44:00.097] INFO: Client Connected - User: +0|whaticket-backend | [22:44:00.100] INFO: Client Connected - User: +0|whaticket-backend | [22:44:00.515] INFO: Client disconnected: +0|whaticket-backend | [22:44:00.515] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:00.565] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:00.567] INFO: Socket ID: +0|whaticket-backend | [22:44:00.567] INFO: Handshake query: +0|whaticket-backend | [22:44:00.568] INFO: Token param: +0|whaticket-backend | [22:44:00.568] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:00.569] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:00.569] INFO: Socket ID: +0|whaticket-backend | [22:44:00.569] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:00.574] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:00.574] INFO: Socket ID: +0|whaticket-backend | [22:44:00.574] INFO: Handshake query: +0|whaticket-backend | [22:44:00.574] INFO: Token param: +0|whaticket-backend | [22:44:00.575] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:00.575] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:00.575] INFO: Socket ID: +0|whaticket-backend | [22:44:00.575] INFO: Attached user ID: +0|whaticket-backend | [22:44:00.583] INFO: Client Connected - User: +0|whaticket-backend | [22:44:00.587] INFO: Client Connected - User: +0|whaticket-backend | [22:44:00.920] INFO: Client disconnected: +0|whaticket-backend | [22:44:00.967] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:00.995] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:00.996] INFO: Socket ID: +0|whaticket-backend | [22:44:00.996] INFO: Handshake query: +0|whaticket-backend | [22:44:00.996] INFO: Token param: +0|whaticket-backend | [22:44:00.996] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:00.997] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:00.997] INFO: Socket ID: +0|whaticket-backend | [22:44:00.997] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:01.001] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:01.001] INFO: Socket ID: +0|whaticket-backend | [22:44:01.001] INFO: Handshake query: +0|whaticket-backend | [22:44:01.001] INFO: Token param: +0|whaticket-backend | [22:44:01.001] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:01.002] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:01.002] INFO: Socket ID: +0|whaticket-backend | [22:44:01.002] INFO: Attached user ID: +0|whaticket-backend | [22:44:01.009] INFO: Client Connected - User: +0|whaticket-backend | [22:44:01.012] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:01.437] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:01.437] INFO: Socket ID: +0|whaticket-backend | [22:44:01.438] INFO: Handshake query: +0|whaticket-backend | [22:44:01.438] INFO: Token param: +0|whaticket-backend | [22:44:01.438] ERROR: Middleware authentication FAILED: +0|whaticket-backend | [22:44:01.665] INFO: Ticket 73 messages read +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:01.773] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:01.773] INFO: Socket ID: +0|whaticket-backend | [22:44:01.773] INFO: Handshake query: +0|whaticket-backend | [22:44:01.773] INFO: Token param: +0|whaticket-backend | [22:44:01.774] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:01.775] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:01.775] INFO: Socket ID: +0|whaticket-backend | [22:44:01.775] INFO: Attached user ID: +0|whaticket-backend | [22:44:01.864] INFO: Client Connected - User: +0|whaticket-backend | [22:44:02.674] INFO: Client disconnected: +0|whaticket-backend | [22:44:02.674] INFO: Client disconnected: +0|whaticket-backend | [22:44:02.675] INFO: Client disconnected: +0|whaticket-backend | [22:44:02.676] INFO: Client disconnected: +0|whaticket-backend | [22:44:02.676] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:02.931] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:02.931] INFO: Socket ID: +0|whaticket-backend | [22:44:02.931] INFO: Handshake query: +0|whaticket-backend | [22:44:02.931] INFO: Token param: +0|whaticket-backend | [22:44:02.932] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:02.978] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:02.979] INFO: Socket ID: +0|whaticket-backend | [22:44:02.979] INFO: Handshake query: +0|whaticket-backend | [22:44:02.980] INFO: Token param: +0|whaticket-backend | [22:44:02.980] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:03.434] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:03.434] INFO: Socket ID: +0|whaticket-backend | [22:44:03.434] INFO: Handshake query: +0|whaticket-backend | [22:44:03.434] INFO: Token param: +0|whaticket-backend | [22:44:03.435] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:03.436] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:03.436] INFO: Socket ID: +0|whaticket-backend | [22:44:03.436] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:03.440] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:03.440] INFO: Socket ID: +0|whaticket-backend | [22:44:03.440] INFO: Handshake query: +0|whaticket-backend | [22:44:03.440] INFO: Token param: +0|whaticket-backend | [22:44:03.440] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:03.441] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:03.441] INFO: Socket ID: +0|whaticket-backend | [22:44:03.441] INFO: Attached user ID: +0|whaticket-backend | [22:44:03.447] INFO: Client Connected - User: +0|whaticket-backend | [22:44:03.450] INFO: Client Connected - User: +0|whaticket-backend | [22:44:03.792] INFO: Client disconnected: +0|whaticket-backend | [22:44:03.813] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:03.846] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:03.847] INFO: Socket ID: +0|whaticket-backend | [22:44:03.847] INFO: Handshake query: +0|whaticket-backend | [22:44:03.847] INFO: Token param: +0|whaticket-backend | [22:44:03.847] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:03.848] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:03.848] INFO: Socket ID: +0|whaticket-backend | [22:44:03.848] INFO: Attached user ID: +0|whaticket-backend | [22:44:03.860] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:04.699] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:04.699] INFO: Socket ID: +0|whaticket-backend | [22:44:04.699] INFO: Handshake query: +0|whaticket-backend | [22:44:04.699] INFO: Token param: +0|whaticket-backend | [22:44:04.700] ERROR: Middleware authentication FAILED: +0|whaticket-backend | [22:44:46.482] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:46.539] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:46.539] INFO: Socket ID: +0|whaticket-backend | [22:44:46.539] INFO: Handshake query: +0|whaticket-backend | [22:44:46.539] INFO: Token param: +0|whaticket-backend | [22:44:46.539] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:46.540] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:46.540] INFO: Socket ID: +0|whaticket-backend | [22:44:46.540] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:46.544] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:46.545] INFO: Socket ID: +0|whaticket-backend | [22:44:46.545] INFO: Handshake query: +0|whaticket-backend | [22:44:46.546] INFO: Token param: +0|whaticket-backend | [22:44:46.546] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:44:46.547] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:44:46.547] INFO: Socket ID: +0|whaticket-backend | [22:44:46.547] INFO: Attached user ID: +0|whaticket-backend | [22:44:46.562] INFO: Client Connected - User: +0|whaticket-backend | [22:44:46.564] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:47.596] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:47.597] INFO: Socket ID: +0|whaticket-backend | [22:44:47.597] INFO: Handshake query: +0|whaticket-backend | [22:44:47.597] INFO: Token param: +0|whaticket-backend | [22:44:47.597] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:48.654] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:48.654] INFO: Socket ID: +0|whaticket-backend | [22:44:48.654] INFO: Handshake query: +0|whaticket-backend | [22:44:48.654] INFO: Token param: +0|whaticket-backend | [22:44:48.654] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:48.882] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:48.882] INFO: Socket ID: +0|whaticket-backend | [22:44:48.883] INFO: Handshake query: +0|whaticket-backend | [22:44:48.883] INFO: Token param: +0|whaticket-backend | [22:44:48.883] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:44:50.476] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:44:50.476] INFO: Socket ID: +0|whaticket-backend | [22:44:50.477] INFO: Handshake query: +0|whaticket-backend | [22:44:50.477] INFO: Token param: +0|whaticket-backend | [22:44:50.477] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 📨 ========== NUEVO MENSAJE ========== +0|whaticket-backend | 🆔 ID: AC48572D46... +0|whaticket-backend | 📞 RemoteJid: 124777406664808@lid +0|whaticket-backend | 👤 FromMe: NO +0|whaticket-backend | 👥 Participant: N/A +0|whaticket-backend | 📛 PushName: Agustín Navarro +0|whaticket-backend | ⏰ Timestamp: 10:45:27 PM +0|whaticket-backend | 🔍 ANALIZANDO JID: 124777406664808@lid +0|whaticket-backend | Tipo: LID +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "124777406664808@lid" +0|whaticket-backend | SessionId: "11" +0|whaticket-backend | Clean input: "124777406664808" (15 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-124777406664808_reverse +0|whaticket-backend | ✅ Encontrado 1 mapping(s) exacto(s) +0|whaticket-backend | whatsappId: 11 +0|whaticket-backend | value: "5492478577503" +0|whaticket-backend | 🎯 ÉXITO: 124777406664808 -> 5492478577503 +0|whaticket-backend | 🎯 [BAILEYS_SESSIONS] CONVERSIÓN EXITOSA: +0|whaticket-backend | Input original: 124777406664808@lid +0|whaticket-backend | Número real: 5492478577503 +0|whaticket-backend | JID real: 5492478577503@s.whatsapp.net +0|whaticket-backend | Es LID? true +0|whaticket-backend | ✅ FIN obtenerNumeroRealDeMensaje - BAILEYS_SESSIONS +0|whaticket-backend | 📞 === ANTES DE verifyContact === +0|whaticket-backend | msgContact recibido: +0|whaticket-backend | id: 5492478577503@s.whatsapp.net +0|whaticket-backend | name: Agustín Navarro +0|whaticket-backend | number: 5492478577503 +0|whaticket-backend | esLid: SÍ +0|whaticket-backend | lid: 124777406664808@lid +0|whaticket-backend | originalJid: 124777406664808@lid +0|whaticket-backend | =============================== +0|whaticket-backend | 🔍 === verifyContact INICIO === +0|whaticket-backend | 📥 DATOS DE ENTRADA: +0|whaticket-backend | ID: 5492478577503@s.whatsapp.net +0|whaticket-backend | Nombre: Agustín Navarro +0|whaticket-backend | Número recibido: 5492478577503 +0|whaticket-backend | Es LID?: SÍ +0|whaticket-backend | LID: 124777406664808@lid +0|whaticket-backend | JID original: 124777406664808@lid +0|whaticket-backend | 🖼️ Foto de perfil obtenida +0|whaticket-backend | 👤 VERIFICANDO CONTACTO: +0|whaticket-backend | ID recibido: 5492478577503@s.whatsapp.net +0|whaticket-backend | Número en msgContact: 5492478577503 +0|whaticket-backend | Es LID?: SÍ +0|whaticket-backend | LID original: 124777406664808@lid +0|whaticket-backend | JID original: 124777406664808@lid +0|whaticket-backend | 🔍 Contacto tiene LID, verificando número real: 124777406664808@lid +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "124777406664808@lid" +0|whaticket-backend | SessionId: "11" +0|whaticket-backend | Clean input: "124777406664808" (15 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-124777406664808_reverse +0|whaticket-backend | ✅ Encontrado 1 mapping(s) exacto(s) +0|whaticket-backend | whatsappId: 11 +0|whaticket-backend | value: "5492478577503" +0|whaticket-backend | 🎯 ÉXITO: 124777406664808 -> 5492478577503 +0|whaticket-backend | ✅ [BAILEYS_SESSIONS] LID convertido a número en verifyContact: 5492478577503 +0|whaticket-backend | 🔧 DATOS DEL WBOT: +0|whaticket-backend | wbot.id: 11 +0|whaticket-backend | wbot.user?.id: 5492478477558:11@s.whatsapp.net +0|whaticket-backend | ⚠️ No se pudo obtener companyId del WhatsApp, usando valor por defecto: 1 +0|whaticket-backend | 📝 DATOS FINALES DEL CONTACTO: +0|whaticket-backend | Nombre: Agustín Navarro +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | LID guardado: 124777406664808@lid +0|whaticket-backend | Company ID: 1 +0|whaticket-backend | WhatsApp ID: 11 +0|whaticket-backend | originaljid: 124777406664808@lid +0|whaticket-backend | 🔎 BUSCANDO CONTACTO EXISTENTE... +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | Company ID buscado: 1 +0|whaticket-backend | ⚠️ No se encontró contacto con companyId: 1 +0|whaticket-backend | Contactos encontrados con mismo número: 1 +0|whaticket-backend | 📊 CONTACTOS EXISTENTES CON ESTE NÚMERO: +0|whaticket-backend | 1. ID: 18632, CompanyId: 2, WhatsAppId: 10, LID: NULL +0|whaticket-backend | ✅ Usando contacto existente más antiguo: ID 18632 +0|whaticket-backend | 🔄 Actualizando companyId de 2 a 1 +0|whaticket-backend | ✅ Contacto final a usar: 18632 +0|whaticket-backend | Nombre actual: Agustín Navarro +0|whaticket-backend | LID actual: NULL +0|whaticket-backend | CompanyId actual: 2 +0|whaticket-backend | Actualizando LID a: 124777406664808@lid +0|whaticket-backend | Actualizando originaljid a: 124777406664808@lid +0|whaticket-backend | ✅ Contacto actualizado exitosamente +0|whaticket-backend | 📊 DIAGNÓSTICO COMPLETO DEL MENSAJE: +0|whaticket-backend | ====================================== +0|whaticket-backend | 📱 DATOS CRUDOS: +0|whaticket-backend | RemoteJid: 124777406664808@lid +0|whaticket-backend | Participant: +0|whaticket-backend | FromMe: false +0|whaticket-backend | PushName: Agustín Navarro +0|whaticket-backend | Tipo de mensaje: conversation +0|whaticket-backend | 🔍 ANÁLISIS LID: +0|whaticket-backend | JID analizado: 124777406664808@lid +0|whaticket-backend | 🎫 === ANTES DE FindOrCreateTicketService === +0|whaticket-backend | CONTACTO OBTENIDO: +0|whaticket-backend | ID: 18632 +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | Nombre: Agustín Navarro +0|whaticket-backend | LID: 124777406664808@lid +0|whaticket-backend | WhatsApp ID: 11 +0|whaticket-backend | Company ID: 1 +0|whaticket-backend | =============================== +0|whaticket-backend | [22:45:29.565] INFO: Ticket 74 messages read +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:45:33.365] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:45:33.366] INFO: Socket ID: +0|whaticket-backend | [22:45:33.366] INFO: Handshake query: +0|whaticket-backend | [22:45:33.367] INFO: Token param: +0|whaticket-backend | [22:45:33.368] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:45:34.302] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:45:34.303] INFO: Socket ID: +0|whaticket-backend | [22:45:34.306] INFO: Handshake query: +0|whaticket-backend | [22:45:34.307] INFO: Token param: +0|whaticket-backend | [22:45:34.308] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:45:35.370] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:45:35.370] INFO: Socket ID: +0|whaticket-backend | [22:45:35.371] INFO: Handshake query: +0|whaticket-backend | [22:45:35.372] INFO: Token param: +0|whaticket-backend | [22:45:35.372] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:45:37.315] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:45:37.315] INFO: Socket ID: +0|whaticket-backend | [22:45:37.316] INFO: Handshake query: +0|whaticket-backend | [22:45:37.316] INFO: Token param: +0|whaticket-backend | [22:45:37.317] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:46:19.289] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:46:19.290] INFO: Socket ID: +0|whaticket-backend | [22:46:19.290] INFO: Handshake query: +0|whaticket-backend | [22:46:19.290] INFO: Token param: +0|whaticket-backend | [22:46:19.290] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:46:20.585] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:46:20.585] INFO: Socket ID: +0|whaticket-backend | [22:46:20.585] INFO: Handshake query: +0|whaticket-backend | [22:46:20.585] INFO: Token param: +0|whaticket-backend | [22:46:20.585] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:46:22.287] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:46:22.288] INFO: Socket ID: +0|whaticket-backend | [22:46:22.288] INFO: Handshake query: +0|whaticket-backend | [22:46:22.288] INFO: Token param: +0|whaticket-backend | [22:46:22.288] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:46:24.279] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:46:24.280] INFO: Socket ID: +0|whaticket-backend | [22:46:24.280] INFO: Handshake query: +0|whaticket-backend | [22:46:24.280] INFO: Token param: +0|whaticket-backend | [22:46:24.280] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:06.369] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:06.369] INFO: Socket ID: +0|whaticket-backend | [22:47:06.370] INFO: Handshake query: +0|whaticket-backend | [22:47:06.370] INFO: Token param: +0|whaticket-backend | [22:47:06.370] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:07.342] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:07.342] INFO: Socket ID: +0|whaticket-backend | [22:47:07.342] INFO: Handshake query: +0|whaticket-backend | [22:47:07.342] INFO: Token param: +0|whaticket-backend | [22:47:07.343] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:09.290] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:09.290] INFO: Socket ID: +0|whaticket-backend | [22:47:09.290] INFO: Handshake query: +0|whaticket-backend | [22:47:09.290] INFO: Token param: +0|whaticket-backend | [22:47:09.290] ERROR: Middleware authentication FAILED: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:11.298] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:11.299] INFO: Socket ID: +0|whaticket-backend | [22:47:11.299] INFO: Handshake query: +0|whaticket-backend | [22:47:11.299] INFO: Token param: +0|whaticket-backend | [22:47:11.300] ERROR: Middleware authentication FAILED: +0|whaticket-backend | [22:47:18.666] WARN: +0|whaticket-backend | message: "ERR_NO_TICKET_FOUND" +0|whaticket-backend | statusCode: 404 +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:18.890] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:18.891] INFO: Socket ID: +0|whaticket-backend | [22:47:18.891] INFO: Handshake query: +0|whaticket-backend | [22:47:18.891] INFO: Token param: +0|whaticket-backend | [22:47:18.892] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:18.892] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:18.892] INFO: Socket ID: +0|whaticket-backend | [22:47:18.892] INFO: Attached user ID: +0|whaticket-backend | [22:47:18.897] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:19.161] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:19.161] INFO: Socket ID: +0|whaticket-backend | [22:47:19.161] INFO: Handshake query: +0|whaticket-backend | [22:47:19.161] INFO: Token param: +0|whaticket-backend | [22:47:19.161] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:19.162] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:19.162] INFO: Socket ID: +0|whaticket-backend | [22:47:19.162] INFO: Attached user ID: +0|whaticket-backend | [22:47:19.170] INFO: Client Connected - User: +0|whaticket-backend | [22:47:19.228] INFO: Invalid attempt to join ticket 74 by user 29 +0|whaticket-backend | [22:47:19.267] WARN: +0|whaticket-backend | message: "ERR_NO_TICKET_FOUND" +0|whaticket-backend | statusCode: 404 +0|whaticket-backend | [22:47:19.271] INFO: Invalid attempt to join ticket 74 by user 29 +0|whaticket-backend | [22:47:19.508] WARN: +0|whaticket-backend | message: "ERR_NO_TICKET_FOUND" +0|whaticket-backend | statusCode: 404 +0|whaticket-backend | [22:47:27.425] INFO: Client disconnected: +0|whaticket-backend | [22:47:27.426] INFO: Client disconnected: +0|whaticket-backend | [22:47:27.426] INFO: Client disconnected: +0|whaticket-backend | [22:47:27.427] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:27.864] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:27.864] INFO: Socket ID: +0|whaticket-backend | [22:47:27.864] INFO: Handshake query: +0|whaticket-backend | [22:47:27.864] INFO: Token param: +0|whaticket-backend | [22:47:27.864] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:27.865] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:27.865] INFO: Socket ID: +0|whaticket-backend | [22:47:27.865] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:27.875] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:27.875] INFO: Socket ID: +0|whaticket-backend | [22:47:27.875] INFO: Handshake query: +0|whaticket-backend | [22:47:27.875] INFO: Token param: +0|whaticket-backend | [22:47:27.875] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:27.876] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:27.876] INFO: Socket ID: +0|whaticket-backend | [22:47:27.877] INFO: Attached user ID: +0|whaticket-backend | [22:47:27.883] INFO: Client Connected - User: +0|whaticket-backend | [22:47:27.962] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:28.106] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:28.106] INFO: Socket ID: +0|whaticket-backend | [22:47:28.106] INFO: Handshake query: +0|whaticket-backend | [22:47:28.106] INFO: Token param: +0|whaticket-backend | [22:47:28.107] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:28.107] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:28.107] INFO: Socket ID: +0|whaticket-backend | [22:47:28.108] INFO: Attached user ID: +0|whaticket-backend | [22:47:28.114] INFO: Client disconnected: +0|whaticket-backend | [22:47:28.117] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:28.690] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:28.690] INFO: Socket ID: +0|whaticket-backend | [22:47:28.691] INFO: Handshake query: +0|whaticket-backend | [22:47:28.691] INFO: Token param: +0|whaticket-backend | [22:47:28.691] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:28.691] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:28.691] INFO: Socket ID: +0|whaticket-backend | [22:47:28.691] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:28.694] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:28.694] INFO: Socket ID: +0|whaticket-backend | [22:47:28.694] INFO: Handshake query: +0|whaticket-backend | [22:47:28.694] INFO: Token param: +0|whaticket-backend | [22:47:28.694] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:28.695] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:28.695] INFO: Socket ID: +0|whaticket-backend | [22:47:28.695] INFO: Attached user ID: +0|whaticket-backend | [22:47:28.704] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:28.706] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:28.706] INFO: Socket ID: +0|whaticket-backend | [22:47:28.706] INFO: Handshake query: +0|whaticket-backend | [22:47:28.706] INFO: Token param: +0|whaticket-backend | [22:47:28.706] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:28.707] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:28.707] INFO: Socket ID: +0|whaticket-backend | [22:47:28.707] INFO: Attached user ID: +0|whaticket-backend | [22:47:28.710] INFO: Client Connected - User: +0|whaticket-backend | [22:47:28.763] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:28.893] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:28.893] INFO: Socket ID: +0|whaticket-backend | [22:47:28.894] INFO: Handshake query: +0|whaticket-backend | [22:47:28.894] INFO: Token param: +0|whaticket-backend | [22:47:28.894] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:28.895] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:28.895] INFO: Socket ID: +0|whaticket-backend | [22:47:28.895] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:28.900] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:28.900] INFO: Socket ID: +0|whaticket-backend | [22:47:28.900] INFO: Handshake query: +0|whaticket-backend | [22:47:28.900] INFO: Token param: +0|whaticket-backend | [22:47:28.900] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:28.900] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:28.900] INFO: Socket ID: +0|whaticket-backend | [22:47:28.901] INFO: Attached user ID: +0|whaticket-backend | [22:47:28.904] INFO: Client Connected - User: +0|whaticket-backend | [22:47:28.906] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:47:29.050] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:47:29.050] INFO: Socket ID: +0|whaticket-backend | [22:47:29.050] INFO: Handshake query: +0|whaticket-backend | [22:47:29.050] INFO: Token param: +0|whaticket-backend | [22:47:29.050] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:47:29.051] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:47:29.051] INFO: Socket ID: +0|whaticket-backend | [22:47:29.051] INFO: Attached user ID: +0|whaticket-backend | [22:47:29.060] INFO: Client Connected - User: +0|whaticket-backend | [22:47:29.180] INFO: Invalid attempt to join ticket 74 by user 29 +0|whaticket-backend | [22:47:29.265] WARN: +0|whaticket-backend | message: "ERR_NO_TICKET_FOUND" +0|whaticket-backend | statusCode: 404 +0|whaticket-backend | [22:47:29.322] INFO: Invalid attempt to join ticket 74 by user 29 +0|whaticket-backend | [22:47:29.326] WARN: +0|whaticket-backend | message: "ERR_NO_TICKET_FOUND" +0|whaticket-backend | statusCode: 404 +0|whaticket-backend | 📨 ========== NUEVO MENSAJE ========== +0|whaticket-backend | 🆔 ID: ACE69ABDC2... +0|whaticket-backend | 📞 RemoteJid: 124777406664808@lid +0|whaticket-backend | 👤 FromMe: NO +0|whaticket-backend | 👥 Participant: N/A +0|whaticket-backend | 📛 PushName: Agustín Navarro +0|whaticket-backend | ⏰ Timestamp: 10:48:22 PM +0|whaticket-backend | 🔍 ANALIZANDO JID: 124777406664808@lid +0|whaticket-backend | Tipo: LID +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "124777406664808@lid" +0|whaticket-backend | SessionId: "11" +0|whaticket-backend | Clean input: "124777406664808" (15 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-124777406664808_reverse +0|whaticket-backend | ✅ Encontrado 1 mapping(s) exacto(s) +0|whaticket-backend | whatsappId: 11 +0|whaticket-backend | value: "5492478577503" +0|whaticket-backend | 🎯 ÉXITO: 124777406664808 -> 5492478577503 +0|whaticket-backend | 🎯 [BAILEYS_SESSIONS] CONVERSIÓN EXITOSA: +0|whaticket-backend | Input original: 124777406664808@lid +0|whaticket-backend | Número real: 5492478577503 +0|whaticket-backend | JID real: 5492478577503@s.whatsapp.net +0|whaticket-backend | Es LID? true +0|whaticket-backend | ✅ FIN obtenerNumeroRealDeMensaje - BAILEYS_SESSIONS +0|whaticket-backend | 📞 === ANTES DE verifyContact === +0|whaticket-backend | msgContact recibido: +0|whaticket-backend | id: 5492478577503@s.whatsapp.net +0|whaticket-backend | name: Agustín Navarro +0|whaticket-backend | number: 5492478577503 +0|whaticket-backend | esLid: SÍ +0|whaticket-backend | lid: 124777406664808@lid +0|whaticket-backend | originalJid: 124777406664808@lid +0|whaticket-backend | =============================== +0|whaticket-backend | 🔍 === verifyContact INICIO === +0|whaticket-backend | 📥 DATOS DE ENTRADA: +0|whaticket-backend | ID: 5492478577503@s.whatsapp.net +0|whaticket-backend | Nombre: Agustín Navarro +0|whaticket-backend | Número recibido: 5492478577503 +0|whaticket-backend | Es LID?: SÍ +0|whaticket-backend | LID: 124777406664808@lid +0|whaticket-backend | JID original: 124777406664808@lid +0|whaticket-backend | 🖼️ Foto de perfil obtenida +0|whaticket-backend | 👤 VERIFICANDO CONTACTO: +0|whaticket-backend | ID recibido: 5492478577503@s.whatsapp.net +0|whaticket-backend | Número en msgContact: 5492478577503 +0|whaticket-backend | Es LID?: SÍ +0|whaticket-backend | LID original: 124777406664808@lid +0|whaticket-backend | JID original: 124777406664808@lid +0|whaticket-backend | 🔍 Contacto tiene LID, verificando número real: 124777406664808@lid +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "124777406664808@lid" +0|whaticket-backend | SessionId: "11" +0|whaticket-backend | Clean input: "124777406664808" (15 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-124777406664808_reverse +0|whaticket-backend | ✅ Encontrado 1 mapping(s) exacto(s) +0|whaticket-backend | whatsappId: 11 +0|whaticket-backend | value: "5492478577503" +0|whaticket-backend | 🎯 ÉXITO: 124777406664808 -> 5492478577503 +0|whaticket-backend | ✅ [BAILEYS_SESSIONS] LID convertido a número en verifyContact: 5492478577503 +0|whaticket-backend | 🔧 DATOS DEL WBOT: +0|whaticket-backend | wbot.id: 11 +0|whaticket-backend | wbot.user?.id: 5492478477558:11@s.whatsapp.net +0|whaticket-backend | ⚠️ No se pudo obtener companyId del WhatsApp, usando valor por defecto: 1 +0|whaticket-backend | 📝 DATOS FINALES DEL CONTACTO: +0|whaticket-backend | Nombre: Agustín Navarro +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | LID guardado: 124777406664808@lid +0|whaticket-backend | Company ID: 1 +0|whaticket-backend | WhatsApp ID: 11 +0|whaticket-backend | originaljid: 124777406664808@lid +0|whaticket-backend | 🔎 BUSCANDO CONTACTO EXISTENTE... +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | Company ID buscado: 1 +0|whaticket-backend | ✅ Contacto encontrado con número y companyId: 18632 +0|whaticket-backend | Contact Company ID: 1 +0|whaticket-backend | ✅ Contacto final a usar: 18632 +0|whaticket-backend | Nombre actual: Agustín Navarro +0|whaticket-backend | LID actual: 124777406664808@lid +0|whaticket-backend | CompanyId actual: 1 +0|whaticket-backend | ✅ Contacto actualizado exitosamente +0|whaticket-backend | 📊 DIAGNÓSTICO COMPLETO DEL MENSAJE: +0|whaticket-backend | ====================================== +0|whaticket-backend | 📱 DATOS CRUDOS: +0|whaticket-backend | RemoteJid: 124777406664808@lid +0|whaticket-backend | Participant: +0|whaticket-backend | FromMe: false +0|whaticket-backend | PushName: Agustín Navarro +0|whaticket-backend | Tipo de mensaje: conversation +0|whaticket-backend | 🔍 ANÁLISIS LID: +0|whaticket-backend | JID analizado: 124777406664808@lid +0|whaticket-backend | 🎫 === ANTES DE FindOrCreateTicketService === +0|whaticket-backend | CONTACTO OBTENIDO: +0|whaticket-backend | ID: 18632 +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | Nombre: Agustín Navarro +0|whaticket-backend | LID: 124777406664808@lid +0|whaticket-backend | WhatsApp ID: 11 +0|whaticket-backend | Company ID: 1 +0|whaticket-backend | =============================== +0|whaticket-backend | [22:48:24.161] INFO: Ticket 75 messages read +0|whaticket-backend | [22:48:38.316] INFO: Ticket 75 messages read +0|whaticket-backend | [22:48:38.438] INFO: Client disconnected: +0|whaticket-backend | [22:48:38.442] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:48:38.505] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:48:38.505] INFO: Socket ID: +0|whaticket-backend | [22:48:38.505] INFO: Handshake query: +0|whaticket-backend | [22:48:38.505] INFO: Token param: +0|whaticket-backend | [22:48:38.505] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:48:38.506] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:48:38.506] INFO: Socket ID: +0|whaticket-backend | [22:48:38.506] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:48:38.510] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:48:38.510] INFO: Socket ID: +0|whaticket-backend | [22:48:38.510] INFO: Handshake query: +0|whaticket-backend | [22:48:38.511] INFO: Token param: +0|whaticket-backend | [22:48:38.511] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:48:38.511] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:48:38.511] INFO: Socket ID: +0|whaticket-backend | [22:48:38.512] INFO: Attached user ID: +0|whaticket-backend | [22:48:38.518] INFO: Client Connected - User: +0|whaticket-backend | [22:48:38.519] INFO: Client Connected - User: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:48:39.253] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:48:39.254] INFO: Socket ID: +0|whaticket-backend | [22:48:39.254] INFO: Handshake query: +0|whaticket-backend | [22:48:39.255] INFO: Token param: +0|whaticket-backend | [22:48:39.255] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:48:39.258] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:48:39.258] INFO: Socket ID: +0|whaticket-backend | [22:48:39.262] INFO: Attached user ID: +0|whaticket-backend | [22:48:39.278] INFO: Ticket 75 messages read +0|whaticket-backend | [22:48:39.281] INFO: Client Connected - User: +0|whaticket-backend | [SendWhatsAppMessage] Enviando mensaje a ticket: 75 +0|whaticket-backend | [SendWhatsAppMessage] Número original: 5492478577503 +0|whaticket-backend | [SendWhatsAppMessage] WhatsApp ID: 11 +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "5492478577503" +0|whaticket-backend | SessionId: "11" +0|whaticket-backend | Clean input: "5492478577503" (13 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-5492478577503_reverse +0|whaticket-backend | [22:48:44.424] INFO: Ticket 75 messages read +0|whaticket-backend | ⚠️ No se encontró mapping exacto +0|whaticket-backend | 🔍 Buscando coincidencias parciales... +0|whaticket-backend | Total mappings: 12 +0|whaticket-backend | 🔍 Buscando LID para número: 5492478577503 +0|whaticket-backend | ✅ Número tiene LID asociado +0|whaticket-backend | LID: "124777406664808" +0|whaticket-backend | [SendWhatsAppMessage] Resultado mapping: +0|whaticket-backend | - Número real: 5492478577503 +0|whaticket-backend | - Es LID: false +0|whaticket-backend | - Original: 5492478577503 +0|whaticket-backend | [SendWhatsAppMessage] Enviando a: 5492478577503@s.whatsapp.net +0|whaticket-backend | Closing session: SessionEntry { +0|whaticket-backend | _chains: { +0|whaticket-backend | 'BW7Kbbik1mK5B/U/1xKBnc75mA8Pj+NCWBx1NVgBPwY/': { chainKey: [Object], chainType: 2, messageKeys: {} }, +0|whaticket-backend | 'BXb1jVaTAuP/AbyEbTqNh4EbI8QP9b5EQIJjxfYMwCxa': { chainKey: [Object], chainType: 1, messageKeys: {} } +0|whaticket-backend | }, +0|whaticket-backend | registrationId: 11985483, +0|whaticket-backend | currentRatchet: { +0|whaticket-backend | ephemeralKeyPair: { +0|whaticket-backend | pubKey: , +0|whaticket-backend | privKey: +0|whaticket-backend | }, +0|whaticket-backend | lastRemoteEphemeralKey: , +0|whaticket-backend | previousCounter: 0, +0|whaticket-backend | rootKey: +0|whaticket-backend | }, +0|whaticket-backend | indexInfo: { +0|whaticket-backend | baseKey: , +0|whaticket-backend | baseKeyType: 2, +0|whaticket-backend | closed: -1, +0|whaticket-backend | used: 1769272289951, +0|whaticket-backend | created: 1769272289951, +0|whaticket-backend | remoteIdentityKey: +0|whaticket-backend | } +0|whaticket-backend | } +0|whaticket-backend | Closing session: SessionEntry { +0|whaticket-backend | _chains: { +0|whaticket-backend | 'BbM1Uc7/iUPO9cD5/FP292Y8LZgJBVEm7ZCbVzNL/2hX': { chainKey: [Object], chainType: 1, messageKeys: {} } +0|whaticket-backend | }, +0|whaticket-backend | registrationId: 387988832, +0|whaticket-backend | currentRatchet: { +0|whaticket-backend | ephemeralKeyPair: { +0|whaticket-backend | pubKey: , +0|whaticket-backend | privKey: +0|whaticket-backend | }, +0|whaticket-backend | lastRemoteEphemeralKey: , +0|whaticket-backend | previousCounter: 0, +0|whaticket-backend | rootKey: +0|whaticket-backend | }, +0|whaticket-backend | indexInfo: { +0|whaticket-backend | baseKey: , +0|whaticket-backend | baseKeyType: 1, +0|whaticket-backend | closed: -1, +0|whaticket-backend | used: 1769293421263, +0|whaticket-backend | created: 1769293421263, +0|whaticket-backend | remoteIdentityKey: +0|whaticket-backend | }, +0|whaticket-backend | pendingPreKey: { +0|whaticket-backend | signedKeyId: 11982441, +0|whaticket-backend | baseKey: , +0|whaticket-backend | preKeyId: 15221312 +0|whaticket-backend | } +0|whaticket-backend | } +0|whaticket-backend | [SendWhatsAppMessage] ✅ Mensaje enviado exitosamente a: 5492478577503@s.whatsapp.net +0|whaticket-backend | 📨 ========== NUEVO MENSAJE ========== +0|whaticket-backend | 🆔 ID: 3EB038FF89... +0|whaticket-backend | 📞 RemoteJid: 5492478577503@s.whatsapp.net +0|whaticket-backend | 👤 FromMe: SÍ +0|whaticket-backend | 👥 Participant: N/A +0|whaticket-backend | 📛 PushName: N/A +0|whaticket-backend | ⏰ Timestamp: 10:48:44 PM +0|whaticket-backend | 🔍 ANALIZANDO JID: 5492478577503@s.whatsapp.net +0|whaticket-backend | Tipo: Tradicional +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "5492478577503@s.whatsapp.net" +0|whaticket-backend | SessionId: "11" +0|whaticket-backend | Clean input: "5492478577503" (13 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-5492478577503_reverse +0|whaticket-backend | ⚠️ No se encontró mapping exacto +0|whaticket-backend | 🔍 Buscando coincidencias parciales... +0|whaticket-backend | Total mappings: 12 +0|whaticket-backend | 🔍 Buscando LID para número: 5492478577503 +0|whaticket-backend | ✅ Número tiene LID asociado +0|whaticket-backend | LID: "124777406664808" +0|whaticket-backend | 🔍 obtenerNumeroReal - JID recibido: 5492478577503@s.whatsapp.net +0|whaticket-backend | ✅ Número tradicional extraído: 5492478577503 +0|whaticket-backend | ✅ RESULTADO (no LID @lid): +0|whaticket-backend | JID original: 5492478577503@s.whatsapp.net +0|whaticket-backend | Número obtenido: 5492478577503 +0|whaticket-backend | Es LID?: NO +0|whaticket-backend | ✅ FIN obtenerNumeroRealDeMensaje - NORMALIZADOR NO LID +0|whaticket-backend | 📞 === ANTES DE verifyContact === +0|whaticket-backend | msgContact recibido: +0|whaticket-backend | id: 5492478577503@s.whatsapp.net +0|whaticket-backend | name: 5492478577503 +0|whaticket-backend | number: 5492478577503 +0|whaticket-backend | esLid: NO +0|whaticket-backend | lid: N/A +0|whaticket-backend | originalJid: 5492478577503@s.whatsapp.net +0|whaticket-backend | =============================== +0|whaticket-backend | 🔍 === verifyContact INICIO === +0|whaticket-backend | 📥 DATOS DE ENTRADA: +0|whaticket-backend | ID: 5492478577503@s.whatsapp.net +0|whaticket-backend | Nombre: 5492478577503 +0|whaticket-backend | Número recibido: 5492478577503 +0|whaticket-backend | Es LID?: NO +0|whaticket-backend | LID: N/A +0|whaticket-backend | JID original: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🖼️ Foto de perfil obtenida +0|whaticket-backend | 👤 VERIFICANDO CONTACTO: +0|whaticket-backend | ID recibido: 5492478577503@s.whatsapp.net +0|whaticket-backend | Número en msgContact: 5492478577503 +0|whaticket-backend | Es LID?: NO +0|whaticket-backend | LID original: N/A +0|whaticket-backend | JID original: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🔧 DATOS DEL WBOT: +0|whaticket-backend | wbot.id: 11 +0|whaticket-backend | wbot.user?.id: 5492478477558:11@s.whatsapp.net +0|whaticket-backend | ⚠️ No se pudo obtener companyId del WhatsApp, usando valor por defecto: 1 +0|whaticket-backend | 📝 DATOS FINALES DEL CONTACTO: +0|whaticket-backend | Nombre: 5492478577503 +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | LID guardado: null +0|whaticket-backend | Company ID: 1 +0|whaticket-backend | WhatsApp ID: 11 +0|whaticket-backend | originaljid: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🔎 BUSCANDO CONTACTO EXISTENTE... +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | Company ID buscado: 1 +0|whaticket-backend | ✅ Contacto encontrado con número y companyId: 18632 +0|whaticket-backend | Contact Company ID: 1 +0|whaticket-backend | ✅ Contacto final a usar: 18632 +0|whaticket-backend | Nombre actual: Agustín Navarro +0|whaticket-backend | LID actual: 124777406664808@lid +0|whaticket-backend | CompanyId actual: 1 +0|whaticket-backend | Actualizando originaljid a: 5492478577503@s.whatsapp.net +0|whaticket-backend | ✅ Contacto actualizado exitosamente +0|whaticket-backend | 📊 DIAGNÓSTICO COMPLETO DEL MENSAJE: +0|whaticket-backend | ====================================== +0|whaticket-backend | 📱 DATOS CRUDOS: +0|whaticket-backend | RemoteJid: 5492478577503@s.whatsapp.net +0|whaticket-backend | Participant: null +0|whaticket-backend | FromMe: true +0|whaticket-backend | PushName: null +0|whaticket-backend | Tipo de mensaje: extendedTextMessage +0|whaticket-backend | 🔍 ANÁLISIS LID: +0|whaticket-backend | JID analizado: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🎫 === ANTES DE FindOrCreateTicketService === +0|whaticket-backend | CONTACTO OBTENIDO: +0|whaticket-backend | ID: 18632 +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | Nombre: 5492478577503 +0|whaticket-backend | LID: 124777406664808@lid +0|whaticket-backend | WhatsApp ID: 11 +0|whaticket-backend | Company ID: 1 +0|whaticket-backend | =============================== +0|whaticket-backend | [SendWhatsAppMessage] Enviando mensaje a ticket: 75 +0|whaticket-backend | [SendWhatsAppMessage] Número original: 5492478577503 +0|whaticket-backend | [SendWhatsAppMessage] WhatsApp ID: 11 +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "5492478577503" +0|whaticket-backend | SessionId: "11" +0|whaticket-backend | Clean input: "5492478577503" (13 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-5492478577503_reverse +0|whaticket-backend | [22:48:52.335] INFO: Ticket 75 messages read +0|whaticket-backend | ⚠️ No se encontró mapping exacto +0|whaticket-backend | 🔍 Buscando coincidencias parciales... +0|whaticket-backend | Total mappings: 12 +0|whaticket-backend | 🔍 Buscando LID para número: 5492478577503 +0|whaticket-backend | ✅ Número tiene LID asociado +0|whaticket-backend | LID: "124777406664808" +0|whaticket-backend | [SendWhatsAppMessage] Resultado mapping: +0|whaticket-backend | - Número real: 5492478577503 +0|whaticket-backend | - Es LID: false +0|whaticket-backend | - Original: 5492478577503 +0|whaticket-backend | [SendWhatsAppMessage] Enviando a: 5492478577503@s.whatsapp.net +0|whaticket-backend | Closing session: SessionEntry { +0|whaticket-backend | _chains: { +0|whaticket-backend | 'BfHqh45fC4/4ftAans3od/FXe+9Ih5/dTcmwgCmIieMy': { chainKey: [Object], chainType: 1, messageKeys: {} } +0|whaticket-backend | }, +0|whaticket-backend | registrationId: 13008, +0|whaticket-backend | currentRatchet: { +0|whaticket-backend | ephemeralKeyPair: { +0|whaticket-backend | pubKey: , +0|whaticket-backend | privKey: +0|whaticket-backend | }, +0|whaticket-backend | lastRemoteEphemeralKey: , +0|whaticket-backend | previousCounter: 0, +0|whaticket-backend | rootKey: +0|whaticket-backend | }, +0|whaticket-backend | indexInfo: { +0|whaticket-backend | baseKey: , +0|whaticket-backend | baseKeyType: 1, +0|whaticket-backend | closed: -1, +0|whaticket-backend | used: 1769294925142, +0|whaticket-backend | created: 1769294925142, +0|whaticket-backend | remoteIdentityKey: +0|whaticket-backend | }, +0|whaticket-backend | pendingPreKey: { +0|whaticket-backend | signedKeyId: 4, +0|whaticket-backend | baseKey: , +0|whaticket-backend | preKeyId: 971 +0|whaticket-backend | } +0|whaticket-backend | } +0|whaticket-backend | Closing session: SessionEntry { +0|whaticket-backend | _chains: { +0|whaticket-backend | 'BUYDkXguNF3mYk8IHBfRIUzuhDJ/amVq02k+LqixtAV7': { chainKey: [Object], chainType: 1, messageKeys: {} } +0|whaticket-backend | }, +0|whaticket-backend | registrationId: 12283, +0|whaticket-backend | currentRatchet: { +0|whaticket-backend | ephemeralKeyPair: { +0|whaticket-backend | pubKey: , +0|whaticket-backend | privKey: +0|whaticket-backend | }, +0|whaticket-backend | lastRemoteEphemeralKey: , +0|whaticket-backend | previousCounter: 0, +0|whaticket-backend | rootKey: +0|whaticket-backend | }, +0|whaticket-backend | indexInfo: { +0|whaticket-backend | baseKey: , +0|whaticket-backend | baseKeyType: 1, +0|whaticket-backend | closed: -1, +0|whaticket-backend | used: 1769294925144, +0|whaticket-backend | created: 1769294925144, +0|whaticket-backend | remoteIdentityKey: +0|whaticket-backend | }, +0|whaticket-backend | pendingPreKey: { +0|whaticket-backend | signedKeyId: 4, +0|whaticket-backend | baseKey: , +0|whaticket-backend | preKeyId: 1036 +0|whaticket-backend | } +0|whaticket-backend | } +0|whaticket-backend | [SendWhatsAppMessage] ✅ Mensaje enviado exitosamente a: 5492478577503@s.whatsapp.net +0|whaticket-backend | 📨 ========== NUEVO MENSAJE ========== +0|whaticket-backend | 🆔 ID: 3EB04CA12B... +0|whaticket-backend | 📞 RemoteJid: 5492478577503@s.whatsapp.net +0|whaticket-backend | 👤 FromMe: SÍ +0|whaticket-backend | 👥 Participant: N/A +0|whaticket-backend | 📛 PushName: N/A +0|whaticket-backend | ⏰ Timestamp: 10:48:52 PM +0|whaticket-backend | 🔍 ANALIZANDO JID: 5492478577503@s.whatsapp.net +0|whaticket-backend | Tipo: Tradicional +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "5492478577503@s.whatsapp.net" +0|whaticket-backend | SessionId: "11" +0|whaticket-backend | Clean input: "5492478577503" (13 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-5492478577503_reverse +0|whaticket-backend | ⚠️ No se encontró mapping exacto +0|whaticket-backend | 🔍 Buscando coincidencias parciales... +0|whaticket-backend | Total mappings: 12 +0|whaticket-backend | 🔍 Buscando LID para número: 5492478577503 +0|whaticket-backend | ✅ Número tiene LID asociado +0|whaticket-backend | LID: "124777406664808" +0|whaticket-backend | 🔍 obtenerNumeroReal - JID recibido: 5492478577503@s.whatsapp.net +0|whaticket-backend | ✅ Número tradicional extraído: 5492478577503 +0|whaticket-backend | ✅ RESULTADO (no LID @lid): +0|whaticket-backend | JID original: 5492478577503@s.whatsapp.net +0|whaticket-backend | Número obtenido: 5492478577503 +0|whaticket-backend | Es LID?: NO +0|whaticket-backend | ✅ FIN obtenerNumeroRealDeMensaje - NORMALIZADOR NO LID +0|whaticket-backend | 📞 === ANTES DE verifyContact === +0|whaticket-backend | msgContact recibido: +0|whaticket-backend | id: 5492478577503@s.whatsapp.net +0|whaticket-backend | name: 5492478577503 +0|whaticket-backend | number: 5492478577503 +0|whaticket-backend | esLid: NO +0|whaticket-backend | lid: N/A +0|whaticket-backend | originalJid: 5492478577503@s.whatsapp.net +0|whaticket-backend | =============================== +0|whaticket-backend | 🔍 === verifyContact INICIO === +0|whaticket-backend | 📥 DATOS DE ENTRADA: +0|whaticket-backend | ID: 5492478577503@s.whatsapp.net +0|whaticket-backend | Nombre: 5492478577503 +0|whaticket-backend | Número recibido: 5492478577503 +0|whaticket-backend | Es LID?: NO +0|whaticket-backend | LID: N/A +0|whaticket-backend | JID original: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🖼️ Foto de perfil obtenida +0|whaticket-backend | 👤 VERIFICANDO CONTACTO: +0|whaticket-backend | ID recibido: 5492478577503@s.whatsapp.net +0|whaticket-backend | Número en msgContact: 5492478577503 +0|whaticket-backend | Es LID?: NO +0|whaticket-backend | LID original: N/A +0|whaticket-backend | JID original: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🔧 DATOS DEL WBOT: +0|whaticket-backend | wbot.id: 11 +0|whaticket-backend | wbot.user?.id: 5492478477558:11@s.whatsapp.net +0|whaticket-backend | ⚠️ No se pudo obtener companyId del WhatsApp, usando valor por defecto: 1 +0|whaticket-backend | 📝 DATOS FINALES DEL CONTACTO: +0|whaticket-backend | Nombre: 5492478577503 +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | LID guardado: null +0|whaticket-backend | Company ID: 1 +0|whaticket-backend | WhatsApp ID: 11 +0|whaticket-backend | originaljid: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🔎 BUSCANDO CONTACTO EXISTENTE... +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | Company ID buscado: 1 +0|whaticket-backend | ✅ Contacto encontrado con número y companyId: 18632 +0|whaticket-backend | Contact Company ID: 1 +0|whaticket-backend | ✅ Contacto final a usar: 18632 +0|whaticket-backend | Nombre actual: 5492478577503 +0|whaticket-backend | LID actual: 124777406664808@lid +0|whaticket-backend | CompanyId actual: 1 +0|whaticket-backend | ✅ Contacto actualizado exitosamente +0|whaticket-backend | 📊 DIAGNÓSTICO COMPLETO DEL MENSAJE: +0|whaticket-backend | ====================================== +0|whaticket-backend | 📱 DATOS CRUDOS: +0|whaticket-backend | RemoteJid: 5492478577503@s.whatsapp.net +0|whaticket-backend | Participant: null +0|whaticket-backend | FromMe: true +0|whaticket-backend | PushName: null +0|whaticket-backend | Tipo de mensaje: extendedTextMessage +0|whaticket-backend | 🔍 ANÁLISIS LID: +0|whaticket-backend | JID analizado: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🎫 === ANTES DE FindOrCreateTicketService === +0|whaticket-backend | CONTACTO OBTENIDO: +0|whaticket-backend | ID: 18632 +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | Nombre: 5492478577503 +0|whaticket-backend | LID: 124777406664808@lid +0|whaticket-backend | WhatsApp ID: 11 +0|whaticket-backend | Company ID: 1 +0|whaticket-backend | =============================== +0|whaticket-backend | [SendWhatsAppMessage] Enviando mensaje a ticket: 75 +0|whaticket-backend | [SendWhatsAppMessage] Número original: 5492478577503 +0|whaticket-backend | [SendWhatsAppMessage] WhatsApp ID: 11 +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "5492478577503" +0|whaticket-backend | SessionId: "11" +0|whaticket-backend | Clean input: "5492478577503" (13 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-5492478577503_reverse +0|whaticket-backend | [22:48:55.076] INFO: Ticket 75 messages read +0|whaticket-backend | ⚠️ No se encontró mapping exacto +0|whaticket-backend | 🔍 Buscando coincidencias parciales... +0|whaticket-backend | Total mappings: 12 +0|whaticket-backend | 🔍 Buscando LID para número: 5492478577503 +0|whaticket-backend | ✅ Número tiene LID asociado +0|whaticket-backend | LID: "124777406664808" +0|whaticket-backend | [SendWhatsAppMessage] Resultado mapping: +0|whaticket-backend | - Número real: 5492478577503 +0|whaticket-backend | - Es LID: false +0|whaticket-backend | - Original: 5492478577503 +0|whaticket-backend | [SendWhatsAppMessage] Enviando a: 5492478577503@s.whatsapp.net +0|whaticket-backend | Closing session: SessionEntry { +0|whaticket-backend | _chains: { +0|whaticket-backend | 'BVQD7LnRySRc3Fn9ziPmdmqoz2m1Ed07jW5xs0+dxgly': { chainKey: [Object], chainType: 1, messageKeys: {} } +0|whaticket-backend | }, +0|whaticket-backend | registrationId: 13008, +0|whaticket-backend | currentRatchet: { +0|whaticket-backend | ephemeralKeyPair: { +0|whaticket-backend | pubKey: , +0|whaticket-backend | privKey: +0|whaticket-backend | }, +0|whaticket-backend | lastRemoteEphemeralKey: , +0|whaticket-backend | previousCounter: 0, +0|whaticket-backend | rootKey: +0|whaticket-backend | }, +0|whaticket-backend | indexInfo: { +0|whaticket-backend | baseKey: , +0|whaticket-backend | baseKeyType: 1, +0|whaticket-backend | closed: -1, +0|whaticket-backend | used: 1769294932574, +0|whaticket-backend | created: 1769294932574, +0|whaticket-backend | remoteIdentityKey: +0|whaticket-backend | }, +0|whaticket-backend | pendingPreKey: { +0|whaticket-backend | signedKeyId: 4, +0|whaticket-backend | baseKey: , +0|whaticket-backend | preKeyId: 1476 +0|whaticket-backend | } +0|whaticket-backend | } +0|whaticket-backend | Closing session: SessionEntry { +0|whaticket-backend | _chains: { +0|whaticket-backend | 'BSsSOgqKeZ+n5dDJMAtWDueNaqu99bCPmUn7XGYhDUp9': { chainKey: [Object], chainType: 1, messageKeys: {} } +0|whaticket-backend | }, +0|whaticket-backend | registrationId: 12283, +0|whaticket-backend | currentRatchet: { +0|whaticket-backend | ephemeralKeyPair: { +0|whaticket-backend | pubKey: , +0|whaticket-backend | privKey: +0|whaticket-backend | }, +0|whaticket-backend | lastRemoteEphemeralKey: , +0|whaticket-backend | previousCounter: 0, +0|whaticket-backend | rootKey: +0|whaticket-backend | }, +0|whaticket-backend | indexInfo: { +0|whaticket-backend | baseKey: , +0|whaticket-backend | baseKeyType: 1, +0|whaticket-backend | closed: -1, +0|whaticket-backend | used: 1769294932580, +0|whaticket-backend | created: 1769294932580, +0|whaticket-backend | remoteIdentityKey: +0|whaticket-backend | }, +0|whaticket-backend | pendingPreKey: { +0|whaticket-backend | signedKeyId: 4, +0|whaticket-backend | baseKey: , +0|whaticket-backend | preKeyId: 1040 +0|whaticket-backend | } +0|whaticket-backend | } +0|whaticket-backend | [SendWhatsAppMessage] ✅ Mensaje enviado exitosamente a: 5492478577503@s.whatsapp.net +0|whaticket-backend | 📨 ========== NUEVO MENSAJE ========== +0|whaticket-backend | 🆔 ID: 3EB0B2C577... +0|whaticket-backend | 📞 RemoteJid: 5492478577503@s.whatsapp.net +0|whaticket-backend | 👤 FromMe: SÍ +0|whaticket-backend | 👥 Participant: N/A +0|whaticket-backend | 📛 PushName: N/A +0|whaticket-backend | ⏰ Timestamp: 10:48:55 PM +0|whaticket-backend | 🔍 ANALIZANDO JID: 5492478577503@s.whatsapp.net +0|whaticket-backend | Tipo: Tradicional +0|whaticket-backend | 🔍 [GetRealNumberFromLid] INICIANDO BUSQUEDA +0|whaticket-backend | Input: "5492478577503@s.whatsapp.net" +0|whaticket-backend | SessionId: "11" +0|whaticket-backend | Clean input: "5492478577503" (13 dígitos) +0|whaticket-backend | Tipo: Número normal +0|whaticket-backend | 🔍 Buscando mapping exacto: lid-mapping-5492478577503_reverse +0|whaticket-backend | ⚠️ No se encontró mapping exacto +0|whaticket-backend | 🔍 Buscando coincidencias parciales... +0|whaticket-backend | Total mappings: 12 +0|whaticket-backend | 🔍 Buscando LID para número: 5492478577503 +0|whaticket-backend | ✅ Número tiene LID asociado +0|whaticket-backend | LID: "124777406664808" +0|whaticket-backend | 🔍 obtenerNumeroReal - JID recibido: 5492478577503@s.whatsapp.net +0|whaticket-backend | ✅ Número tradicional extraído: 5492478577503 +0|whaticket-backend | ✅ RESULTADO (no LID @lid): +0|whaticket-backend | JID original: 5492478577503@s.whatsapp.net +0|whaticket-backend | Número obtenido: 5492478577503 +0|whaticket-backend | Es LID?: NO +0|whaticket-backend | ✅ FIN obtenerNumeroRealDeMensaje - NORMALIZADOR NO LID +0|whaticket-backend | 📞 === ANTES DE verifyContact === +0|whaticket-backend | msgContact recibido: +0|whaticket-backend | id: 5492478577503@s.whatsapp.net +0|whaticket-backend | name: 5492478577503 +0|whaticket-backend | number: 5492478577503 +0|whaticket-backend | esLid: NO +0|whaticket-backend | lid: N/A +0|whaticket-backend | originalJid: 5492478577503@s.whatsapp.net +0|whaticket-backend | =============================== +0|whaticket-backend | 🔍 === verifyContact INICIO === +0|whaticket-backend | 📥 DATOS DE ENTRADA: +0|whaticket-backend | ID: 5492478577503@s.whatsapp.net +0|whaticket-backend | Nombre: 5492478577503 +0|whaticket-backend | Número recibido: 5492478577503 +0|whaticket-backend | Es LID?: NO +0|whaticket-backend | LID: N/A +0|whaticket-backend | JID original: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🖼️ Foto de perfil obtenida +0|whaticket-backend | 👤 VERIFICANDO CONTACTO: +0|whaticket-backend | ID recibido: 5492478577503@s.whatsapp.net +0|whaticket-backend | Número en msgContact: 5492478577503 +0|whaticket-backend | Es LID?: NO +0|whaticket-backend | LID original: N/A +0|whaticket-backend | JID original: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🔧 DATOS DEL WBOT: +0|whaticket-backend | wbot.id: 11 +0|whaticket-backend | wbot.user?.id: 5492478477558:11@s.whatsapp.net +0|whaticket-backend | ⚠️ No se pudo obtener companyId del WhatsApp, usando valor por defecto: 1 +0|whaticket-backend | 📝 DATOS FINALES DEL CONTACTO: +0|whaticket-backend | Nombre: 5492478577503 +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | LID guardado: null +0|whaticket-backend | Company ID: 1 +0|whaticket-backend | WhatsApp ID: 11 +0|whaticket-backend | originaljid: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🔎 BUSCANDO CONTACTO EXISTENTE... +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | Company ID buscado: 1 +0|whaticket-backend | ✅ Contacto encontrado con número y companyId: 18632 +0|whaticket-backend | Contact Company ID: 1 +0|whaticket-backend | ✅ Contacto final a usar: 18632 +0|whaticket-backend | Nombre actual: 5492478577503 +0|whaticket-backend | LID actual: 124777406664808@lid +0|whaticket-backend | CompanyId actual: 1 +0|whaticket-backend | ✅ Contacto actualizado exitosamente +0|whaticket-backend | 📊 DIAGNÓSTICO COMPLETO DEL MENSAJE: +0|whaticket-backend | ====================================== +0|whaticket-backend | 📱 DATOS CRUDOS: +0|whaticket-backend | RemoteJid: 5492478577503@s.whatsapp.net +0|whaticket-backend | Participant: null +0|whaticket-backend | FromMe: true +0|whaticket-backend | PushName: null +0|whaticket-backend | Tipo de mensaje: extendedTextMessage +0|whaticket-backend | 🔍 ANÁLISIS LID: +0|whaticket-backend | JID analizado: 5492478577503@s.whatsapp.net +0|whaticket-backend | 🎫 === ANTES DE FindOrCreateTicketService === +0|whaticket-backend | CONTACTO OBTENIDO: +0|whaticket-backend | ID: 18632 +0|whaticket-backend | Número: 5492478577503 +0|whaticket-backend | Nombre: 5492478577503 +0|whaticket-backend | LID: 124777406664808@lid +0|whaticket-backend | WhatsApp ID: 11 +0|whaticket-backend | Company ID: 1 +0|whaticket-backend | =============================== +0|whaticket-backend | [22:56:10.559] INFO: Client disconnected: +0|whaticket-backend | [22:56:10.560] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:56:10.861] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:56:10.862] INFO: Socket ID: +0|whaticket-backend | [22:56:10.862] INFO: Handshake query: +0|whaticket-backend | [22:56:10.862] INFO: Token param: +0|whaticket-backend | [22:56:10.863] ERROR: Middleware authentication FAILED: +0|whaticket-backend | [22:56:11.361] WARN: +0|whaticket-backend | message: "Invalid token. We'll try to assign a new one on next request" +0|whaticket-backend | statusCode: 403 +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:56:13.161] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:56:13.161] INFO: Socket ID: +0|whaticket-backend | [22:56:13.162] INFO: Handshake query: +0|whaticket-backend | [22:56:13.162] INFO: Token param: +0|whaticket-backend | [22:56:13.163] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:56:13.164] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:56:13.164] INFO: Socket ID: +0|whaticket-backend | [22:56:13.165] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:56:13.169] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:56:13.171] INFO: Socket ID: +0|whaticket-backend | [22:56:13.171] INFO: Handshake query: +0|whaticket-backend | [22:56:13.172] INFO: Token param: +0|whaticket-backend | [22:56:13.172] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:56:13.174] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:56:13.174] INFO: Socket ID: +0|whaticket-backend | [22:56:13.175] INFO: Attached user ID: +0|whaticket-backend | [22:56:13.180] INFO: Client Connected - User: +0|whaticket-backend | [22:56:13.181] INFO: Client Connected - User: +0|whaticket-backend | [22:57:05.276] INFO: Client disconnected: +0|whaticket-backend | [22:57:05.277] INFO: Client disconnected: +0|whaticket-backend | [22:57:05.278] INFO: Client disconnected: +0|whaticket-backend | [22:57:05.278] INFO: Client disconnected: +0|whaticket-backend | [22:57:05.278] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:57:16.215] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:57:16.215] INFO: Socket ID: +0|whaticket-backend | [22:57:16.216] INFO: Handshake query: +0|whaticket-backend | [22:57:16.216] INFO: Token param: +0|whaticket-backend | [22:57:16.216] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:57:16.216] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:57:16.216] INFO: Socket ID: +0|whaticket-backend | [22:57:16.216] INFO: Attached user ID: +0|whaticket-backend | [22:57:16.221] INFO: Client Connected - User: +0|whaticket-backend | [22:57:18.886] INFO: Client disconnected: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:57:18.948] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:57:18.949] INFO: Socket ID: +0|whaticket-backend | [22:57:18.949] INFO: Handshake query: +0|whaticket-backend | [22:57:18.949] INFO: Token param: +0|whaticket-backend | [22:57:18.949] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:57:18.949] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:57:18.949] INFO: Socket ID: +0|whaticket-backend | [22:57:18.949] INFO: Attached user ID: +0|whaticket-backend | 🟢🟢🟢 SOCKET.IO MIDDLEWARE EXECUTED! +0|whaticket-backend | [22:57:18.955] INFO: === MIDDLEWARE EXECUTED === +0|whaticket-backend | [22:57:18.955] INFO: Socket ID: +0|whaticket-backend | [22:57:18.955] INFO: Handshake query: +0|whaticket-backend | [22:57:18.955] INFO: Token param: +0|whaticket-backend | [22:57:18.955] INFO: Middleware SUCCESS - User ID: +0|whaticket-backend | 🟢🟢🟢 CONNECTION EVENT FINALLY FIRED! +0|whaticket-backend | [22:57:18.956] INFO: === CONNECTION EVENT === +0|whaticket-backend | [22:57:18.956] INFO: Socket ID: +0|whaticket-backend | [22:57:18.956] INFO: Attached user ID: +0|whaticket-backend | [22:57:18.959] INFO: Client Connected - User: +0|whaticket-backend | [22:57:18.961] INFO: Client Connected - User: +0|whaticket-backend | [22:57:33.282] INFO: Client disconnected: +0|whaticket-backend | [22:57:33.282] INFO: Client disconnected: +0|whaticket-backend | [22:57:33.283] INFO: Client disconnected: +0|whaticket-backend | [22:57:33.284] INFO: Client disconnected: +0|whaticket-backend | [22:57:33.284] INFO: Client disconnected: +0|whaticket-backend | [22:57:33.284] INFO: Client disconnected: diff --git a/frontend/src/components/MessagesList/index.js b/frontend/src/components/MessagesList/index.js index bfb7c86..975d056 100644 --- a/frontend/src/components/MessagesList/index.js +++ b/frontend/src/components/MessagesList/index.js @@ -1,7 +1,7 @@ import React, { useState, useEffect, useReducer, useRef } from "react"; import { isSameDay, parseISO, format } from "date-fns"; -import openSocket from "socket.io-client"; +import openSocket from "../../services/socket-io"; import clsx from "clsx"; import { green } from "@material-ui/core/colors"; @@ -379,11 +379,9 @@ const MessagesList = ({ ticketId, isGroup }) => { }, [pageNumber, ticketId]); useEffect(() => { - const socket = openSocket(`${process.env.REACT_APP_BACKEND_URL}`, { - transports: ["websocket"], - }); + const socket = openSocket(); - socket.on("connect", () => socket.emit("joinChatBox", ticketId)); + socket.on("ready", () => socket.emit("joinChatBox", ticketId)); socket.on("appMessage", (data) => { if (data.action === "create") { diff --git a/frontend/src/components/NotificationsPopOver/index.js b/frontend/src/components/NotificationsPopOver/index.js index e6acc68..3c2783e 100644 --- a/frontend/src/components/NotificationsPopOver/index.js +++ b/frontend/src/components/NotificationsPopOver/index.js @@ -80,7 +80,7 @@ const NotificationsPopOver = () => { useEffect(() => { const socket = openSocket(); - socket.on("connect", () => socket.emit("joinNotification")); + socket.on("ready", () => socket.emit("joinNotification")); socket.on("ticket", data => { if (data.action === "updateUnread" || data.action === "delete") { diff --git a/frontend/src/components/Ticket/index.js b/frontend/src/components/Ticket/index.js index 6ef88c6..89e54fc 100644 --- a/frontend/src/components/Ticket/index.js +++ b/frontend/src/components/Ticket/index.js @@ -107,7 +107,7 @@ const Ticket = () => { useEffect(() => { const socket = openSocket(); - socket.on("connect", () => socket.emit("joinChatBox", ticketId)); + socket.on("ready", () => socket.emit("joinChatBox", ticketId)); socket.on("ticket", (data) => { if (data.action === "update") { diff --git a/frontend/src/components/TicketsList/index.js b/frontend/src/components/TicketsList/index.js index 84bc494..62703b2 100644 --- a/frontend/src/components/TicketsList/index.js +++ b/frontend/src/components/TicketsList/index.js @@ -203,7 +203,7 @@ const TicketsList = (props) => { const notBelongsToUserQueues = (ticket) => ticket.queueId && selectedQueueIds.indexOf(ticket.queueId) === -1; - socket.on("connect", () => { + socket.on("ready", () => { if (status) { socket.emit("joinTickets", status); } else { diff --git a/frontend/src/hooks/useSocket/index.js b/frontend/src/hooks/useSocket/index.js index 4e5def8..4e4cf85 100644 --- a/frontend/src/hooks/useSocket/index.js +++ b/frontend/src/hooks/useSocket/index.js @@ -1,26 +1,35 @@ -import React, { useMemo } from "react"; -import openSocket from "socket.io-client"; -import { getBackendUrl } from "../../config"; +import { useEffect, useState } from "react"; +import connectToSocket from "../../services/socket-io"; const useProvideSocket = () => { - const socket = useMemo(() => { - const token = localStorage.getItem("token"); - const socket = openSocket(getBackendUrl() + "1", { - query: { - token: JSON.parse(token), - }, - transports: ["websocket"], - }); - return socket; - }, []); + const [socket, setSocket] = useState(null); + + useEffect(() => { + // Obtener la instancia única del socket + const socketInstance = connectToSocket(); + + if (socketInstance) { + setSocket(socketInstance); - React.useMemo(() => { - socket.on("connect", () => { - console.log("connected"); - }); - }, [socket]); + // Escuchar cuando está listo + socketInstance.on("ready", () => { + console.log("✅ Socket ready event received"); + }); + + // Cleanup al desmontar + return () => { + if (socketInstance) { + socketInstance.off("ready"); + } + }; + } else { + console.warn("⚠️ Socket instance is null - check token and backend URL"); + } + }, []); - return socket; + // Retorna una función que obtiene el socket + // Esto permite que los componentes llamen socketProvider() para obtener la instancia + return () => socket; }; export default useProvideSocket; diff --git a/frontend/src/pages/Queues/index.js b/frontend/src/pages/Queues/index.js index 8349aeb..4614ac1 100644 --- a/frontend/src/pages/Queues/index.js +++ b/frontend/src/pages/Queues/index.js @@ -113,7 +113,7 @@ const Queues = () => { const token = JSON.parse(localStorage.getItem("token")); const socket = openSocket(process.env.REACT_APP_BACKEND_URL, {query: {token}}); - socket.on("connect", () => { + socket.on("ready", () => { socket.emit("joinCompany") }); diff --git a/frontend/src/pages/Schedules/index.js b/frontend/src/pages/Schedules/index.js index 37594df..e8588cf 100644 --- a/frontend/src/pages/Schedules/index.js +++ b/frontend/src/pages/Schedules/index.js @@ -1,6 +1,6 @@ import React, { useState, useEffect, useReducer, useCallback } from "react"; import { toast } from "react-toastify"; -import openSocket from "socket.io-client"; +import openSocket from "../../services/socket-io"; import { makeStyles } from "@material-ui/core/styles"; import Paper from "@material-ui/core/Paper"; @@ -140,7 +140,7 @@ const Schedules = () => { useEffect(() => { handleOpenScheduleModalFromContactId(); - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + const socket = openSocket(); socket.on("user", (data) => { if (data.action === "update" || data.action === "create") { diff --git a/frontend/src/pages/Settings/index.js b/frontend/src/pages/Settings/index.js index af65efd..bceb408 100644 --- a/frontend/src/pages/Settings/index.js +++ b/frontend/src/pages/Settings/index.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import openSocket from "socket.io-client"; +import openSocket from "../../services/socket-io"; import { makeStyles } from "@material-ui/core/styles"; import Paper from "@material-ui/core/Paper"; @@ -57,7 +57,7 @@ const Settings = () => { }, []); useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + const socket = openSocket(); socket.on("settings", data => { if (data.action === "update") { diff --git a/frontend/src/pages/Tags/index.js b/frontend/src/pages/Tags/index.js index a5e9d9b..5034b2b 100644 --- a/frontend/src/pages/Tags/index.js +++ b/frontend/src/pages/Tags/index.js @@ -1,6 +1,6 @@ import React, { useState, useEffect, useReducer, useCallback } from "react"; import { toast } from "react-toastify"; -import openSocket from "socket.io-client"; +import openSocket from "../../services/socket-io"; import { makeStyles } from "@material-ui/core/styles"; import Paper from "@material-ui/core/Paper"; @@ -124,7 +124,7 @@ const Tags = () => { }, [searchParam, pageNumber, fetchTags]); useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + const socket = openSocket(); socket.on("user", (data) => { if (data.action === "update" || data.action === "create") { diff --git a/frontend/src/services/socket-io.js b/frontend/src/services/socket-io.js index 5a7ed43..0faef36 100644 --- a/frontend/src/services/socket-io.js +++ b/frontend/src/services/socket-io.js @@ -1,14 +1,53 @@ import openSocket from "socket.io-client"; import { getBackendUrl } from "../config"; + function connectToSocket() { const token = localStorage.getItem("token"); - const socket = openSocket(getBackendUrl(), { - query: { - auth_token: JSON.parse(token), - }, - }); + + // ✅ Validar que el token existe + if (!token) { + console.error("❌ No token found in localStorage"); + return null; + } + + try { + const parsedToken = JSON.parse(token); + const backendUrl = getBackendUrl(); + + if (!backendUrl) { + console.error("❌ Backend URL not configured. Check REACT_APP_BACKEND_URL in .env"); + return null; + } + + console.log(`✅ Connecting to socket at: ${backendUrl}`); + + const socket = openSocket(backendUrl, { + query: { + token: parsedToken, + }, + reconnection: true, + reconnectionDelay: 1000, + reconnectionDelayMax: 5000, + reconnectionAttempts: 5 + }); + + socket.on("connect", () => { + console.log("✅ Socket connected successfully"); + }); + + socket.on("connect_error", (error) => { + console.error("❌ Socket connection error:", error); + }); + + socket.on("disconnect", (reason) => { + console.warn("⚠️ Socket disconnected:", reason); + }); - return socket; + return socket; + } catch (error) { + console.error("❌ Error parsing token or connecting:", error); + return null; + } } export default connectToSocket;