Como estruturar métodos de validação em JavaScript puro, escapar da pegadinha dos argumentos posicionais e orquestrar decisões e modais nativos sem corromper o SQLite local.

Se você já passou uma tarde inteira tentando descobrir por que um pedido com quantidade zerada foi gravado com sucesso no dispositivo móvel de um promotor e só explodiu horas depois na sincronização noturna da Salesforce com um erro enigmático de banco, você conhece o peso do paradigma Offline-First.

No Salesforce Consumer Goods Cloud (CGC) com a engine Modeler (Mv2), o aplicativo móvel não tem o luxo de consultar triggers em nuvem em tempo real a cada clique do usuário no supermercado. Tudo — absolutamente tudo — nasce, é calculado e persiste fisicamente no banco relacional SQLite local (app.db3) do aparelho.

Se a sua validação falhar no tablet, o dado podre entra na base local. O promotor encerra a visita crente de que cumpriu a meta. À noite, no Upload Sync, o servidor corporativo rejeita o lote. O resultado? Filas de sincronização travadas, chamados urgentes na sustentação e promotores em campo com dados divergentes.

Nós testamos essa esteira de ponta a ponta no nosso ambiente local de desenvolvimento (projeto_modeler), compilamos os contratos com a CLI do Modeler e dissecamos as pegadinhas mais cruéis que derrubam até arquitetos experientes.


1. O Princípio de Separação: Onde Cada Coisa Deve Morar

O Modeler divide a aplicação em camadas muito bem demarcadas. Misturar essas responsabilidades é a receita perfeita para ter dor de cabeça:

  • Interface Visual (.userinterface.xml): Cuida estritamente de renderização, labels e bindings de tela. Não invente de colocar validações lógicas pesadas nos bindings de UI. Máscara de texto não é barreira de segurança.
  • ProcessFlow (.processflow.xml): É a máquina de estados finitos do aplicativo. Seu trabalho é conduzir o fluxo de telas, controlar transações de banco (commit="true") e tomar decisões booleanas simples (sim ou não). O ProcessFlow não deve fazer contas ou parsear JSON complexo.
  • Business Logic (.bl.js): É o cérebro da regra de negócio. Aqui roda o JavaScript puro do framework móvel. É onde você lê as listas de itens (LoOrderItems), confere limites comerciais, formata diagnósticos e devolve um veredito estruturado para o fluxo.

2. A Armadilha Número 1: Argumentos Posicionais vs o Mito do MethodInput

Aqui está a armadilha clássica que vemos com mais frequência em fóruns técnicos e auditorias de código: como os parâmetros declarados no XML chegam à função JavaScript?

No .processflow.xml, quando você invoca uma ação do tipo LOGIC, você declara os parâmetros de entrada dentro de <Parameters>:

<!-- No ProcessFlow -->
<Action name="Order_ValidateQuantities" actionType="LOGIC"
        call="ProcessContext::CurrentOrder.myValidateOrderForSubmission">
  <Parameters>
    <Input name="orderItemsList" value="ProcessContext::OrderItemsList" />
    <Input name="minimumOrderValue" value="ProcessContext::CustomerMinimumThreshold" />
  </Parameters>
  <Return name="ProcessContext::ValidationResult" />
</Action>

O Erro Fatal (Objeto Empacotado Inexistente)

Quem vem do ecossistema React, LWC ou Node.js geralmente assume que o compilador do Modeler empacota essas propriedades em um objeto único de argumentos:

// ❌ ANTI-PATTERN: O objeto MethodInput NÃO EXISTE no runtime Mv2!
function myValidateOrderForSubmission(MethodInput) {
    // MethodInput.minimumOrderValue será SEMPRE undefined!
    var threshold = MethodInput.minimumOrderValue; 
    // A validação passa direto em branco sem acusar erro de sintaxe!
}

O método roda em 2 milissegundos, não quebra a execução, mas lê undefined. Resultado: a validação é solenemente ignorada e o pedido inválido vai direto para o disco.

A Forma Correta (Argumentos Posicionais)

No motor Mv2 do CGC Modeler, cada tag <Input> é injetada na função JavaScript como um argumento posicional individual, exatamente na mesma ordem em que foi declarada no XML:

// ✅ CORRETO: Os argumentos chegam de forma posicional direta
function myValidateOrderForSubmission(orderItemsList, minimumOrderValue) {
    // orderItemsList recebe o ListObject do primeiro <Input>
    // minimumOrderValue recebe o valor decimal do segundo <Input>
    AppLog.info("Validação executada com limiar: " + minimumOrderValue);
}

3. A Regra do Compilador: O Prefixo Obrigatório 'My' (Código 03112666)

Ao validar o contrato no terminal com a CLI oficial do Modeler (sf modeler workspace validate), nos deparamos com uma diretriz estrita do compilador:

Erro 03112666: The file name must include ‘My’ for a custom businesslogicjs contract. For example, BoMyBusinessObject.afterCreateAsync.bl.js, BoBpaMeta.MyAddValue.bl.js.

Se você tentar nomear o arquivo customizado de um Business Object padrão (como BoOrder) sem o prefixo ou elemento 'My', o build falha imediatamente. Portanto, para estender a regra no BoOrder, o método e o arquivo devem se chamar:

  • Arquivo: BoOrder.MyValidateOrderForSubmission.bl.js
  • Contrato no XML: <Method name="myValidateOrderForSubmission"/>

O Contrato no BoOrder.businessobject.xml

<BusinessObject name="BoOrder" schemaVersion="2.0">
  <Methods>
    <!-- Outros métodos omitidos por brevidade -->
    <Method name="myValidateOrderForSubmission" />
  </Methods>
  <Validations>
    <Validation name="validateOrderValue" />
    <Validation name="validateDeliveryDate" />
  </Validations>
</BusinessObject>

A Lógica Pura no BoOrder.MyValidateOrderForSubmission.bl.js

Implementamos e validamos este método dentro de projeto_modeler/src/Order/BO/BoOrder/Mv1/:

"use strict";

/**
 * Data: 2026-10-06
 * Objetivo da Customização: Validação de regras de negócio de pedido de venda com argumentos posicionais e diagnóstico estruturado para o ProcessFlow
 * @function myValidateOrderForSubmission
 * @this BoOrder
 * @kind TODO_ADD_BUSINESS_OBJECT_TYPE
 * @namespace CUSTOM
 * @description Executa a auditoria de regras de negócio antes do fechamento do pedido.
 * @param {LoOrderItems} orderItemsList Coleção de itens adicionados ao pedido.
 * @param {DomDecimal} minimumOrderValue Valor mínimo exigido em reais.
 * @returns {Object} result Objeto contendo status de validação e identificadores de erro.
 */
function myValidateOrderForSubmission(orderItemsList, minimumOrderValue) {
    var me = this;

    /* 
     * Data: 2026-10-06
     * Objetivo da Customização: Validação de regras de negócio de pedido de venda com argumentos posicionais e diagnóstico estruturado para o ProcessFlow
     */
    var result = {
        isValid: true,
        errorCode: "",
        errorMessageId: "",
        errorParameter: ""
    };

    // 1. O pedido possui produtos selecionados?
    if (!orderItemsList || (typeof orderItemsList.getAllItems === "function" && orderItemsList.getAllItems().length === 0)) {
        result.isValid = false;
        result.errorCode = "EMPTY_ORDER";
        result.errorMessageId = "OrderError_NoItemsSelected";
        return result;
    }

    var items = typeof orderItemsList.getAllItems === "function" ? orderItemsList.getAllItems() : [];
    var calculatedTotal = 0.0;

    // 2. Todos os itens possuem quantidade válida e maior que zero?
    for (var i = 0; i < items.length; i++) {
        var item = items[i];
        var quantity = typeof item.getQuantity === "function" ? item.getQuantity() : 0;

        if (quantity <= 0) {
            result.isValid = false;
            result.errorCode = "INVALID_QUANTITY";
            result.errorMessageId = "OrderError_ItemQuantityZero";
            result.errorParameter = typeof item.getProductName === "function" ? item.getProductName() : ("Item " + (i + 1));
            return result;
        }

        var price = typeof item.getUnitPrice === "function" ? item.getUnitPrice() : 0.0;
        calculatedTotal += (quantity * price);
    }

    // 3. Faturamento mínimo exigido pelo cliente
    var requiredThreshold = Number(minimumOrderValue) || 0.0;
    if (calculatedTotal < requiredThreshold) {
        result.isValid = false;
        result.errorCode = "MINIMUM_ORDER_NOT_MET";
        result.errorMessageId = "OrderError_ThresholdViolated";
        result.errorParameter = String(requiredThreshold.toFixed(2));
        return result;
    }

    AppLog.info("Validação concluída com sucesso. Total aprovado: R$ " + calculatedTotal);
    return result;
}

4. Orquestrando o Fluxo com <Cases> no ProcessFlow

Com o veredito da Business Logic em mãos, o arquivo de processo assume a batuta. Ele não tenta recalcular valores; ele apenas lê a propriedade booleana isValid e desvia os trilhos da máquina de estados:

<!-- 1. Executa a validação na camada de lógica de negócios -->
<Action name="Order_ExecuteValidation" actionType="LOGIC"
        call="ProcessContext::CurrentOrder.myValidateOrderForSubmission">
  <Parameters>
    <Input name="orderItemsList" value="ProcessContext::OrderItemsList" />
    <Input name="minimumOrderValue" value="ProcessContext::CustomerMinimumThreshold" />
  </Parameters>
  <Return name="ProcessContext::ValidationResult" />
  <TransitionTo action="Order_EvaluateValidationResult" />
</Action>

<!-- 2. Ramificação Condicional: Avalia o resultado -->
<Action name="Order_EvaluateValidationResult" actionType="DECISION"
        parameter="ProcessContext::ValidationResult.isValid">
  <Cases>
    <!-- Válido: Segue para gravação física com commit="true" -->
    <Case value="true" action="Order_CommitAndSave" />
    
    <!-- Inválido: Desvia para diálogo de erro sem tocar no banco -->
    <Case value="false" action="Order_ShowValidationErrorDialog" />
  </Cases>
</Action>

<!-- 3. Ramo de Erro: Dispara o MessageBoxProcess nativo -->
<Action name="Order_ShowValidationErrorDialog" actionType="PROCESS"
        process="Core::MessageBoxProcess">
  <Parameters>
    <Input name="messageId" value="ProcessContext::ValidationResult.errorMessageId" />
    <Input name="param1" value="ProcessContext::ValidationResult.errorParameter" />
  </Parameters>
  <TransitionTo action="Order_StayInEditView" />
</Action>

<!-- 4. Ramo de Sucesso: Transação efetivada no SQLite -->
<Action name="Order_CommitAndSave" actionType="PROCESS" 
        process="Order::OrderSaveProcess">
  <TransitionTo action="Order_CloseAndExit" commit="true" />
</Action>

Observe o detalhe crucial: o atributo commit="true" existe exclusivamente na transição de saída do Order_CommitAndSave. Se a validação apontar false, o fluxo é desviado para o Core::MessageBoxProcess, mantendo a transação do SQLite intocada.


5. Textos Dinâmicos e Internacionalização no .locale.xml

Ninguém merece ver telas móveis com mensagens em inglês técnico ou códigos de erro incompreensíveis. O identificador passado em errorMessageId busca as strings no dicionário de idioma (br.locale.xml), aceitando interpolação posicional de parâmetros {0}:

<Locale language="br" languageCode="pt">
  <Texts>
    <Text id="OrderError_NoItemsSelected" 
          value="Atenção: Adicione ao menos um item para concluir o pedido." />
          
    <Text id="OrderError_ItemQuantityZero" 
          value="Inconsistência no item {0}: A quantidade solicitada deve ser maior que zero." />
          
    <Text id="OrderError_ThresholdViolated" 
          value="O pedido não atingiu o faturamento mínimo da loja (R$ {0}). Adicione mais produtos." />
  </Texts>
</Locale>

Quando o MessageBoxProcess recebe o parâmetro em param1, ele substitui automaticamente o {0}, entregando ao promotor um alerta preciso e educativo.


6. Evidências Visuais e Mapeamento de Onde Coletar Prints

Para auditar e documentar a solução com comprovação real de ambiente, mapeamos os locais exatos onde coletar as evidências no ecossistema do CG Cloud:

A. Terminal de Validação e Compilação da CLI do Modeler

No terminal do projeto local (projeto_modeler), executamos a validação e o empacotamento completo do workspace:

sf modeler workspace validate
sf modeler workspace build

Ambos os comandos retornaram com status Validation successful e Build successful (código de saída 0), validando formalmente os schemas XSD e os arquivos .bl.js.

B. Mapeamento para o Desenvolvedor em Campo (Onde Coletar Prints Adicionais)

Se você estiver homologando no simulador móvel ou dispositivo físico:

  1. Simulador Mobile / CG Cloud App: Acesse a rota de criação de pedido (Order Entry Wizard), adicione um produto com quantidade 0 ou deixe o valor abaixo do limiar mínimo e clique em “Concluir”. Capture o modal nativo exibindo a mensagem localizada.
  2. Servidor de Logs do Modeler (http://localhost:3000/logs): Com o visualizador ativo monitorando appl/data/log/app.log, observe o disparo da linha AppLog.info("Validação concluída com sucesso...") e o tempo de execução milimétrico da chamada.
  3. Contratos no VS Code: Captura de tela dos arquivos BoOrder.MyValidateOrderForSubmission.bl.js e do trecho de <Cases> no .processflow.xml.

7. Comparativo Arquitetural: Onde Posicionar as Regras de Integridade

Camada da SoluçãoMecanismo no ModelerCasos de Uso RecomendadosRisco Arquitetural se Mal Posicionado
Interface Visual (.userinterface.xml)Máscaras de input (pattern, tipos)Validação cosmética de digitação (ex: impedir letras em campos de quantidade).Alto. Não barra dados inseridos via script ou automações.
Business Logic (.bl.js)Funções JavaScript com argumentos posicionaisCálculos comerciais, limites de faturamento, validação cruzada de itens.Nenhum. É o local ideal para regras analíticas.
ProcessFlow (.processflow.xml)Ação DECISION com ramificação <Cases>Governança da máquina de estados e bloqueio de transições.Médio. Nunca calcule totais aqui; avalie apenas flags booleanas.
Banco Local SQLite (app.db3)Constraints relacionais (NOT NULL, FKs)Última linha de defesa contra corrupção estrutural física.Alto se usado como interface: estoura exceções de SQLite cruas na tela.

Lição de Campo

Blindar o SQLite local contra dados corrompidos não é preciosismo: é a única garantia de que o aplicativo móvel offline do Consumer Goods Cloud continuará funcionando sem atritos.

Ao tratar o .bl.js como o guardião da regra de negócio — lendo argumentos posicionais sem assumir abstrações imaginárias como MethodInput — e delegar para o ProcessFlow o papel estrito de conduzir a máquina de estados através de <Cases>, você garante que nenhuma transação inválida chegue ao app.db3.

A sua equipe já teve problemas com pedidos travando na sincronização noturna por falta de validação local? Deixe sua experiência nos comentários!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *