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 buildAmbos 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:
- Simulador Mobile / CG Cloud App: Acesse a rota de criação de pedido (Order Entry Wizard), adicione um produto com quantidade
0ou deixe o valor abaixo do limiar mínimo e clique em “Concluir”. Capture o modal nativo exibindo a mensagem localizada. - Servidor de Logs do Modeler (
http://localhost:3000/logs): Com o visualizador ativo monitorandoappl/data/log/app.log, observe o disparo da linhaAppLog.info("Validação concluída com sucesso...")e o tempo de execução milimétrico da chamada. - Contratos no VS Code: Captura de tela dos arquivos
BoOrder.MyValidateOrderForSubmission.bl.jse do trecho de<Cases>no.processflow.xml.
7. Comparativo Arquitetural: Onde Posicionar as Regras de Integridade
| Camada da Solução | Mecanismo no Modeler | Casos de Uso Recomendados | Risco 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 posicionais | Cá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!
