Skip to main content

Melhores Práticas

Este guia apresenta as melhores práticas para usar a API Speedio de forma eficiente, segura e escalável em produção.

Segurança

1. Gerenciamento de Credenciais

# Use variáveis de ambiente
export SPEEDIO_USERNAME="usuario_producao"
export SPEEDIO_PASSWORD="senha_complexa_123!"

# Use serviços de secrets management
aws secretsmanager get-secret-value --secret-id "speedio-api-credentials"

# Configure rotação automática
kubectl create secret generic speedio-credentials \
  --from-literal=username="$SPEEDIO_USERNAME" \
  --from-literal=password="$SPEEDIO_PASSWORD"

2. Rotação de Credenciais

class CredentialManager {
  constructor() {
    this.credentials = null;
    this.lastRotation = null;
    this.rotationInterval = 24 * 60 * 60 * 1000; // 24 horas
  }
  
  async getCredentials() {
    if (this.needsRotation()) {
      await this.rotateCredentials();
    }
    
    return this.credentials;
  }
  
  needsRotation() {
    if (!this.lastRotation) return true;
    return Date.now() - this.lastRotation > this.rotationInterval;
  }
  
  async rotateCredentials() {
    try {
      // Buscar novas credenciais do secret manager
      this.credentials = await this.fetchFromSecretManager();
      this.lastRotation = Date.now();
      
      console.log('🔑 Credenciais rotacionadas com sucesso');
    } catch (error) {
      console.error('❌ Erro ao rotacionar credenciais:', error);
      // Manter credenciais atuais se a rotação falhar
    }
  }
  
  async fetchFromSecretManager() {
    // Implementar busca no AWS Secrets Manager, Azure Key Vault, etc.
    return {
      username: process.env.SPEEDIO_USERNAME,
      password: process.env.SPEEDIO_PASSWORD
    };
  }
}

3. Validação de Entrada

class InputValidator {
  static validateCNPJ(cnpj) {
    if (!cnpj || typeof cnpj !== 'string') {
      throw new ValidationError('CNPJ deve ser uma string válida');
    }
    
    // Remove formatação
    const cleanCNPJ = cnpj.replace(/\D/g, '');
    
    if (cleanCNPJ.length !== 14) {
      throw new ValidationError('CNPJ deve ter 14 dígitos');
    }
    
    // Validar dígitos verificadores
    if (!this.isValidCNPJ(cleanCNPJ)) {
      throw new ValidationError('CNPJ inválido');
    }
    
    return cleanCNPJ;
  }
  
  static validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    
    if (!email || typeof email !== 'string') {
      throw new ValidationError('E-mail deve ser uma string válida');
    }
    
    if (!emailRegex.test(email)) {
      throw new ValidationError('Formato de e-mail inválido');
    }
    
    if (email.length > 320) {
      throw new ValidationError('E-mail muito longo');
    }
    
    return email.toLowerCase().trim();
  }
  
  static validateBatchSize(items, maxSize = 100) {
    if (!Array.isArray(items)) {
      throw new ValidationError('Deve ser um array');
    }
    
    if (items.length === 0) {
      throw new ValidationError('Array não pode estar vazio');
    }
    
    if (items.length > maxSize) {
      throw new ValidationError(`Máximo ${maxSize} itens por requisição`);
    }
    
    return items;
  }
}

// Uso
async function buscarEmpresasSeguro(cnpjs) {
  // Validar entrada
  InputValidator.validateBatchSize(cnpjs, 10);
  const validCNPJs = cnpjs.map(cnpj => InputValidator.validateCNPJ(cnpj));
  
  return await client.buscarCNPJ(validCNPJs);
}

Performance

1. Cache Inteligente

class IntelligentCache {
  constructor(options = {}) {
    this.cache = new Map();
    this.hitCount = 0;
    this.missCount = 0;
    this.defaultTTL = options.defaultTTL || 3600000; // 1 hora
    this.maxSize = options.maxSize || 1000;
    
    // TTLs específicos por tipo de dados
    this.ttlByType = {
      'cnpj': 24 * 60 * 60 * 1000, // 24 horas - dados empresariais mudam pouco
      'validation': 12 * 60 * 60 * 1000, // 12 horas - validações são estáveis
      'leads': 4 * 60 * 60 * 1000 // 4 horas - leads podem mudar mais
    };
    
    this.startCleanup();
  }
  
  generateKey(type, identifier) {
    return `${type}:${identifier}`;
  }
  
  set(type, identifier, data, customTTL = null) {
    const key = this.generateKey(type, identifier);
    const ttl = customTTL || this.ttlByType[type] || this.defaultTTL;
    
    const item = {
      data,
      timestamp: Date.now(),
      ttl,
      accessCount: 0,
      lastAccess: Date.now()
    };
    
    // Implementar LRU se cache estiver cheio
    if (this.cache.size >= this.maxSize) {
      this.evictLRU();
    }
    
    this.cache.set(key, item);
    
    console.debug(`📋 Cache SET: ${key} (TTL: ${ttl / 1000}s)`);
  }
  
  get(type, identifier) {
    const key = this.generateKey(type, identifier);
    const item = this.cache.get(key);
    
    if (!item) {
      this.missCount++;
      console.debug(`📋 Cache MISS: ${key}`);
      return null;
    }
    
    // Verificar se expirou
    if (Date.now() - item.timestamp > item.ttl) {
      this.cache.delete(key);
      this.missCount++;
      console.debug(`📋 Cache EXPIRED: ${key}`);
      return null;
    }
    
    // Atualizar estatísticas de acesso
    item.accessCount++;
    item.lastAccess = Date.now();
    
    this.hitCount++;
    console.debug(`📋 Cache HIT: ${key} (acesso #${item.accessCount})`);
    
    return item.data;
  }
  
  has(type, identifier) {
    return this.get(type, identifier) !== null;
  }
  
  evictLRU() {
    let oldestKey = null;
    let oldestTime = Date.now();
    
    for (const [key, item] of this.cache.entries()) {
      if (item.lastAccess < oldestTime) {
        oldestTime = item.lastAccess;
        oldestKey = key;
      }
    }
    
    if (oldestKey) {
      this.cache.delete(oldestKey);
      console.debug(`📋 Cache EVICTED: ${oldestKey}`);
    }
  }
  
  getStats() {
    const totalRequests = this.hitCount + this.missCount;
    const hitRate = totalRequests > 0 ? (this.hitCount / totalRequests) * 100 : 0;
    
    return {
      size: this.cache.size,
      maxSize: this.maxSize,
      hitCount: this.hitCount,
      missCount: this.missCount,
      hitRate: hitRate.toFixed(2) + '%',
      totalRequests
    };
  }
  
  startCleanup() {
    // Limpeza periódica de itens expirados
    setInterval(() => {
      const now = Date.now();
      let cleanedCount = 0;
      
      for (const [key, item] of this.cache.entries()) {
        if (now - item.timestamp > item.ttl) {
          this.cache.delete(key);
          cleanedCount++;
        }
      }
      
      if (cleanedCount > 0) {
        console.debug(`📋 Cache cleanup: ${cleanedCount} itens removidos`);
      }
    }, 5 * 60 * 1000); // A cada 5 minutos
  }
}

const cache = new IntelligentCache({
  maxSize: 5000,
  defaultTTL: 3600000
});

// Integração com cliente
async function buscarCNPJComCache(cnpj) {
  // Verificar cache primeiro
  const cached = cache.get('cnpj', cnpj);
  if (cached) {
    return cached;
  }
  
  // Buscar na API
  const result = await client.buscarCNPJ([cnpj]);
  
  // Cachear resultado
  if (result && result.length > 0) {
    cache.set('cnpj', cnpj, result[0]);
  }
  
  return result;
}

2. Processamento em Lote

class BatchProcessor {
  constructor(options = {}) {
    this.batchSize = options.batchSize || 10;
    this.concurrency = options.concurrency || 3;
    this.delayBetweenBatches = options.delayBetweenBatches || 1000;
    this.queue = [];
    this.processing = false;
  }
  
  async processCNPJs(cnpjs, onProgress = null) {
    const results = [];
    const batches = this.createBatches(cnpjs);
    
    console.log(`🔄 Processando ${cnpjs.length} CNPJs em ${batches.length} lotes`);
    
    for (let i = 0; i < batches.length; i += this.concurrency) {
      const concurrentBatches = batches.slice(i, i + this.concurrency);
      
      // Processar lotes concorrentemente
      const batchPromises = concurrentBatches.map(async (batch, batchIndex) => {
        try {
          console.log(`📦 Processando lote ${i + batchIndex + 1}/${batches.length}`);
          
          const batchResults = await this.processBatch(batch);
          
          if (onProgress) {
            onProgress({
              currentBatch: i + batchIndex + 1,
              totalBatches: batches.length,
              processed: (i + batchIndex + 1) * this.batchSize,
              total: cnpjs.length,
              results: batchResults
            });
          }
          
          return batchResults;
        } catch (error) {
          console.error(`❌ Erro no lote ${i + batchIndex + 1}:`, error.message);
          return []; // Retornar array vazio em caso de erro
        }
      });
      
      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults.flat());
      
      // Aguardar entre grupos de lotes para respeitar rate limits
      if (i + this.concurrency < batches.length) {
        console.log(`⏳ Aguardando ${this.delayBetweenBatches / 1000}s...`);
        await this.sleep(this.delayBetweenBatches);
      }
    }
    
    console.log(`✅ Processamento concluído: ${results.length} resultados`);
    return results;
  }
  
  createBatches(items) {
    const batches = [];
    for (let i = 0; i < items.length; i += this.batchSize) {
      batches.push(items.slice(i, i + this.batchSize));
    }
    return batches;
  }
  
  async processBatch(cnpjs) {
    try {
      const result = await client.buscarCNPJ(cnpjs);
      
      // Cachear resultados individuais
      result.forEach(empresa => {
        if (empresa && empresa.cnpj) {
          cache.set('cnpj', empresa.cnpj, empresa);
        }
      });
      
      return result;
    } catch (error) {
      // Tentar processar individualmente em caso de erro do lote
      console.warn('⚠️ Erro no lote, tentando processamento individual...');
      return await this.processIndividually(cnpjs);
    }
  }
  
  async processIndividually(cnpjs) {
    const results = [];
    
    for (const cnpj of cnpjs) {
      try {
        const result = await buscarCNPJComCache(cnpj);
        if (result) {
          results.push(result);
        }
        
        // Pequeno delay entre requisições individuais
        await this.sleep(200);
      } catch (error) {
        console.error(`❌ Erro ao processar CNPJ ${cnpj}:`, error.message);
      }
    }
    
    return results;
  }
  
  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Uso do processador em lote
const batchProcessor = new BatchProcessor({
  batchSize: 10,
  concurrency: 2,
  delayBetweenBatches: 1500
});

async function processarGrandeVolume(cnpjs) {
  return await batchProcessor.processCNPJs(cnpjs, (progress) => {
    console.log(`📊 Progresso: ${progress.processed}/${progress.total} (${((progress.processed / progress.total) * 100).toFixed(1)}%)`);
  });
}

3. Connection Pooling

class ConnectionPool {
  constructor(options = {}) {
    this.maxConnections = options.maxConnections || 10;
    this.timeout = options.timeout || 30000;
    this.keepAlive = options.keepAlive !== false;
    
    // Configurar agente HTTP com pool de conexões
    this.httpAgent = new require('http').Agent({
      keepAlive: this.keepAlive,
      maxSockets: this.maxConnections,
      timeout: this.timeout
    });
    
    this.httpsAgent = new require('https').Agent({
      keepAlive: this.keepAlive,
      maxSockets: this.maxConnections,
      timeout: this.timeout
    });
    
    this.setupAxios();
  }
  
  setupAxios() {
    axios.defaults.httpAgent = this.httpAgent;
    axios.defaults.httpsAgent = this.httpsAgent;
    
    // Configurar timeouts globais
    axios.defaults.timeout = this.timeout;
    
    console.log(`🔌 Connection pool configurado: ${this.maxConnections} conexões máximas`);
  }
  
  getStats() {
    return {
      httpSockets: this.httpAgent.sockets,
      httpRequests: this.httpAgent.requests,
      httpsSocket: this.httpsAgent.sockets,
      httpsRequests: this.httpsAgent.requests
    };
  }
}

const connectionPool = new ConnectionPool({
  maxConnections: 15,
  timeout: 10000,
  keepAlive: true
});

Monitoramento e Observabilidade

1. Métricas Detalhadas

class MetricsCollector {
  constructor() {
    this.metrics = {
      // Contadores
      requests_total: 0,
      requests_success: 0,
      requests_error: 0,
      cache_hits: 0,
      cache_misses: 0,
      
      // Histogramas
      response_times: [],
      batch_sizes: [],
      
      // Gauges
      active_connections: 0,
      queue_size: 0,
      
      // Por endpoint
      endpoints: {},
      
      // Por código de erro
      error_codes: {},
      
      // Por horário
      hourly_stats: {}
    };
    
    this.startTime = Date.now();
    this.lastReport = Date.now();
  }
  
  recordRequest(endpoint, success, responseTime, statusCode, batchSize = 1) {
    this.metrics.requests_total++;
    
    if (success) {
      this.metrics.requests_success++;
    } else {
      this.metrics.requests_error++;
      
      // Contar por código de erro
      this.metrics.error_codes[statusCode] = 
        (this.metrics.error_codes[statusCode] || 0) + 1;
    }
    
    // Registrar por endpoint
    if (!this.metrics.endpoints[endpoint]) {
      this.metrics.endpoints[endpoint] = {
        total: 0,
        success: 0,
        error: 0,
        avg_response_time: 0,
        response_times: []
      };
    }
    
    const endpointMetrics = this.metrics.endpoints[endpoint];
    endpointMetrics.total++;
    
    if (success) {
      endpointMetrics.success++;
    } else {
      endpointMetrics.error++;
    }
    
    // Response times
    this.metrics.response_times.push(responseTime);
    endpointMetrics.response_times.push(responseTime);
    
    // Manter apenas os últimos 1000 tempos de resposta
    if (this.metrics.response_times.length > 1000) {
      this.metrics.response_times.shift();
    }
    
    if (endpointMetrics.response_times.length > 100) {
      endpointMetrics.response_times.shift();
    }
    
    // Batch sizes
    this.metrics.batch_sizes.push(batchSize);
    if (this.metrics.batch_sizes.length > 1000) {
      this.metrics.batch_sizes.shift();
    }
    
    // Estatísticas horárias
    const hour = new Date().getHours();
    if (!this.metrics.hourly_stats[hour]) {
      this.metrics.hourly_stats[hour] = { requests: 0, errors: 0 };
    }
    this.metrics.hourly_stats[hour].requests++;
    if (!success) {
      this.metrics.hourly_stats[hour].errors++;
    }
    
    // Atualizar médias
    this.updateAverages();
  }
  
  recordCacheHit() {
    this.metrics.cache_hits++;
  }
  
  recordCacheMiss() {
    this.metrics.cache_misses++;
  }
  
  updateAverages() {
    // Calcular médias globais
    if (this.metrics.response_times.length > 0) {
      this.metrics.avg_response_time = 
        this.metrics.response_times.reduce((a, b) => a + b, 0) / 
        this.metrics.response_times.length;
    }
    
    // Calcular médias por endpoint
    Object.values(this.metrics.endpoints).forEach(endpoint => {
      if (endpoint.response_times.length > 0) {
        endpoint.avg_response_time = 
          endpoint.response_times.reduce((a, b) => a + b, 0) / 
          endpoint.response_times.length;
      }
    });
  }
  
  getReport() {
    const now = Date.now();
    const uptime = now - this.startTime;
    const timeSinceLastReport = now - this.lastReport;
    
    const report = {
      timestamp: new Date().toISOString(),
      uptime_seconds: Math.floor(uptime / 1000),
      period_seconds: Math.floor(timeSinceLastReport / 1000),
      
      // Taxas
      success_rate: this.metrics.requests_total > 0 ? 
        (this.metrics.requests_success / this.metrics.requests_total * 100).toFixed(2) + '%' : '0%',
      
      cache_hit_rate: (this.metrics.cache_hits + this.metrics.cache_misses) > 0 ? 
        (this.metrics.cache_hits / (this.metrics.cache_hits + this.metrics.cache_misses) * 100).toFixed(2) + '%' : '0%',
      
      requests_per_second: timeSinceLastReport > 0 ? 
        (this.metrics.requests_total / (timeSinceLastReport / 1000)).toFixed(2) : '0',
      
      // Performance
      avg_response_time: this.metrics.avg_response_time?.toFixed(2) + 'ms' || '0ms',
      
      p95_response_time: this.calculatePercentile(this.metrics.response_times, 95)?.toFixed(2) + 'ms' || '0ms',
      
      max_response_time: this.metrics.response_times.length > 0 ? 
        Math.max(...this.metrics.response_times) + 'ms' : '0ms',
      
      // Contadores
      total_requests: this.metrics.requests_total,
      successful_requests: this.metrics.requests_success,
      failed_requests: this.metrics.requests_error,
      cache_hits: this.metrics.cache_hits,
      cache_misses: this.metrics.cache_misses,
      
      // Detalhes
      endpoints: Object.fromEntries(
        Object.entries(this.metrics.endpoints).map(([endpoint, stats]) => [
          endpoint,
          {
            total: stats.total,
            success_rate: stats.total > 0 ? 
              (stats.success / stats.total * 100).toFixed(2) + '%' : '0%',
            avg_response_time: stats.avg_response_time?.toFixed(2) + 'ms' || '0ms'
          }
        ])
      ),
      
      error_codes: this.metrics.error_codes,
      hourly_distribution: this.metrics.hourly_stats
    };
    
    this.lastReport = now;
    return report;
  }
  
  calculatePercentile(values, percentile) {
    if (values.length === 0) return 0;
    
    const sorted = [...values].sort((a, b) => a - b);
    const index = Math.ceil((percentile / 100) * sorted.length) - 1;
    return sorted[index];
  }
  
  reset() {
    this.metrics = {
      requests_total: 0,
      requests_success: 0,
      requests_error: 0,
      cache_hits: 0,
      cache_misses: 0,
      response_times: [],
      batch_sizes: [],
      active_connections: 0,
      queue_size: 0,
      endpoints: {},
      error_codes: {},
      hourly_stats: {}
    };
    
    this.startTime = Date.now();
    this.lastReport = Date.now();
  }
}

const metricsCollector = new MetricsCollector();

// Relatório periódico
setInterval(() => {
  const report = metricsCollector.getReport();
  console.log('📊 Relatório de métricas:', JSON.stringify(report, null, 2));
  
  // Enviar para sistema de monitoramento
  // sendToDataDog(report);
  // sendToPrometheus(report);
}, 60000); // A cada 1 minuto

2. Health Checks

class HealthChecker {
  constructor(client) {
    this.client = client;
    this.checks = new Map();
    this.lastResults = new Map();
    
    this.registerChecks();
  }
  
  registerChecks() {
    this.checks.set('api_connectivity', async () => {
      try {
        const start = Date.now();
        await this.client.buscarCNPJ(['21071712000171']);
        const responseTime = Date.now() - start;
        
        return {
          status: 'healthy',
          responseTime,
          message: 'API respondendo normalmente'
        };
      } catch (error) {
        return {
          status: 'unhealthy',
          error: error.message,
          message: 'Falha na conectividade com a API'
        };
      }
    });
    
    this.checks.set('cache_performance', async () => {
      const stats = cache.getStats();
      const hitRate = parseFloat(stats.hitRate.replace('%', ''));
      
      if (hitRate < 30) {
        return {
          status: 'degraded',
          hitRate: stats.hitRate,
          message: 'Taxa de cache hit baixa'
        };
      }
      
      return {
        status: 'healthy',
        hitRate: stats.hitRate,
        message: 'Cache funcionando adequadamente'
      };
    });
    
    this.checks.set('rate_limits', async () => {
      const metrics = metricsCollector.getReport();
      const rps = parseFloat(metrics.requests_per_second);
      
      if (rps > 90) { // Próximo do limite de 100/min
        return {
          status: 'warning',
          requestsPerSecond: rps,
          message: 'Próximo do limite de rate limit'
        };
      }
      
      return {
        status: 'healthy',
        requestsPerSecond: rps,
        message: 'Rate limits sob controle'
      };
    });
  }
  
  async runCheck(checkName) {
    const checkFn = this.checks.get(checkName);
    if (!checkFn) {
      throw new Error(`Health check '${checkName}' não encontrado`);
    }
    
    try {
      const result = await checkFn();
      this.lastResults.set(checkName, {
        ...result,
        timestamp: new Date().toISOString(),
        checkName
      });
      
      return this.lastResults.get(checkName);
    } catch (error) {
      const result = {
        status: 'error',
        error: error.message,
        timestamp: new Date().toISOString(),
        checkName
      };
      
      this.lastResults.set(checkName, result);
      return result;
    }
  }
  
  async runAllChecks() {
    const results = {};
    
    for (const checkName of this.checks.keys()) {
      results[checkName] = await this.runCheck(checkName);
    }
    
    const overallStatus = this.calculateOverallStatus(results);
    
    return {
      status: overallStatus,
      timestamp: new Date().toISOString(),
      checks: results
    };
  }
  
  calculateOverallStatus(results) {
    const statuses = Object.values(results).map(r => r.status);
    
    if (statuses.includes('unhealthy') || statuses.includes('error')) {
      return 'unhealthy';
    } else if (statuses.includes('degraded') || statuses.includes('warning')) {
      return 'degraded';
    } else {
      return 'healthy';
    }
  }
  
  getLastResults() {
    return Object.fromEntries(this.lastResults);
  }
}

const healthChecker = new HealthChecker(client);

// Health check periódico
setInterval(async () => {
  const health = await healthChecker.runAllChecks();
  
  if (health.status !== 'healthy') {
    console.warn('⚠️ Health check falhou:', health);
    
    // Enviar alerta
    // sendAlert(health);
  }
}, 30000); // A cada 30 segundos

Otimização de Custos

1. Estratégia de Cache por Prioridade

class CostOptimizedCache extends IntelligentCache {
  constructor(options = {}) {
    super(options);
    
    // Definir custos por operação (em créditos)
    this.operationCosts = {
      'cnpj': 1,
      'validation': 1,
      'leads': 5
    };
    
    // TTLs otimizados para economizar créditos
    this.ttlByType = {
      'cnpj': 7 * 24 * 60 * 60 * 1000, // 7 dias - dados mudam pouco
      'validation': 24 * 60 * 60 * 1000, // 24 horas - validações são estáveis
      'leads': 6 * 60 * 60 * 1000 // 6 horas - leads podem mudar
    };
    
    this.creditsSaved = 0;
  }
  
  get(type, identifier) {
    const result = super.get(type, identifier);
    
    if (result) {
      // Calcular créditos economizados
      const cost = this.operationCosts[type] || 1;
      this.creditsSaved += cost;
      
      console.debug(`💰 Créditos economizados: ${cost} (total: ${this.creditsSaved})`);
    }
    
    return result;
  }
  
  getCostReport() {
    const stats = this.getStats();
    const avgCostPerOperation = 1.5; // Custo médio estimado
    
    return {
      ...stats,
      creditsEstimated: this.creditsSaved,
      costSavingsEstimated: this.creditsSaved * 0.05, // R$ 0,05 por crédito
      hitRateImpact: `${((this.creditsSaved / (stats.totalRequests * avgCostPerOperation)) * 100).toFixed(1)}% de economia`
    };
  }
}

const costOptimizedCache = new CostOptimizedCache({
  maxSize: 10000,
  defaultTTL: 24 * 60 * 60 * 1000 // 24 horas padrão
});

2. Rate Limiting Inteligente

class IntelligentRateLimiter {
  constructor(options = {}) {
    this.limits = {
      peak_hours: { // 9h-18h
        requests_per_minute: 80, // 80% do limite para dar margem
        concurrent_requests: 5
      },
      off_hours: { // Demais horários
        requests_per_minute: 100,
        concurrent_requests: 8
      }
    };
    
    this.queue = [];
    this.activeRequests = 0;
    this.requestHistory = [];
  }
  
  isPeakHour() {
    const hour = new Date().getHours();
    return hour >= 9 && hour <= 18;
  }
  
  getCurrentLimits() {
    return this.isPeakHour() ? this.limits.peak_hours : this.limits.off_hours;
  }
  
  async acquire() {
    return new Promise((resolve, reject) => {
      this.queue.push({ resolve, reject, timestamp: Date.now() });
      this.processQueue();
    });
  }
  
  release() {
    this.activeRequests--;
    this.processQueue();
  }
  
  processQueue() {
    const limits = this.getCurrentLimits();
    
    // Verificar limites de concorrência
    if (this.activeRequests >= limits.concurrent_requests) {
      return;
    }
    
    // Verificar rate limit por minuto
    const now = Date.now();
    const oneMinuteAgo = now - 60000;
    
    this.requestHistory = this.requestHistory.filter(time => time > oneMinuteAgo);
    
    if (this.requestHistory.length >= limits.requests_per_minute) {
      // Reagendar para quando o limite resetar
      const oldestRequest = Math.min(...this.requestHistory);
      const waitTime = 60000 - (now - oldestRequest);
      
      setTimeout(() => this.processQueue(), waitTime);
      return;
    }
    
    // Processar próxima requisição na fila
    if (this.queue.length > 0) {
      const { resolve } = this.queue.shift();
      
      this.activeRequests++;
      this.requestHistory.push(now);
      
      resolve();
      
      // Continuar processando se houver mais na fila
      if (this.queue.length > 0) {
        setImmediate(() => this.processQueue());
      }
    }
  }
  
  getStatus() {
    const limits = this.getCurrentLimits();
    
    return {
      activeRequests: this.activeRequests,
      queueSize: this.queue.length,
      currentLimits: limits,
      isPeakHour: this.isPeakHour(),
      requestsLastMinute: this.requestHistory.length
    };
  }
}

const rateLimiter = new IntelligentRateLimiter();

3. Otimização de Consultas

class QueryOptimizer {
  constructor() {
    this.queryPatterns = new Map();
    this.optimizationRules = [
      this.deduplicateRule,
      this.batchingRule,
      this.cachingRule,
      this.priorityRule
    ];
  }
  
  async optimizeQuery(operation, params) {
    let optimizedParams = { ...params };
    
    // Aplicar regras de otimização
    for (const rule of this.optimizationRules) {
      optimizedParams = await rule.call(this, operation, optimizedParams);
    }
    
    return optimizedParams;
  }
  
  deduplicateRule(operation, params) {
    if (operation === 'buscar_cnpj' && Array.isArray(params.cnpjs)) {
      // Remover duplicatas
      const uniqueCNPJs = [...new Set(params.cnpjs)];
      
      if (uniqueCNPJs.length !== params.cnpjs.length) {
        console.log(`🔧 Otimização: Removidas ${params.cnpjs.length - uniqueCNPJs.length} duplicatas`);
      }
      
      return { ...params, cnpjs: uniqueCNPJs };
    }
    
    return params;
  }
  
  batchingRule(operation, params) {
    if (operation === 'buscar_cnpj' && Array.isArray(params.cnpjs)) {
      // Otimizar tamanho do lote baseado no cache
      const uncachedCNPJs = params.cnpjs.filter(cnpj => !cache.has('cnpj', cnpj));
      
      if (uncachedCNPJs.length < params.cnpjs.length) {
        console.log(`🔧 Otimização: ${params.cnpjs.length - uncachedCNPJs.length} CNPJs encontrados em cache`);
        return { ...params, cnpjs: uncachedCNPJs };
      }
    }
    
    return params;
  }
  
  cachingRule(operation, params) {
    // Regra de cache já implementada em outras partes
    return params;
  }
  
  priorityRule(operation, params) {
    if (operation === 'validacao' && Array.isArray(params.data)) {
      // Priorizar validações mais importantes
      const prioritized = params.data.sort((a, b) => {
        const priorityA = this.getValidationPriority(a.type);
        const priorityB = this.getValidationPriority(b.type);
        return priorityB - priorityA;
      });
      
      return { ...params, data: prioritized };
    }
    
    return params;
  }
  
  getValidationPriority(type) {
    const priorities = { 'cnpj': 3, 'email': 2, 'phone': 1 };
    return priorities[type] || 0;
  }
}

const queryOptimizer = new QueryOptimizer();

Implementação em Produção

1. Cliente de Produção Completo

class ProductionSpeedioClient {
  constructor(options = {}) {
    // Validar configuração
    this.validateConfig(options);
    
    // Inicializar componentes
    this.credentialManager = new CredentialManager();
    this.cache = new CostOptimizedCache(options.cache);
    this.rateLimiter = new IntelligentRateLimiter(options.rateLimiting);
    this.retryManager = new RetryManager(options.retry);
    this.circuitBreaker = new CircuitBreaker(options.circuitBreaker);
    this.metricsCollector = new MetricsCollector();
    this.healthChecker = new HealthChecker(this);
    this.queryOptimizer = new QueryOptimizer();
    this.connectionPool = new ConnectionPool(options.connectionPool);
    
    // Configuração
    this.baseUrl = options.baseUrl || 'https://api-get-leads.speedio.com.br';
    this.defaultTimeout = options.timeout || 10000;
    
    this.startBackgroundTasks();
  }
  
  validateConfig(options) {
    if (!process.env.SPEEDIO_USERNAME || !process.env.SPEEDIO_PASSWORD) {
      throw new Error('Credenciais SPEEDIO_USERNAME e SPEEDIO_PASSWORD são obrigatórias');
    }
    
    if (process.env.NODE_ENV === 'production') {
      if (!options.monitoring?.endpoint) {
        console.warn('⚠️ Endpoint de monitoramento não configurado para produção');
      }
    }
  }
  
  async buscarCNPJ(cnpjs, options = {}) {
    // Otimizar query
    const optimizedParams = await this.queryOptimizer.optimizeQuery('buscar_cnpj', { cnpjs });
    
    if (optimizedParams.cnpjs.length === 0) {
      console.log('✅ Todos os CNPJs encontrados em cache');
      return cnpjs.map(cnpj => this.cache.get('cnpj', cnpj)).filter(Boolean);
    }
    
    return await this.executeRequest(
      'buscar_cnpj',
      async () => {
        await this.rateLimiter.acquire();
        
        try {
          const response = await axios.get(`${this.baseUrl}/search_enriched_leads/cnpj`, {
            params: { cnpjs: JSON.stringify(optimizedParams.cnpjs) },
            headers: await this.getHeaders(),
            timeout: options.timeout || this.defaultTimeout
          });
          
          // Cachear resultados
          response.data.forEach(empresa => {
            if (empresa && empresa.cnpj) {
              this.cache.set('cnpj', empresa.cnpj, empresa);
            }
          });
          
          return response.data;
        } finally {
          this.rateLimiter.release();
        }
      },
      { cnpjs: optimizedParams.cnpjs, originalCount: cnpjs.length }
    );
  }
  
  async executeRequest(operation, fn, context = {}) {
    const startTime = Date.now();
    
    try {
      const result = await this.circuitBreaker.execute(
        () => this.retryManager.executeWithRetry(fn)
      );
      
      const responseTime = Date.now() - startTime;
      this.metricsCollector.recordRequest(operation, true, responseTime, 200, context.originalCount || 1);
      
      return result;
    } catch (error) {
      const responseTime = Date.now() - startTime;
      this.metricsCollector.recordRequest(operation, false, responseTime, error.status || 0, context.originalCount || 1);
      
      throw error;
    }
  }
  
  async getHeaders() {
    const credentials = await this.credentialManager.getCredentials();
    const auth = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64');
    
    return {
      'Authorization': `Basic ${auth}`,
      'Content-Type': 'application/json',
      'User-Agent': 'Speedio-Client-Production/1.0',
      'X-Client-Version': process.env.npm_package_version || '1.0.0'
    };
  }
  
  startBackgroundTasks() {
    // Health checks
    setInterval(async () => {
      const health = await this.healthChecker.runAllChecks();
      
      if (health.status !== 'healthy') {
        console.warn('⚠️ Health check:', health);
        this.sendAlert('health_check_failed', health);
      }
    }, 60000);
    
    // Relatórios de métricas
    setInterval(() => {
      const metrics = this.metricsCollector.getReport();
      const cacheReport = this.cache.getCostReport();
      
      console.log('📊 Métricas:', metrics);
      console.log('💰 Cache:', cacheReport);
      
      this.sendMetrics(metrics, cacheReport);
    }, 300000); // A cada 5 minutos
    
    // Limpeza de cache
    setInterval(() => {
      const sizeBefore = this.cache.getStats().size;
      // Implementar limpeza se necessário
      console.log(`🧹 Cache cleanup: ${sizeBefore} itens`);
    }, 3600000); // A cada 1 hora
  }
  
  sendAlert(type, data) {
    // Implementar envio de alertas (Slack, PagerDuty, etc.)
    console.error(`🚨 ALERTA [${type}]:`, data);
  }
  
  sendMetrics(metrics, cacheReport) {
    // Implementar envio para sistema de monitoramento
    console.log('📈 Enviando métricas para sistema de monitoramento');
  }
  
  getStatus() {
    return {
      client: 'ProductionSpeedioClient',
      version: '1.0.0',
      uptime: process.uptime(),
      memory: process.memoryUsage(),
      circuitBreaker: this.circuitBreaker.getStatus(),
      rateLimiter: this.rateLimiter.getStatus(),
      cache: this.cache.getCostReport(),
      health: this.healthChecker.getLastResults(),
      metrics: this.metricsCollector.getReport(),
      timestamp: new Date().toISOString()
    };
  }
  
  async shutdown() {
    console.log('🔄 Iniciando shutdown graceful...');
    
    // Aguardar requisições ativas terminarem
    let attempts = 0;
    while (this.rateLimiter.getStatus().activeRequests > 0 && attempts < 30) {
      console.log(`⏳ Aguardando ${this.rateLimiter.getStatus().activeRequests} requisições terminarem...`);
      await new Promise(resolve => setTimeout(resolve, 1000));
      attempts++;
    }
    
    // Relatório final
    const finalReport = this.getStatus();
    console.log('📊 Relatório final:', finalReport);
    
    console.log('✅ Shutdown concluído');
  }
}

// Configuração para produção
const prodClient = new ProductionSpeedioClient({
  cache: {
    maxSize: 50000,
    defaultTTL: 24 * 60 * 60 * 1000
  },
  rateLimiting: {
    peak_hours: { requests_per_minute: 80, concurrent_requests: 5 },
    off_hours: { requests_per_minute: 100, concurrent_requests: 8 }
  },
  retry: {
    maxRetries: 3,
    baseDelay: 1000,
    maxDelay: 30000
  },
  circuitBreaker: {
    failureThreshold: 5,
    recoveryTimeout: 60000
  },
  connectionPool: {
    maxConnections: 15,
    timeout: 10000,
    keepAlive: true
  },
  monitoring: {
    endpoint: process.env.MONITORING_ENDPOINT
  }
});

// Graceful shutdown
process.on('SIGTERM', async () => {
  await prodClient.shutdown();
  process.exit(0);
});

process.on('SIGINT', async () => {
  await prodClient.shutdown();
  process.exit(0);
});

export default prodClient;

Próximos Passos

Dica de Produção: Implemente sempre monitoramento robusto, cache inteligente e tratamento de erros em produção. A API Speedio é construída para alta disponibilidade, mas sua aplicação deve estar preparada para cenários de degradação.