From 24d35333dff3e86df0c5db5ff349302e901b2a5a Mon Sep 17 00:00:00 2001 From: Horux Dev Date: Mon, 3 Aug 2026 04:07:58 +0000 Subject: [PATCH] =?UTF-8?q?feat(sat):=20agrega=20ProxyManager=20para=20rot?= =?UTF-8?q?aci=C3=B3n=20de=20proxies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nuevo servicio apps/api/src/services/sat/proxy.service.ts. - Soporta estrategias round-robin y random. - Parsea SAT_PROXY_LIST y crea agentes HttpsProxyAgent. - Script scripts/test-proxy-rotation.ts para validar configuración. --- apps/api/scripts/test-proxy-rotation.ts | 20 +++++ apps/api/src/services/sat/proxy.service.ts | 100 +++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 apps/api/scripts/test-proxy-rotation.ts create mode 100644 apps/api/src/services/sat/proxy.service.ts diff --git a/apps/api/scripts/test-proxy-rotation.ts b/apps/api/scripts/test-proxy-rotation.ts new file mode 100644 index 0000000..84a87a8 --- /dev/null +++ b/apps/api/scripts/test-proxy-rotation.ts @@ -0,0 +1,20 @@ +import 'dotenv/config'; +import { ProxyManager } from '../src/services/sat/proxy.service.js'; + +const manager = new ProxyManager( + process.env.SAT_PROXY_LIST || '', + (process.env.SAT_PROXY_STRATEGY as any) || 'round-robin', +); + +console.log(`Total de proxies: ${manager.getProxyCount()}`); +console.log(`Estrategia: ${process.env.SAT_PROXY_STRATEGY || 'round-robin'}`); +console.log('Próximos 10 proxies seleccionados:'); + +for (let i = 0; i < 10; i++) { + const proxy = manager.getNextProxy(); + if (!proxy) { + console.log(` ${i + 1}. (sin proxy configurado)`); + } else { + console.log(` ${i + 1}. ${proxy.host}:${proxy.port}`); + } +} diff --git a/apps/api/src/services/sat/proxy.service.ts b/apps/api/src/services/sat/proxy.service.ts new file mode 100644 index 0000000..699c225 --- /dev/null +++ b/apps/api/src/services/sat/proxy.service.ts @@ -0,0 +1,100 @@ +import { HttpsProxyAgent } from 'https-proxy-agent'; + +export interface ProxyConfig { + url: string; + host: string; + port: number; + username?: string; + password?: string; +} + +export type ProxyStrategy = 'round-robin' | 'random'; + +function parseProxyList(raw: string): ProxyConfig[] { + if (!raw.trim()) return []; + + const configs: ProxyConfig[] = []; + const items = raw.split(',').map(s => s.trim()).filter(Boolean); + + for (const item of items) { + try { + const url = new URL(item); + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + console.warn(`[ProxyManager] Protocolo no soportado, se omite: ${item}`); + continue; + } + configs.push({ + url: item, + host: url.hostname, + port: Number(url.port) || (url.protocol === 'https:' ? 443 : 80), + username: url.username || undefined, + password: url.password || undefined, + }); + } catch (err) { + console.warn(`[ProxyManager] URL de proxy inválida, se omite: ${item}`); + } + } + + return configs; +} + +export class ProxyManager { + private proxies: ProxyConfig[]; + private strategy: ProxyStrategy; + private currentIndex = 0; + + constructor( + proxyList = process.env.SAT_PROXY_LIST || '', + strategy: ProxyStrategy = (process.env.SAT_PROXY_STRATEGY as ProxyStrategy) || 'round-robin', + ) { + this.proxies = parseProxyList(proxyList); + this.strategy = ['round-robin', 'random'].includes(strategy) ? strategy : 'round-robin'; + + if (this.proxies.length > 0) { + console.log(`[ProxyManager] ${this.proxies.length} proxy(s) configurados (estrategia: ${this.strategy})`); + } else { + console.log('[ProxyManager] No hay proxies configurados; se usará la IP directa del servidor'); + } + } + + hasProxies(): boolean { + return this.proxies.length > 0; + } + + getProxyCount(): number { + return this.proxies.length; + } + + getNextProxy(): ProxyConfig | null { + if (this.proxies.length === 0) return null; + + if (this.strategy === 'random') { + return this.proxies[Math.floor(Math.random() * this.proxies.length)]; + } + + const proxy = this.proxies[this.currentIndex]; + this.currentIndex = (this.currentIndex + 1) % this.proxies.length; + return proxy; + } + + createAgent(proxy: ProxyConfig): HttpsProxyAgent { + return new HttpsProxyAgent(proxy.url); + } + + /** + * Crea un agente con el siguiente proxy del pool. + * Útil cuando se quiere una nueva conexión por solicitud. + */ + createNextAgent(): HttpsProxyAgent | null { + const proxy = this.getNextProxy(); + if (!proxy) return null; + console.log(`[ProxyManager] Usando proxy: ${proxy.host}:${proxy.port}`); + return this.createAgent(proxy); + } +} + +/** + * Instancia global del proxy manager. + * Lee SAT_PROXY_LIST y SAT_PROXY_STRATEGY del entorno. + */ +export const proxyManager = new ProxyManager();