|  |  | @ -2,125 +2,125 @@ import type { Database } from 'bun:sqlite'; | 
			
		
	
		
		
			
				
					
					|  |  |  | import { db } from '../db'; |  |  |  | import { db } from '../db'; | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | type QueuedResponse = { |  |  |  | type QueuedResponse = { | 
			
		
	
		
		
			
				
					
					|  |  |  |   recipient: string; |  |  |  | 	recipient: string; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   message: string; |  |  |  | 	message: string; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   mentions?: string[]; // full JIDs to mention (e.g., '346xxx@s.whatsapp.net')
 |  |  |  | 	mentions?: string[]; // full JIDs to mention (e.g., '346xxx@s.whatsapp.net')
 | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | }; |  |  |  | }; | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | type ClaimedItem = { |  |  |  | type ClaimedItem = { | 
			
		
	
		
		
			
				
					
					|  |  |  |   id: number; |  |  |  | 	id: number; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   recipient: string; |  |  |  | 	recipient: string; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   message: string; |  |  |  | 	message: string; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   metadata?: string | null; // JSON-encoded metadata (e.g., { mentioned: [...] })
 |  |  |  | 	metadata?: string | null; // JSON-encoded metadata (e.g., { mentioned: [...] })
 | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | }; |  |  |  | }; | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | export const ResponseQueue = { |  |  |  | export const ResponseQueue = { | 
			
		
	
		
		
			
				
					
					|  |  |  |   // Permite inyectar una DB distinta en tests si se necesita
 |  |  |  | 	// Permite inyectar una DB distinta en tests si se necesita
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   dbInstance: db as Database, |  |  |  | 	dbInstance: db as Database, | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   // Conservamos la cola en memoria por compatibilidad, aunque no se usa para persistencia
 |  |  |  | 	// Conservamos la cola en memoria por compatibilidad, aunque no se usa para persistencia
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   queue: [] as QueuedResponse[], |  |  |  | 	queue: [] as QueuedResponse[], | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   // Configuración fija (MVP)
 |  |  |  | 	// Configuración fija (MVP)
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   WORKERS: 2, |  |  |  | 	WORKERS: 2, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   BATCH_SIZE: 10, |  |  |  | 	BATCH_SIZE: 10, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   SLEEP_MS: 500, |  |  |  | 	SLEEP_MS: 500, | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   _running: false, |  |  |  | 	_running: false, | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   async add(responses: QueuedResponse[]) { |  |  |  | 	async add(responses: QueuedResponse[]) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     try { |  |  |  | 		try { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       const botNumber = process.env.CHATBOT_PHONE_NUMBER; |  |  |  | 			const botNumber = process.env.CHATBOT_PHONE_NUMBER; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       const filtered = responses.filter(r => |  |  |  | 			const filtered = responses.filter(r => | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         r.recipient && |  |  |  | 				r.recipient && | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         r.message && |  |  |  | 				r.message && | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         (!botNumber || r.recipient !== botNumber) |  |  |  | 				(!botNumber || r.recipient !== botNumber) | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       ); |  |  |  | 			); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |       if (filtered.length === 0) { |  |  |  | 			if (filtered.length === 0) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         return; |  |  |  | 				return; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       } |  |  |  | 			} | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |       const insert = this.dbInstance.prepare(` |  |  |  | 			const insert = this.dbInstance.prepare(` | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |         INSERT INTO response_queue (recipient, message, metadata) |  |  |  |         INSERT INTO response_queue (recipient, message, metadata) | 
			
		
	
		
		
			
				
					
					|  |  |  |         VALUES (?, ?, ?) |  |  |  |         VALUES (?, ?, ?) | 
			
		
	
		
		
			
				
					
					|  |  |  |       `);
 |  |  |  |       `);
 | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |       this.dbInstance.transaction((rows: QueuedResponse[]) => { |  |  |  | 			this.dbInstance.transaction((rows: QueuedResponse[]) => { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         for (const r of rows) { |  |  |  | 				for (const r of rows) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           const metadata = |  |  |  | 					const metadata = | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |             r.mentions && r.mentions.length > 0 |  |  |  | 						r.mentions && r.mentions.length > 0 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |               ? JSON.stringify({ mentioned: r.mentions }) |  |  |  | 							? JSON.stringify({ mentioned: r.mentions }) | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |               : null; |  |  |  | 							: null; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           insert.run(r.recipient, r.message, metadata); |  |  |  | 					insert.run(r.recipient, r.message, metadata); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         } |  |  |  | 				} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       })(filtered); |  |  |  | 			})(filtered); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       console.log('Queued responses (persisted):', filtered.length); |  |  |  | 			console.log('Queued responses (persisted):', filtered.length); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     } catch (err) { |  |  |  | 		} catch (err) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       console.error('Failed to persist queued responses:', err); |  |  |  | 			console.error('Failed to persist queued responses:', err); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       throw err; |  |  |  | 			throw err; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  | 		} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   }, |  |  |  | 	}, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   getHeaders(): HeadersInit { |  |  |  | 	getHeaders(): HeadersInit { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     return { |  |  |  | 		return { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       apikey: process.env.EVOLUTION_API_KEY || '', |  |  |  | 			apikey: process.env.EVOLUTION_API_KEY || '', | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       'Content-Type': 'application/json', |  |  |  | 			'Content-Type': 'application/json', | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     }; |  |  |  | 		}; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   }, |  |  |  | 	}, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   async sendOne(item: ClaimedItem): Promise<boolean> { |  |  |  | 	async sendOne(item: ClaimedItem): Promise<boolean> { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     const baseUrl = process.env.EVOLUTION_API_URL; |  |  |  | 		const baseUrl = process.env.EVOLUTION_API_URL; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     const instance = process.env.EVOLUTION_API_INSTANCE; |  |  |  | 		const instance = process.env.EVOLUTION_API_INSTANCE; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     if (!baseUrl || !instance) { |  |  |  | 		if (!baseUrl || !instance) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       console.error('Missing EVOLUTION_API_URL or EVOLUTION_API_INSTANCE'); |  |  |  | 			console.error('Missing EVOLUTION_API_URL or EVOLUTION_API_INSTANCE'); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       return false; |  |  |  | 			return false; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  | 		} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     // Endpoint típico de Evolution API para texto simple
 |  |  |  | 		// Endpoint típico de Evolution API para texto simple
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     const url = `${baseUrl}/message/sendText/${instance}`; |  |  |  | 		const url = `${baseUrl}/message/sendText/${instance}`; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     try { |  |  |  | 		try { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       // Build payload, adding mentioned JIDs if present in metadata
 |  |  |  | 			// Build payload, adding mentioned JIDs if present in metadata
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       const payload: any = { |  |  |  | 			const payload: any = { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         number: item.recipient, |  |  |  | 				number: item.recipient, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         text: item.message, |  |  |  | 				text: item.message, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       }; |  |  |  | 			}; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       if (item.metadata) { |  |  |  | 			if (item.metadata) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         try { |  |  |  | 				try { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           const parsed = JSON.parse(item.metadata); |  |  |  | 					const parsed = JSON.parse(item.metadata); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           if (parsed && Array.isArray(parsed.mentioned) && parsed.mentioned.length > 0) { |  |  |  | 					if (parsed && Array.isArray(parsed.mentioned) && parsed.mentioned.length > 0) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |             payload.mentioned = parsed.mentioned; |  |  |  | 						payload.mentioned = parsed.mentioned; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           } |  |  |  | 					} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         } catch { |  |  |  | 				} catch { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           // ignore bad metadata
 |  |  |  | 					// ignore bad metadata
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         } |  |  |  | 				} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       } |  |  |  | 			} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       const response = await fetch(url, { |  |  |  | 			const response = await fetch(url, { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         method: 'POST', |  |  |  | 				method: 'POST', | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         headers: this.getHeaders(), |  |  |  | 				headers: this.getHeaders(), | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         body: JSON.stringify(payload), |  |  |  | 				body: JSON.stringify(payload), | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       }); |  |  |  | 			}); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       if (!response.ok) { |  |  |  | 			if (!response.ok) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         const body = await response.text().catch(() => ''); |  |  |  | 				const body = await response.text().catch(() => ''); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         console.warn('Send failed:', { status: response.status, body: body?.slice(0, 200) }); |  |  |  | 				console.warn('Send failed:', { status: response.status, body: body?.slice(0, 200) }); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         return false; |  |  |  | 				return false; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       } |  |  |  | 			} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 			console.log(`✅ Sent message to with this as payload: ${JSON.stringify(payload)}`); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       return true; |  |  |  | 			return true; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     } catch (err) { |  |  |  | 		} catch (err) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       console.error('Network error sending message:', err); |  |  |  | 			console.error('Network error sending message:', err); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       return false; |  |  |  | 			return false; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  | 		} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   }, |  |  |  | 	}, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   claimNextBatch(limit: number): ClaimedItem[] { |  |  |  | 	claimNextBatch(limit: number): ClaimedItem[] { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     // Selecciona y marca como 'processing' en una sola sentencia para evitar carreras
 |  |  |  | 		// Selecciona y marca como 'processing' en una sola sentencia para evitar carreras
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     const rows = this.dbInstance.prepare(` |  |  |  | 		const rows = this.dbInstance.prepare(` | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |       UPDATE response_queue |  |  |  |       UPDATE response_queue | 
			
		
	
		
		
			
				
					
					|  |  |  |       SET status = 'processing', |  |  |  |       SET status = 'processing', | 
			
		
	
		
		
			
				
					
					|  |  |  |           updated_at = strftime('%Y-%m-%d %H:%M:%f', 'now') |  |  |  |           updated_at = strftime('%Y-%m-%d %H:%M:%f', 'now') | 
			
		
	
	
		
		
			
				
					|  |  | @ -133,70 +133,70 @@ export const ResponseQueue = { | 
			
		
	
		
		
			
				
					
					|  |  |  |       RETURNING id, recipient, message, metadata |  |  |  |       RETURNING id, recipient, message, metadata | 
			
		
	
		
		
			
				
					
					|  |  |  |     `).all(limit) as ClaimedItem[];
 |  |  |  |     `).all(limit) as ClaimedItem[];
 | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     return rows || []; |  |  |  | 		return rows || []; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   }, |  |  |  | 	}, | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   markSent(id: number) { |  |  |  | 	markSent(id: number) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     this.dbInstance.prepare(` |  |  |  | 		this.dbInstance.prepare(` | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |       UPDATE response_queue |  |  |  |       UPDATE response_queue | 
			
		
	
		
		
			
				
					
					|  |  |  |       SET status = 'sent', |  |  |  |       SET status = 'sent', | 
			
		
	
		
		
			
				
					
					|  |  |  |           updated_at = strftime('%Y-%m-%d %H:%M:%f', 'now') |  |  |  |           updated_at = strftime('%Y-%m-%d %H:%M:%f', 'now') | 
			
		
	
		
		
			
				
					
					|  |  |  |       WHERE id = ? |  |  |  |       WHERE id = ? | 
			
		
	
		
		
			
				
					
					|  |  |  |     `).run(id);
 |  |  |  |     `).run(id);
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   }, |  |  |  | 	}, | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   markFailed(id: number, errorMsg: string) { |  |  |  | 	markFailed(id: number, errorMsg: string) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     const msg = (errorMsg || '').toString().slice(0, 500); |  |  |  | 		const msg = (errorMsg || '').toString().slice(0, 500); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     this.dbInstance.prepare(` |  |  |  | 		this.dbInstance.prepare(` | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |       UPDATE response_queue |  |  |  |       UPDATE response_queue | 
			
		
	
		
		
			
				
					
					|  |  |  |       SET status = 'failed', |  |  |  |       SET status = 'failed', | 
			
		
	
		
		
			
				
					
					|  |  |  |           last_error = ?, |  |  |  |           last_error = ?, | 
			
		
	
		
		
			
				
					
					|  |  |  |           updated_at = strftime('%Y-%m-%d %H:%M:%f', 'now') |  |  |  |           updated_at = strftime('%Y-%m-%d %H:%M:%f', 'now') | 
			
		
	
		
		
			
				
					
					|  |  |  |       WHERE id = ? |  |  |  |       WHERE id = ? | 
			
		
	
		
		
			
				
					
					|  |  |  |     `).run(msg, id);
 |  |  |  |     `).run(msg, id);
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   }, |  |  |  | 	}, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   async workerLoop(workerId: number) { |  |  |  | 	async workerLoop(workerId: number) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     while (this._running) { |  |  |  | 		while (this._running) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       try { |  |  |  | 			try { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         const batch = this.claimNextBatch(this.BATCH_SIZE); |  |  |  | 				const batch = this.claimNextBatch(this.BATCH_SIZE); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         if (batch.length === 0) { |  |  |  | 				if (batch.length === 0) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           await new Promise(r => setTimeout(r, this.SLEEP_MS)); |  |  |  | 					await new Promise(r => setTimeout(r, this.SLEEP_MS)); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           continue; |  |  |  | 					continue; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         } |  |  |  | 				} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         for (const item of batch) { |  |  |  | 				for (const item of batch) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           const ok = await this.sendOne(item); |  |  |  | 					const ok = await this.sendOne(item); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           if (ok) { |  |  |  | 					if (ok) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |             this.markSent(item.id); |  |  |  | 						this.markSent(item.id); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           } else { |  |  |  | 					} else { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |             this.markFailed(item.id, 'send failed'); |  |  |  | 						this.markFailed(item.id, 'send failed'); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |           } |  |  |  | 					} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         } |  |  |  | 				} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       } catch (err) { |  |  |  | 			} catch (err) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         console.error(`ResponseQueue worker ${workerId} error:`, err); |  |  |  | 				console.error(`ResponseQueue worker ${workerId} error:`, err); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         // Evitar bucle apretado ante errores
 |  |  |  | 				// Evitar bucle apretado ante errores
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         await new Promise(r => setTimeout(r, this.SLEEP_MS)); |  |  |  | 				await new Promise(r => setTimeout(r, this.SLEEP_MS)); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       } |  |  |  | 			} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  | 		} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   }, |  |  |  | 	}, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   async process() { |  |  |  | 	async process() { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     // Inicia N workers en background, retorna inmediatamente
 |  |  |  | 		// Inicia N workers en background, retorna inmediatamente
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     if (this._running) { |  |  |  | 		if (this._running) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       return; |  |  |  | 			return; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  | 		} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     this._running = true; |  |  |  | 		this._running = true; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     console.log(`Starting ResponseQueue with ${this.WORKERS} workers`); |  |  |  | 		console.log(`Starting ResponseQueue with ${this.WORKERS} workers`); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     for (let i = 0; i < this.WORKERS; i++) { |  |  |  | 		for (let i = 0; i < this.WORKERS; i++) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       // No await: correr en paralelo
 |  |  |  | 			// No await: correr en paralelo
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       this.workerLoop(i + 1); |  |  |  | 			this.workerLoop(i + 1); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  | 		} | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   }, |  |  |  | 	}, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   stop() { |  |  |  | 	stop() { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     this._running = false; |  |  |  | 		this._running = false; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   } |  |  |  | 	} | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | }; |  |  |  | }; | 
			
		
	
	
		
		
			
				
					|  |  | 
 |