Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.speedio.com.br/llms.txt

Use this file to discover all available pages before exploring further.

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

Documentação de Endpoints

Explore todos os endpoints disponíveis

Tratamento de Erros

Implemente tratamento robusto de erros

Suporte

Entre em contato para dúvidas ou suporte

Dashboard

Monitore seu uso no dashboard
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.