testes/Modulos Angular/projects/idt_app/docs/components/COMPUTE_SYSTEM_DOCUMENTATIO...

15 KiB
Raw Blame History

🚀 Sistema Compute - Campos Calculados Automáticos

📚 Índice

  1. Visão Geral
  2. Arquitetura
  3. Implementação
  4. Como Usar
  5. Exemplos Práticos
  6. Configuração Avançada
  7. Troubleshooting
  8. Melhorias Futuras

🎯 Visão Geral

O sistema compute é uma solução dinâmica e reutilizável para campos calculados automaticamente em formulários Angular. Ele permite que campos sejam calculados em tempo real baseado em outros campos do formulário, sem necessidade de código específico por domínio.

Características Principais

  • 100% Reutilizável para qualquer domínio
  • Detecção automática de dependências
  • Recálculo em tempo real quando campos relacionados mudam
  • Configuração declarativa por campo
  • Performance otimizada com listeners inteligentes
  • Tratamento de erros robusto

🏗️ Arquitetura

📁 Estrutura de Arquivos

projects/idt_app/src/app/shared/
├── interfaces/
│   └── generic-tab-form.interface.ts    # Interface TabFormField estendida
└── components/
    └── generic-tab-form/
        └── generic-tab-form.component.ts # Lógica de campos computados

🔧 Componentes do Sistema

  1. Interface Estendida: TabFormField com propriedades compute e computeDependencies
  2. Detector de Dependências: Analisa funções compute automaticamente
  3. Listener Dinâmico: Escuta mudanças apenas nos campos relevantes
  4. Calculador: Executa funções compute e atualiza valores
  5. Gerenciador de Estado: Preserva valores calculados durante edição

🚀 Implementação

1. Interface TabFormField Estendida

// projects/idt_app/src/app/shared/interfaces/generic-tab-form.interface.ts

export interface TabFormField {
  // ... propriedades existentes ...
  
  // 🎯 NOVA: Propriedade para campos calculados
  compute?: (model: any) => any;
  
  // 🎯 NOVA: Dependências para campos computados
  computeDependencies?: string[];
}

Propriedades:

  • compute: Função que calcula o valor do campo
  • computeDependencies: Array de nomes dos campos que afetam o cálculo

2. Sistema de Detecção Automática

// projects/idt_app/src/app/shared/components/generic-tab-form/generic-tab-form.component.ts

private getAllComputedFieldDependencies(): string[] {
  const allFields = this.getAllFieldsFromSubTabs();
  const dependencies = new Set<string>();
  
  allFields.forEach(field => {
    if (field.compute) {
      // 🎯 Dependências explícitas (declaradas pelo desenvolvedor)
      if (field.computeDependencies) {
        field.computeDependencies.forEach(dep => dependencies.add(dep));
      }
      
      // 🚀 DETECÇÃO AUTOMÁTICA: Analisar a função compute
      const autoDependencies = this.detectDependenciesFromComputeFunction(field);
      autoDependencies.forEach(dep => dependencies.add(dep));
    }
  });
  
  return Array.from(dependencies);
}

3. Detecção Inteligente de Dependências

private detectDependenciesFromComputeFunction(field: TabFormField): string[] {
  if (!field.compute) return [];
  
  try {
    const functionString = field.compute.toString();
    const dependencies: string[] = [];
    
    // 🎯 Padrões comuns de acesso a propriedades
    const patterns = [
      /model\.(\w+)/g,           // model.propertyName
      /model\?\.(\w+)/g,         // model?.propertyName
      /model\[['"`](\w+)['"`]\]/g, // model['propertyName']
      /model\[`(\w+)`\]/g        // model[`propertyName`]
    ];
    
    patterns.forEach(pattern => {
      let match;
      while ((match = pattern.exec(functionString)) !== null) {
        const propertyName = match[1];
        if (propertyName && !dependencies.includes(propertyName)) {
          dependencies.push(propertyName);
        }
      }
    });
    
    return dependencies;
  } catch (error) {
    console.warn(`⚠️ Erro ao detectar dependências automáticas para ${field.key}:`, error);
    return [];
  }
}

4. Listener Dinâmico

private setupComputedFieldsListener(): void {
  if (!this.form) return;
  
  // 🚀 DINÂMICO: Detectar automaticamente todas as dependências
  const allDependencies = this.getAllComputedFieldDependencies();
  
  if (allDependencies.length === 0) {
    console.log(' [COMPUTE] Nenhum campo computado encontrado');
    return;
  }
  
  console.log('🎯 [COMPUTE] Dependências detectadas:', allDependencies);
  
  // Escutar mudanças em TODOS os campos que afetam cálculos
  this.form.valueChanges.subscribe(value => {
    const hasRelevantChange = allDependencies.some(field => 
      value[field] !== undefined && value[field] !== null
    );
    
    if (hasRelevantChange) {
      this.updateComputedFields();
    }
  });
}

5. Atualização de Campos Computados

private updateComputedFields(): void {
  if (!this.form) return;
  
  const formValue = this.form.value;
  
  this.getAllFieldsFromSubTabs().forEach(field => {
    if (field.compute) {
      try {
        const computedValue = field.compute(formValue);
        if (computedValue !== undefined && computedValue !== null) {
          this.form.get(field.key)?.setValue(computedValue, { emitEvent: false });
        }
      } catch (error) {
        console.warn(`⚠️ Erro ao recalcular campo ${field.key}:`, error);
      }
    }
  });
}

📝 Como Usar

1. Configuração Básica de Campo Computado

{
  key: 'campo_calculado',
  label: 'Campo Calculado',
  type: 'number',
  readOnly: true,
  compute: (model: any) => {
    // Lógica de cálculo aqui
    return resultado;
  },
  computeDependencies: ['campo1', 'campo2']
}

2. Tipos de Campo Suportados

// ✅ Tipos que funcionam bem com compute
type: 'number'           // Para valores numéricos
type: 'currency-input'   // Para valores monetários
type: 'text'             // Para texto calculado
type: 'date'             // Para datas calculadas

3. Estrutura da Função Compute

compute: (model: any) => {
  // 🎯 model contém todos os valores do formulário
  
  // 🎯 Acessar campos específicos
  const valor1 = Number(model?.campo1) || 0;
  const valor2 = Number(model?.campo2) || 0;
  
  // 🎯 Lógica de cálculo
  const resultado = valor1 + valor2;
  
  // 🎯 Retornar o valor calculado
  return resultado;
}

💡 Exemplos Práticos

1. Cálculo Financeiro (Veículos)

{
  key: 'calc_total_pago',
  label: 'Valor Total Pago',
  type: 'currency-input',
  readOnly: true,
  compute: (model: any) => {
    const parcela = Number(model?.alienation_payment_installment_value) || 0;
    const pagas = Number(model?.alienation_payment_installment_total) || 0;
    return parcela * pagas;
  },
  computeDependencies: ['alienation_payment_installment_value', 'alienation_payment_installment_total'],
  formatOptions: { locale: 'pt-BR', useGrouping: true, suffix: ' R$' }
}

2. Cálculo de Idade (Motoristas)

{
  key: 'idade_calculada',
  label: 'Idade',
  type: 'number',
  readOnly: true,
  compute: (model: any) => {
    if (!model?.data_nascimento) return null;
    
    const nascimento = new Date(model.data_nascimento);
    const hoje = new Date();
    const idade = hoje.getFullYear() - nascimento.getFullYear();
    
    // Ajustar para mês/dia
    const mesAtual = hoje.getMonth();
    const mesNascimento = nascimento.getMonth();
    
    if (mesAtual < mesNascimento || 
        (mesAtual === mesNascimento && hoje.getDate() < nascimento.getDate())) {
      return idade - 1;
    }
    
    return idade;
  },
  computeDependencies: ['data_nascimento']
}

3. Formatação de CNPJ (Empresas)

{
  key: 'cnpj_formatado',
  label: 'CNPJ Formatado',
  type: 'text',
  readOnly: true,
  compute: (model: any) => {
    const cnpj = model?.cnpj;
    if (!cnpj) return '';
    
    // Remover caracteres não numéricos
    const cleanCnpj = cnpj.replace(/\D/g, '');
    
    if (cleanCnpj.length !== 14) return cnpj;
    
    // Formatar: XX.XXX.XXX/XXXX-XX
    return cleanCnpj.replace(
      /(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/,
      '$1.$2.$3/$4-$5'
    );
  },
  computeDependencies: ['cnpj']
}

4. Cálculo de Distância (Rotas)

{
  key: 'distancia_total',
  label: 'Distância Total',
  type: 'number',
  readOnly: true,
  compute: (model: any) => {
    const paradas = model?.stops || [];
    
    if (paradas.length < 2) return 0;
    
    let distanciaTotal = 0;
    
    for (let i = 1; i < paradas.length; i++) {
      const anterior = paradas[i - 1];
      const atual = paradas[i];
      
      const distancia = this.calcularDistancia(
        anterior.latitude, anterior.longitude,
        atual.latitude, atual.longitude
      );
      
      distanciaTotal += distancia;
    }
    
    return Math.round(distanciaTotal * 100) / 100; // 2 casas decimais
  },
  computeDependencies: ['stops']
}

⚙️ Configuração Avançada

1. Dependências Múltiplas

{
  key: 'campo_complexo',
  compute: (model: any) => {
    // Campo depende de vários outros campos
    const valor1 = model?.campo1 || 0;
    const valor2 = model?.campo2 || 0;
    const valor3 = model?.campo3 || 0;
    
    return (valor1 + valor2) * valor3;
  },
  // 🎯 Declarar TODAS as dependências
  computeDependencies: ['campo1', 'campo2', 'campo3']
}

2. Validação Condicional

{
  key: 'campo_validado',
  compute: (model: any) => {
    const valor = model?.campo_base;
    
    // 🎯 Validação antes do cálculo
    if (!valor || valor <= 0) {
      return null; // Retornar null para indicar valor inválido
    }
    
    return valor * 2;
  },
  computeDependencies: ['campo_base']
}

3. Cálculos com Formatação

{
  key: 'valor_formatado',
  type: 'text',
  readOnly: true,
  compute: (model: any) => {
    const valor = Number(model?.valor_base) || 0;
    
    if (valor === 0) return 'R$ 0,00';
    
    // 🎯 Formatação brasileira
    return valor.toLocaleString('pt-BR', {
      style: 'currency',
      currency: 'BRL',
      minimumFractionDigits: 2
    });
  },
  computeDependencies: ['valor_base']
}

🔧 Troubleshooting

Problemas Comuns e Soluções

1. Campo não está sendo recalculado

Sintomas:

  • Campo computado não atualiza quando dependências mudam
  • Valor inicial não é calculado

Soluções:

// ✅ Verificar se computeDependencies está correto
computeDependencies: ['campo1', 'campo2']

// ✅ Verificar se a função compute retorna valor válido
compute: (model: any) => {
  const valor = model?.campo1 || 0;
  return valor > 0 ? valor * 2 : 0; // Sempre retornar valor válido
}

2. Erro "Cannot read property of undefined"

Sintomas:

  • Erro no console ao tentar calcular campo
  • Campo fica vazio

Soluções:

// ✅ Usar operador de coalescência nula
compute: (model: any) => {
  const valor = model?.campo1 ?? 0; // Usar ?? em vez de ||
  return valor * 2;
}

// ✅ Verificar se campos existem antes de usar
compute: (model: any) => {
  if (!model?.campo1 || !model?.campo2) return 0;
  return model.campo1 + model.campo2;
}

3. Performance lenta com muitos campos computados

Sintomas:

  • Formulário lento ao digitar
  • Muitos recálculos desnecessários

Soluções:

// ✅ Limitar dependências apenas aos campos essenciais
computeDependencies: ['campo_essencial'] // Não incluir campos opcionais

// ✅ Usar debounce para campos que mudam frequentemente
// (Implementar no futuro se necessário)

🔍 Debug e Logs

// ✅ Adicionar logs para debug
compute: (model: any) => {
  console.log('🔍 [DEBUG] Modelo recebido:', model);
  
  const valor1 = Number(model?.campo1) || 0;
  const valor2 = Number(model?.campo2) || 0;
  
  console.log('🔍 [DEBUG] Valores:', { valor1, valor2 });
  
  const resultado = valor1 + valor2;
  console.log('🔍 [DEBUG] Resultado:', resultado);
  
  return resultado;
}

🚀 Melhorias Futuras

1. Sistema de Cache Inteligente

// 🎯 Cache configurável por campo
computeCache?: {
  enabled?: boolean;
  ttl?: number; // Time to live em ms
  strategy?: 'memory' | 'localStorage';
}

2. Debounce para Campos Frequentes

// 🎯 Debounce para campos que mudam muito
computeDebounce?: {
  enabled?: boolean;
  delay?: number; // ms
}

3. Validação de Dependências

// 🎯 Validação automática de dependências
computeValidation?: {
  required?: boolean;
  minValue?: number;
  maxValue?: number;
  pattern?: RegExp;
}

4. Métricas de Performance

// 🎯 Monitoramento de performance
computeMetrics?: {
  trackExecutionTime?: boolean;
  trackDependencyChanges?: boolean;
  logPerformance?: boolean;
}

Checklist de Implementação

Para Desenvolvedores

  • Campo tem propriedade compute definida
  • Campo tem computeDependencies declaradas
  • Campo é readOnly: true
  • Função compute retorna valor válido
  • Dependências estão corretas
  • Tratamento de erros implementado
  • Testado com valores vazios/nulos
  • Performance aceitável

Para Testes

  • Campo é calculado ao carregar formulário
  • Campo é recalculado quando dependências mudam
  • Campo não é recalculado desnecessariamente
  • Tratamento de erros funciona
  • Performance é aceitável
  • Funciona em diferentes domínios

🎯 Conclusão

O sistema compute para campos calculados é uma solução robusta, escalável e reutilizável que elimina a necessidade de código específico por domínio para cálculos automáticos.

🌟 Benefícios

  • Produtividade: Desenvolvimento mais rápido
  • Manutenibilidade: Código centralizado e limpo
  • Reutilização: Funciona em qualquer domínio
  • Performance: Otimizado e eficiente
  • Flexibilidade: Configurável por campo

🎯 Próximos Passos

  1. Implementar em novos domínios
  2. Adicionar testes automatizados
  3. Monitorar performance em produção
  4. Coletar feedback dos usuários
  5. Implementar melhorias baseadas em uso real

📚 Referências

Arquivos do Sistema

  • Interface: projects/idt_app/src/app/shared/interfaces/generic-tab-form.interface.ts
  • Componente: projects/idt_app/src/app/shared/components/generic-tab-form/generic-tab-form.component.ts
  • Exemplo de Uso: projects/idt_app/src/app/domain/vehicles/vehicles.component.ts

Padrões Utilizados

  • Registry Pattern: Para configuração de formulários
  • Observer Pattern: Para listeners de mudanças
  • Strategy Pattern: Para diferentes tipos de cálculo
  • Factory Pattern: Para criação de campos computados

Autor: PraFrota Development Team
Data: $(date)
Versão: 2.0.0 - Padronização Completa