var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var WorkerService_1; import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom } from 'rxjs'; let WorkerService = WorkerService_1 = class WorkerService { config; http; logger = new Logger(WorkerService_1.name); isRunning = true; apiUrl; workerKey; openwaUrl; openwaApiKey; intervalMs; sesionesCache = []; sesionesCacheTimestamp = 0; SESIONES_CACHE_TTL = 5 * 60 * 1000; constructor(config, http) { this.config = config; this.http = http; this.apiUrl = this.config.get('API_URL') || 'http://localhost:3000/v1'; this.workerKey = this.config.get('WORKER_KEY') || ''; this.openwaUrl = this.config.get('OPENWA_URL') || 'http://localhost:2785'; this.openwaApiKey = this.config.get('OPENWA_API_KEY') || ''; this.intervalMs = Number(this.config.get('WORKER_INTERVAL')) || 30000; } onModuleInit() { this.logger.log(`Worker iniciado. Intervalo entre mensajes: ${this.intervalMs}ms`); this.procesarCiclo(); } onModuleDestroy() { this.isRunning = false; this.logger.log('Worker detenido'); } timestamp() { return new Date().toLocaleTimeString('es-BO', { hour12: false }); } async procesarCiclo() { if (!this.isRunning) return; await this.procesar(); const delayMs = this.calcularDelayEntreMensajes(); const delaySeg = (delayMs / 1000).toFixed(1); this.logger.log(`[${this.timestamp()}] ⏳ Esperando ${delaySeg}s antes del siguiente mensaje...`); await this.delay(delayMs); setTimeout(() => this.procesarCiclo(), 0); } async procesar() { const mensaje = await this.obtenerPendiente(); if (!mensaje) { this.logger.log(`[${this.timestamp()}] No hay mensajes pendientes`); return; } await this.procesarMensaje(mensaje); } async obtenerPendiente() { try { const response = await firstValueFrom(this.http.get(`${this.apiUrl}/mensaje/pendientes`, { params: { limit: 1 }, headers: { 'x-worker-key': this.workerKey }, })); const data = response.data?.data; return data && data.length > 0 ? data[0] : null; } catch (error) { const message = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`[${this.timestamp()}] Error obteniendo mensaje pendiente: ${message}`); return null; } } async obtenerSesionesDisponibles() { const ahora = Date.now(); if (this.sesionesCache.length > 0 && ahora - this.sesionesCacheTimestamp < this.SESIONES_CACHE_TTL) { return this.sesionesCache; } try { const response = await firstValueFrom(this.http.get(`${this.openwaUrl}/api/sessions`, { headers: { 'X-API-Key': this.openwaApiKey }, })); const todas = response.data; this.sesionesCache = todas .filter((s) => s.status === 'ready') .map((s) => ({ id: s.id, name: s.name, phone: s.phone })); this.sesionesCacheTimestamp = ahora; this.logger.log(`[${this.timestamp()}] 🔄 Sesiones OpenWA actualizadas: ${this.sesionesCache.length} disponibles`); } catch (error) { const message = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`[${this.timestamp()}] Error obteniendo sesiones OpenWA: ${message}`); } return this.sesionesCache; } async obtenerSesionRandom() { const sesiones = await this.obtenerSesionesDisponibles(); if (sesiones.length === 0) { this.logger.warn(`[${this.timestamp()}] ⚠️ No hay sesiones OpenWA disponibles`); return null; } const elegida = sesiones[Math.floor(Math.random() * sesiones.length)]; this.logger.log(`[${this.timestamp()}] 🎲 Sesión seleccionada: ${elegida.name} (${elegida.phone})`); return elegida.id; } async procesarMensaje(mensaje) { const { idMensajeWhatsApp, telefonoDestino, tipo, mensaje1, mensaje2, mensaje3, urlQR } = mensaje; const inicio = Date.now(); this.logger.log(`[${this.timestamp()}] ▶ Procesando mensaje ${idMensajeWhatsApp} → ${telefonoDestino} (${tipo})`); try { const sessionId = await this.obtenerSesionRandom(); if (!sessionId) { await this.actualizarEstado(idMensajeWhatsApp, 'ERROR', 'No hay sesiones OpenWA disponibles'); return; } await this.actualizarEstado(idMensajeWhatsApp, 'ENVIANDO'); this.logger.log(`[${this.timestamp()}] 🔄 Estado cambiado a ENVIANDO`); const chatId = `${telefonoDestino}@c.us`; const mensajes = [mensaje1, mensaje2, mensaje3].filter((m) => Boolean(m)); if (mensajes.length > 0) { const texto1 = mensajes[0]; if (tipo === 'RECEPCION_PAQUETE' && urlQR) { try { await this.enviarImagen(sessionId, chatId, urlQR, texto1); this.logger.log(`[${this.timestamp()}] 📷✉️ Mensaje 1 (QR + imagen) enviado INMEDIATO`); } catch { this.logger.warn(`[${this.timestamp()}] ⚠️ Error enviando imagen, usando fallback con link`); const fullUrl = urlQR.startsWith('http') ? urlQR : `${this.apiUrl.replace('/v1', '')}${urlQR}`; const textoConQR = `${texto1}\n\n🔗 *Ver QR:* ${fullUrl}`; await this.enviarTexto(sessionId, chatId, textoConQR); this.logger.log(`[${this.timestamp()}] ✉️ Mensaje 1 (QR como link) enviado INMEDIATO`); } } else { await this.enviarTexto(sessionId, chatId, texto1); this.logger.log(`[${this.timestamp()}] ✉️ Mensaje 1 enviado INMEDIATO`); } } const texto1ParaDelay = mensajes[0] || ''; const delay1 = this.calcularDelayEscritura(texto1ParaDelay.length); const delay1Seg = (delay1 / 1000).toFixed(1); this.logger.log(`[${this.timestamp()}] ⏳ Delay1 calculado: ${delay1Seg}s (para usar después)`); for (let i = 1; i < mensajes.length; i++) { const texto = mensajes[i]; const delayMsg = this.calcularDelayEscritura(texto.length); const delayMsgSeg = (delayMsg / 1000).toFixed(1); await this.setTyping(sessionId, chatId); this.logger.log(`[${this.timestamp()}] ⌨️ Typing mensaje ${i + 1}...`); this.logger.log(`[${this.timestamp()}] ⏳ Esperando ${delayMsgSeg}s (${texto.length} chars)`); await this.delay(delayMsg); await this.enviarTexto(sessionId, chatId, texto); this.logger.log(`[${this.timestamp()}] ✉️ Mensaje ${i + 1} enviado`); if (i < mensajes.length - 1) { const delayEntre = this.calcularDelayEntreMensajes(); const delayEntreSeg = (delayEntre / 1000).toFixed(1); this.logger.log(`[${this.timestamp()}] ⏳ Esperando ${delayEntreSeg}s antes del siguiente mensaje...`); await this.delay(delayEntre); } } await this.actualizarEstado(idMensajeWhatsApp, 'ENVIADO'); const totalSeg = ((Date.now() - inicio) / 1000).toFixed(1); this.logger.log(`[${this.timestamp()}] ✅ Mensaje ${idMensajeWhatsApp} ENVIADO (${mensajes.length} mensajes) → tiempo total: ${totalSeg}s`); this.logger.log(`[${this.timestamp()}] ⏳ Esperando delay1: ${delay1Seg}s antes de continuar...`); await this.delay(delay1); } catch (error) { const message = error instanceof Error ? error.message : 'Unknown error'; const responseMessage = error?.response?.data?.message || error?.response?.data?.error || ''; const fullError = responseMessage ? `${message} → ${responseMessage}` : message; await this.actualizarEstado(idMensajeWhatsApp, 'ERROR', fullError); const totalSeg = ((Date.now() - inicio) / 1000).toFixed(1); this.logger.error(`[${this.timestamp()}] ❌ Error en ${idMensajeWhatsApp}: ${fullError} (${totalSeg}s)`); } } calcularDelayEscritura(caracteres) { const caracteresPorMinuto = 350; const base = (caracteres / caracteresPorMinuto) * 60 * 1000; const random = 1 + (Math.random() * 0.6 - 0.3); const reaccion = 500 + Math.random() * 1000; const delay = base * random + reaccion; return Math.min(Math.max(delay, 3000), 120000); } calcularDelayEntreMensajes() { const base = 3000; const random = 1 + (Math.random() * 0.6 - 0.3); return Math.min(Math.max(base * random, 2000), 8000); } delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async setTyping(sessionId, chatId) { try { await firstValueFrom(this.http.post(`${this.openwaUrl}/api/sessions/${sessionId}/chat-state`, { chatId, state: 'composing' }, { headers: { 'X-API-Key': this.openwaApiKey, 'Content-Type': 'application/json', }, })); } catch { } } async enviarTexto(sessionId, chatId, text) { const response = await firstValueFrom(this.http.post(`${this.openwaUrl}/api/sessions/${sessionId}/messages/send-text`, { chatId, text }, { headers: { 'X-API-Key': this.openwaApiKey, 'Content-Type': 'application/json', }, })); return response.data; } async enviarImagen(sessionId, chatId, imageUrl, caption) { const fullUrl = imageUrl.startsWith('http') ? imageUrl : `http://localhost:3000${imageUrl}`; const response = await firstValueFrom(this.http.get(fullUrl, { responseType: 'arraybuffer' })); const base64Data = Buffer.from(response.data).toString('base64'); const dataUrl = `data:image/png;base64,${base64Data}`; const result = await firstValueFrom(this.http.post(`${this.openwaUrl}/api/sessions/${sessionId}/messages/send-image`, { chatId, base64: dataUrl, caption, mimetype: 'image/png', filename: 'qr.png', }, { headers: { 'X-API-Key': this.openwaApiKey, 'Content-Type': 'application/json', }, })); return result.data; } async actualizarEstado(id, estado, error) { try { await firstValueFrom(this.http.put(`${this.apiUrl}/mensaje/${id}/estado`, { estado, error: error || null, fechaEnvio: new Date().toISOString(), }, { headers: { 'x-worker-key': this.workerKey }, })); } catch (err) { const message = err instanceof Error ? err.message : 'Unknown error'; this.logger.error(`[${this.timestamp()}] Error actualizando estado del mensaje ${id}: ${message}`); } } }; WorkerService = WorkerService_1 = __decorate([ Injectable(), __metadata("design:paramtypes", [ConfigService, HttpService]) ], WorkerService); export { WorkerService }; //# sourceMappingURL=worker.service.js.map