app-paqueteria-worker/dist/worker/worker.service.js
2026-09-18 22:50:02 -04:00

265 lines
No EOL
12 KiB
JavaScript

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;
}
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`);
}
if (mensajes.length > 1) {
await this.actualizarEstado(idMensajeWhatsApp, 'ENVIANDO');
this.logger.log(`[${this.timestamp()}] 🔄 Estado cambiado a ENVIANDO`);
}
}
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