Introdução ao GeneXus

 

Introdução ao GeneXus

O que é GeneXus?

GeneXus é uma plataforma de desenvolvimento low-code que gera código a partir de modelos declarativos. Diferente de builders visuais que limitam o desenvolvedor, o GeneXus gera aplicações completas em Java, .NET ou Angular a partir de definições de alto nível.

Pensando como desenvolvedor

Se você está acostumado com o fluxo:

Definir schema SQL → Criar models/entities → Escrever controllers → Criar views → Deploy

No GeneXus o fluxo é:

Definir Transactions (modelo) → Configurar Rules → Gerar aplicação completa → Deploy

O GeneXus analisa seu modelo de dados e infere relacionamentos, normalização, interfaces CRUD e lógica de navegação automaticamente. Você intervém apenas onde precisa de comportamento customizado.

Por que um dev experiente deveria conhecer GeneXus?

  • Produtividade: CRUDs completos com validações são gerados em minutos
  • Manutenção: mudanças no modelo propagam automaticamente para toda a aplicação
  • Multi-plataforma: o mesmo modelo gera backends Java/.NET e frontends web/mobile
  • Integração: suporte nativo a APIs REST, SOAP, mensageria

Instalação do GeneXus 18

Na Interface

  1. Acesse o site oficial da GeneXus e baixe a versão Trial do GeneXus 18
  2. Execute o instalador — a instalação é padrão Windows (next, next, finish)
  3. Na primeira execução, o GeneXus pedirá a ativação da licença trial
  4. Após ativar, você verá a tela inicial (Start Page) com opções para criar ou abrir Knowledge Bases

Requisitos: Windows 10/11, mínimo 8GB RAM, SQL Server (LocalDB incluso) ou MySQL/PostgreSQL


Capítulo 2 — Knowledge Base e Modelo de Dados

O que é uma Knowledge Base?

Uma Knowledge Base (KB) é o equivalente a um "projeto" ou "workspace" no GeneXus. Ela contém todos os objetos da sua aplicação: modelos de dados, lógica de negócio, interfaces e configurações.

Conceito tradicional GeneXus
Projeto (VS Code / IntelliJ) Knowledge Base
Arquivos de código Objetos GeneXus
package.json / .csproj Propriedades da KB

Na Interface — Criando uma Knowledge Base

  1. File → New → Knowledge Base
  2. Informe o nome da KB (ex: SistemaVendas)
  3. Escolha o generator principal (Java, .NET, Angular)
  4. Escolha o banco de dados (SQL Server, MySQL, PostgreSQL)
  5. Clique em Create — o GeneXus criará a estrutura da KB

Atributos

Atributos no GeneXus são equivalentes a colunas em uma tabela SQL ou campos de uma interface TypeScript.

Exemplo lado a lado

TypeScript:

interface Cliente {
  clienteId: number;
  clienteNome: string;
  clienteEmail: string;
}

SQL:

CREATE TABLE Cliente (
  ClienteId INT PRIMARY KEY,
  ClienteNome VARCHAR(100),
  ClienteEmail VARCHAR(200)
);

GeneXus:
No GeneXus, você não escreve SQL nem interfaces. Os atributos são definidos dentro de uma Transaction (veremos no próximo capítulo). O GeneXus cria a tabela, os tipos e a interface automaticamente.

ClienteId       Numeric(4)    - Identificador (chave primária, marcado com *)
ClienteNome     Character(100) - Nome do cliente
ClienteEmail    Character(200) - Email do cliente

Diferença chave: No GeneXus, um atributo é uma entidade global. Se ClienteId aparece em outra Transaction, o GeneXus entende automaticamente que é uma referência (foreign key). Não é preciso declarar relações manualmente.

Domínios

Domínios no GeneXus são tipos reutilizáveis — equivalem a type aliases no TypeScript ou DOMAIN no SQL.

Exemplo lado a lado — Domínios

TypeScript:

type Email = string;       // alias semântico
type Preco = number;       // alias semântico
type Status = "Ativo" | "Inativo";

interface Cliente {
  email: Email;
}

GeneXus — Domínio:

Domínio: Email       → Character(200)
Domínio: Preco       → Numeric(10.2)
Domínio: Status      → Character(10), com valores enumerados: "Ativo", "Inativo"

Quando você cria um atributo, pode associá-lo a um domínio. Se o domínio mudar (ex: Email passa de 200 para 300 caracteres), todos os atributos que usam esse domínio são atualizados automaticamente.

Na Interface — Criando Domínios

  1. No KB Explorer (painel lateral esquerdo), clique com botão direito em Domains
  2. Selecione New Domain
  3. Defina o nome, tipo de dado e, opcionalmente, valores enumerados
  4. Salve com Ctrl+S

Na Interface — Criando Atributos

Os atributos são criados diretamente dentro das Transactions (próximo capítulo). Não existem isoladamente — sempre pertencem a um contexto.


Capítulo 3 — Transactions (Modelo de Dados)

O que são Transactions?

Transactions são o coração do GeneXus. Elas definem simultaneamente:

  • A estrutura da tabela no banco de dados (como CREATE TABLE)
  • A entidade/model da aplicação (como uma Entity do TypeORM ou um Model do Prisma)
  • A interface CRUD gerada automaticamente (como um scaffold do Rails/NestJS)

Exemplo lado a lado — Transactions

SQL + TypeORM:

// Entity
@Entity()
class Cliente {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 100 })
  nome: string;

  @Column({ length: 200 })
  email: string;
}
-- SQL gerado
CREATE TABLE Cliente (
  Id INT PRIMARY KEY AUTO_INCREMENT,
  Nome VARCHAR(100),
  Email VARCHAR(200)
);

GeneXus — Transaction Cliente:

{ClienteId*    Numeric(4)     Autonumber}
 ClienteNome   Character(100)
 ClienteEmail  Character(200)

O * indica chave primária. Autonumber equivale ao AUTO_INCREMENT. Com essa definição, o GeneXus:

  • Cria a tabela no banco
  • Gera a tela de cadastro (inserir, editar, excluir)
  • Gera validações de tipo e chave primária

Primary Key, Relacionamentos e Normalização

Primary Key

No GeneXus, o atributo marcado com * é a PK. Equivale a:

PRIMARY KEY (ClienteId)

Relacionamentos (Foreign Keys)

No GeneXus, você não declara foreign keys. O relacionamento é inferido pelo nome do atributo.

SQL tradicional:

CREATE TABLE Pedido (
  PedidoId INT PRIMARY KEY,
  ClienteId INT REFERENCES Cliente(ClienteId),  -- FK explícita
  PedidoData DATE
);

GeneXus — Transaction Pedido:

{PedidoId*     Numeric(4)   Autonumber}
 ClienteId     Numeric(4)              -- GeneXus INFERE que é FK para Cliente
 ClienteNome   Character(100)          -- Atributo inferido (somente leitura)
 PedidoData    Date

Como ClienteId já existe na Transaction Cliente, o GeneXus entende que é uma referência. Ele até traz ClienteNome como campo somente-leitura na tela de Pedido.

Normalização Automática

O GeneXus aplica normalização automaticamente. Se você colocar ClienteNome na Transaction Pedido, ele não cria uma coluna redundante na tabela Pedido do banco. Em vez disso, resolve por JOIN na hora de exibir.

Para devs SQL: Pense nisso como uma VIEW automática. Você define o que quer ver, e o GeneXus resolve como buscar.

Transactions com Níveis (Relações 1:N)

Transactions podem ter níveis, que representam relações mestre-detalhe (1:N).

SQL tradicional:

CREATE TABLE Pedido (
  PedidoId INT PRIMARY KEY,
  ClienteId INT REFERENCES Cliente(ClienteId),
  PedidoData DATE
);

CREATE TABLE PedidoItem (
  PedidoId INT REFERENCES Pedido(PedidoId),
  PedidoItemId INT,
  ProdutoId INT REFERENCES Produto(ProdutoId),
  PedidoItemQtd INT,
  PedidoItemPreco DECIMAL(10,2),
  PRIMARY KEY (PedidoId, PedidoItemId)
);

GeneXus — Transaction Pedido com nível:

{PedidoId*        Numeric(4)     Autonumber}
 ClienteId        Numeric(4)
 ClienteNome      Character(100)
 PedidoData       Date

 // Nível (linha detalhe)
 {PedidoItemId*    Numeric(4)     Autonumber}
  ProdutoId        Numeric(4)
  ProdutoNome      Character(100)
  PedidoItemQtd    Numeric(4)
  PedidoItemPreco  Numeric(10.2)

O nível indentado dentro da Transaction cria automaticamente:

  • A tabela PedidoItem com FK composta (PedidoId, PedidoItemId)
  • Uma grid de itens na tela de cadastro do Pedido
  • A relação 1:N entre Pedido e PedidoItem

Na Interface — Criando Transactions

  1. No KB Explorer, clique com botão direito → New → Transaction
  2. Dê um nome (ex: Cliente)
  3. Na aba Structure, adicione os atributos:
    • Clique na primeira linha e digite o nome do atributo (ex: ClienteId)
    • Defina o tipo de dado na coluna Type (ex: Numeric(4))
    • Marque o checkbox * para indicar chave primária
    • Para autonumber: clique com botão direito no atributo → Autonumber → True
  4. Para criar níveis (detalhes 1:N):
    • Posicione o cursor após o último atributo do nível principal
    • Clique no botão Insert Level na toolbar (ou botão direito → Insert Level)
    • Adicione os atributos do nível filho
  5. Salve com Ctrl+S
  6. Execute Build → Build All para que o GeneXus crie as tabelas no banco

Capítulo 4 — Rules e Validações

O que são Rules?

Rules são regras de negócio declarativas associadas a uma Transaction. Elas definem validações, valores padrão e comportamentos — sem precisar escrever lógica imperativa.

Comparação com o mundo TypeScript/SQL

GeneXus Rule Equivalente TS/SQL
ClienteNome.Required() Zod: z.string().min(1) / SQL: NOT NULL
Default(PedidoData, &Today) TypeORM: @Column({ default: () => 'NOW()' })
Error("Email inválido") if ClienteEmail.IsEmpty() Zod: .refine() / Express middleware de validação
Noaccept(ClienteId) Campo readonly / disabled no form
Msg("Salvo com sucesso") on AfterComplete Toast/notification após submit

Rules Básicas

Required

Torna um campo obrigatório. Equivale a NOT NULL no SQL + validação no frontend.

Zod (TypeScript):

const clienteSchema = z.object({
  clienteNome: z.string().min(1, "Nome é obrigatório"),
  clienteEmail: z.string().email("Email inválido"),
});

GeneXus Rules:

ClienteNome.Required("Nome é obrigatório");
ClienteEmail.Required("Email é obrigatório");

Default

Define valor padrão para um atributo.

TypeORM:

@Column({ default: () => 'CURRENT_DATE' })
pedidoData: Date;

@Column({ default: 'Ativo' })
clienteStatus: string;

GeneXus Rules:

Default(PedidoData, &Today);
Default(ClienteStatus, "Ativo");

Error e Msg

Error impede o salvamento e exibe mensagem. Msg exibe aviso sem impedir.

Express/Zod:

// Validação customizada
if (pedidoItemQtd <= 0) {
  throw new BadRequestException("Quantidade deve ser maior que zero");
}

GeneXus Rules:

Error("Quantidade deve ser maior que zero") if PedidoItemQtd <= 0;
Msg("Atenção: pedido com valor alto") if PedidoTotal > 10000;

Noaccept

Torna um campo somente-leitura na interface (o usuário não pode editar).

Noaccept(PedidoTotal);          // campo calculado, não editável
Noaccept(ClienteId) on Update;  // não permite alterar ID ao editar

Equivale a disabled ou readonly em um campo de formulário HTML/React.

Rules com Condições

Rules podem ser condicionais usando if e podem disparar em momentos específicos:

// Desconto só para clientes VIP
Default(PedidoDesconto, 10) if ClienteCategoria = "VIP";

// Validação condicional
Error("CEP obrigatório para entregas") if PedidoTipoEntrega = "Correios" and ClienteCEP.IsEmpty();

// Timing: on AfterInsert, on BeforeInsert, on AfterComplete, etc.
Msg("Pedido criado com sucesso!") on AfterComplete;

Na Interface — Escrevendo Rules

  1. Abra a Transaction desejada (ex: Cliente)
  2. Clique na aba Rules (na parte inferior da tela, ao lado de Structure, Events, etc.)
  3. Digite as rules diretamente no editor de texto
  4. Cada rule termina com ;
  5. Salve com Ctrl+S

Dica: O editor de Rules tem autocomplete — pressione Ctrl+Space para ver opções disponíveis.


Capítulo 5 — Procedures (Lógica de Backend)

O que são Procedures?

Procedures são o equivalente GeneXus a funções ou services no backend. São usados para lógica de negócio, cálculos, consultas e operações em lote — tudo que não é CRUD direto.

Conceito TypeScript GeneXus
Função / Service Procedure
Parâmetros de função Parm rule
return valor Variável de saída no Parm
Query com Prisma/TypeORM for each
if/else if/else
console.log() msg()

Comandos Essenciais

for each — Consulta ao banco

O for each é o SELECT do GeneXus. Ele itera sobre registros de uma tabela, e o GeneXus resolve automaticamente a query SQL.

Prisma (TypeScript):

// Listar todos os clientes ativos
const clientes = await prisma.cliente.findMany({
  where: { clienteStatus: "Ativo" },
  orderBy: { clienteNome: "asc" },
});

for (const cliente of clientes) {
  console.log(`${cliente.clienteNome} - ${cliente.clienteEmail}`);
}

GeneXus Procedure:

for each Cliente
  where ClienteStatus = "Ativo"
  order ClienteNome

  msg(ClienteNome + " - " + ClienteEmail)
endfor

O que acontece por baixo: O GeneXus gera um SELECT ClienteNome, ClienteEmail FROM Cliente WHERE ClienteStatus = 'Ativo' ORDER BY ClienteNome. Você nunca escreve SQL diretamente.

where — Filtros

for each Pedido
  where PedidoData >= &DataInicio
  where PedidoData <= &DataFim
  where ClienteId = &ClienteId   // join automático com Cliente

  // GeneXus resolve o JOIN automaticamente
  msg(ClienteNome + " - Pedido #" + PedidoId.ToString())
endfor

Equivalente SQL gerado:

SELECT C.ClienteNome, P.PedidoId
FROM Pedido P
JOIN Cliente C ON P.ClienteId = C.ClienteId
WHERE P.PedidoData >= @DataInicio
  AND P.PedidoData <= @DataFim
  AND P.ClienteId = @ClienteId

if/else

if &Quantidade > 100
  &Desconto = 15
else
  if &Quantidade > 50
    &Desconto = 10
  else
    &Desconto = 0
  endif
endif

new — Inserir registros

Prisma:

await prisma.logAuditoria.create({
  data: {
    logData: new Date(),
    logDescricao: "Pedido criado",
    logUsuario: usuario.id,
  },
});

GeneXus:

new
  LogData = &Today
  LogDescricao = "Pedido criado"
  LogUsuario = &UsuarioId
endnew

Parâmetros de Entrada e Saída

Procedures recebem e retornam dados via a rule Parm.

TypeScript:

function calcularTotalPedido(pedidoId: number): number {
  // ... lógica
  return total;
}

GeneXus — Procedure CalcularTotalPedido:

Rules:

Parm(in: &PedidoId, out: &Total);

Source:

&Total = 0

for each PedidoItem
  where PedidoId = &PedidoId

  &Total += PedidoItemQtd * PedidoItemPreco
endfor
  • in: = parâmetro de entrada (somente leitura)
  • out: = parâmetro de saída (o "return")
  • inout: = entrada e saída (passagem por referência)

Chamando um Procedure

// De outro Procedure ou evento
CalcularTotalPedido(PedidoId, &Total)
msg("Total do pedido: " + &Total.ToString())

Equivale a:

const total = calcularTotalPedido(pedidoId);
console.log(`Total do pedido: ${total}`);

Na Interface — Criando um Procedure

  1. No KB Explorer, clique com botão direito → New → Procedure
  2. Dê um nome (ex: CalcularTotalPedido)
  3. Na aba Rules, defina os parâmetros com Parm(...)
  4. Na aba Source, escreva a lógica (for each, if, new, etc.)
  5. Na aba Variables, defina as variáveis locais usadas (prefixadas com &)
    • Clique em Insert para adicionar uma variável
    • Defina nome, tipo de dado (pode ser baseado em um atributo existente)
  6. Salve com Ctrl+S

Variáveis vs Atributos: No GeneXus, &Total (com &) é uma variável local. PedidoId (sem &) é um atributo do banco. A diferença é crucial: atributos navegam tabelas, variáveis são temporárias.


Capítulo 6 — Data Providers

O que são Data Providers?

Data Providers são objetos especializados em retornar dados estruturados — pense neles como funções que retornam JSON/objetos tipados. São ideais para alimentar APIs REST, grids e relatórios.

Conceito TypeScript GeneXus
Função que retorna { data: [...] } Data Provider
Interface/Type do retorno SDT (Structured Data Type)
Array.map() sobre resultados Grupo com for each implícito

Exemplo lado a lado

TypeScript:

interface ClienteDTO {
  id: number;
  nome: string;
  email: string;
  totalPedidos: number;
}

function listarClientesAtivos(): ClienteDTO[] {
  return clientes
    .filter(c => c.status === "Ativo")
    .map(c => ({
      id: c.id,
      nome: c.nome,
      email: c.email,
      totalPedidos: c.pedidos.length,
    }));
}

GeneXus — Data Provider DPClientesAtivos:

Primeiro, crie um SDT (tipo estruturado) para o retorno:

SDT: ClienteDTO (Collection)
  ClienteDTOId        Numeric(4)
  ClienteDTONome      Character(100)
  ClienteDTOEmail     Character(200)
  ClienteDTOTotalPedidos  Numeric(4)

Depois, o Data Provider:

// Output: ClienteDTO (Collection)

ClienteDTO
{
  ClienteDTOId        = ClienteId
  ClienteDTONome      = ClienteNome
  ClienteDTOEmail     = ClienteEmail
  ClienteDTOTotalPedidos = count(PedidoId)

  where ClienteStatus = "Ativo"
}

A grande diferença: No Data Provider, cada "grupo" (entre { }) gera automaticamente uma iteração sobre a tabela base. Não é preciso escrever for each — o GeneXus infere a navegação a partir dos atributos usados.

Estrutura Hierárquica

Data Providers suportam retornos aninhados, como objetos JSON complexos.

TypeScript:

interface PedidoCompleto {
  id: number;
  data: Date;
  cliente: { nome: string };
  itens: Array<{
    produto: string;
    qtd: number;
    preco: number;
  }>;
}

GeneXus — Data Provider com níveis:

PedidoCompleto
{
  PedidoId    = PedidoId
  PedidoData  = PedidoData
  ClienteNome = ClienteNome

  Itens
  {
    ProdutoNome     = ProdutoNome
    PedidoItemQtd   = PedidoItemQtd
    PedidoItemPreco = PedidoItemPreco
  }
}

O nível Itens aninhado gera automaticamente a relação 1:N, iterando sobre os itens de cada pedido.

Na Interface — Criando um Data Provider

  1. No KB Explorer, clique com botão direito → New → Data Provider
  2. Dê um nome (ex: DPClientesAtivos)
  3. Na aba Source, defina a estrutura hierárquica do retorno
  4. Na aba Rules, defina Parm(out: &Resultado) onde &Resultado é do tipo SDT definido
  5. Para criar o SDT (tipo do retorno):
    • New → Structured Data Type
    • Defina os campos e seus tipos
    • Marque como Collection se retornar uma lista
  6. Salve com Ctrl+S

Capítulo 7 — Business Components

O que são Business Components?

Business Components (BC) permitem manipular dados por código — inserir, atualizar e excluir registros programaticamente, mantendo todas as validações (Rules) da Transaction.

Conceito TypeScript/ORM GeneXus
prisma.cliente.create({...}) &Cliente.Save() (insert)
prisma.cliente.update({...}) &Cliente.Save() (update)
prisma.cliente.delete({...}) &Cliente.Delete()
prisma.cliente.findUnique({...}) &Cliente.Load(id)
Try/catch em operações &Cliente.GetMessages()
Repository Pattern Business Component

Habilitando Business Components

Por padrão, Transactions não são Business Components. Você precisa habilitar a propriedade.

Exemplo lado a lado — Inserir

Prisma (TypeScript):

try {
  const novoCliente = await prisma.cliente.create({
    data: {
      clienteNome: "João Silva",
      clienteEmail: "joao@email.com",
      clienteStatus: "Ativo",
    },
  });
  console.log(`Cliente criado: ID ${novoCliente.id}`);
} catch (error) {
  console.error("Erro ao criar cliente:", error.message);
}

GeneXus:

&Cliente = new()
&Cliente.ClienteNome  = "João Silva"
&Cliente.ClienteEmail = "joao@email.com"
&Cliente.ClienteStatus = "Ativo"
&Cliente.Save()

if &Cliente.Success()
  msg("Cliente criado: ID " + &Cliente.ClienteId.ToString())
else
  // Iterar sobre erros (como validações de Rules)
  for &Msg in &Cliente.GetMessages()
    msg(&Msg.Description)
  endfor
endif

Exemplo lado a lado — Atualizar

Prisma:

await prisma.cliente.update({
  where: { id: clienteId },
  data: { clienteEmail: "novo@email.com" },
});

GeneXus:

&Cliente.Load(ClienteId)
&Cliente.ClienteEmail = "novo@email.com"
&Cliente.Save()

Exemplo lado a lado — Excluir

Prisma:

await prisma.cliente.delete({
  where: { id: clienteId },
});

GeneXus:

&Cliente.Load(ClienteId)
&Cliente.Delete()

Exemplo — Inserir Pedido com Itens (Mestre-Detalhe)

Este é um cenário comum: inserir um registro pai com filhos.

Prisma:

await prisma.pedido.create({
  data: {
    clienteId: 1,
    pedidoData: new Date(),
    itens: {
      create: [
        { produtoId: 10, pedidoItemQtd: 2, pedidoItemPreco: 49.90 },
        { produtoId: 20, pedidoItemQtd: 1, pedidoItemPreco: 99.90 },
      ],
    },
  },
});

GeneXus:

&Pedido = new()
&Pedido.ClienteId  = 1
&Pedido.PedidoData = &Today

// Adicionar item 1
&PedidoItem = new()
&PedidoItem.ProdutoId       = 10
&PedidoItem.PedidoItemQtd   = 2
&PedidoItem.PedidoItemPreco = 49.90
&Pedido.PedidoItem.Add(&PedidoItem)

// Adicionar item 2
&PedidoItem = new()
&PedidoItem.ProdutoId       = 20
&PedidoItem.PedidoItemQtd   = 1
&PedidoItem.PedidoItemPreco = 99.90
&Pedido.PedidoItem.Add(&PedidoItem)

&Pedido.Save()

if &Pedido.Success()
  msg("Pedido criado com " + &Pedido.PedidoItem.Count.ToString() + " itens")
else
  for &Msg in &Pedido.GetMessages()
    msg(&Msg.Description)
  endfor
endif

Tratamento de Erros

O método GetMessages() retorna todas as mensagens de erro geradas pelas Rules da Transaction. Isso é equivalente a validações que disparam exceptions no backend.

&Cliente.Save()

if not &Cliente.Success()
  for &Msg in &Cliente.GetMessages()
    // &Msg.Type = 1 (erro), 0 (warning)
    if &Msg.Type = 1
      msg("ERRO: " + &Msg.Description)
    else
      msg("AVISO: " + &Msg.Description)
    endif
  endfor
endif

Na Interface — Habilitando Business Components

  1. Abra a Transaction desejada (ex: Cliente)
  2. No painel Properties (geralmente à direita), procure a propriedade Business Component
  3. Mude de No para Yes
  4. Salve com Ctrl+S
  5. Agora você pode declarar variáveis do tipo Cliente em Procedures e Web Panels:
    • Na aba Variables, crie uma variável com nome Cliente
    • O tipo será automaticamente Cliente (o Business Component)

Importante: Quando BC está habilitado, toda operação via código (Save, Delete) respeita as Rules definidas na Transaction. Se ClienteNome.Required() existe como Rule, o Save() falhará se o nome estiver vazio — assim como validações de schema falham no backend.


Capítulo 8 — Web Panels (Interface)

O que são Web Panels?

Web Panels são o equivalente GeneXus a componentes/páginas no mundo web. Se você vem do React, pense em Web Panels como componentes de página com layout visual, estado local (variáveis) e event handlers.

Conceito React/HTML GeneXus
Componente de página Web Panel
JSX / template HTML Layout visual (designer)
useState / variáveis locais Variáveis &
onClick, onSubmit Event 'NomeBotao'
useEffect(() => {}, []) Event Start
Re-render / fetch de dados Event Refresh
<table> / data grid Grid
<form> Tabela com controles

Layout: Tabelas, Grids e Controles

O layout de uma Web Panel é construído visualmente no designer (drag and drop), mas os conceitos mapeiam diretamente para HTML:

  • Table<div> ou <table> para organizar layout
  • Grid<table> com dados dinâmicos (como uma DataTable)
  • TextBlock<span> ou <label>
  • Attribute/Variable<input> vinculado a um dado
  • Button<button> com evento associado
  • Image<img>

Eventos

Eventos em Web Panels funcionam como event handlers em JavaScript.

React:

const [filtro, setFiltro] = useState("");

// Equivale a Event Start
useEffect(() => {
  carregarDados();
}, []);

// Equivale a Event 'Buscar'
const handleBuscar = () => {
  const resultados = dados.filter(d => d.nome.includes(filtro));
  setDadosFiltrados(resultados);
};

// Equivale a Event Refresh
const carregarDados = async () => {
  const res = await fetch("/api/clientes");
  setDados(await res.json());
};

GeneXus Web Panel:

// Executa ao carregar a página
Event Start
  &Filtro = ""
EndEvent

// Executa quando o grid precisa carregar/recarregar dados
Event Refresh
  // O grid carrega automaticamente baseado nos atributos definidos
  // Filtros são aplicados via Conditions
EndEvent

// Executa ao clicar no botão "Buscar"
Event 'Buscar'
  // Recarrega o grid com o novo filtro
  Grid1.Search()
EndEvent

// Executa ao clicar em uma linha do grid
Event Grid1.Click
  // Navegar para detalhe
  ClienteDetalhe(ClienteId)
EndEvent

Variáveis Locais

Variáveis em Web Panels (prefixadas com &) são como variáveis de estado do componente.

// Variáveis definidas na aba Variables:
// &FiltroNome    Character(100)
// &DataInicio    Date
// &TotalExibido  Numeric(10.2)

Event 'Calcular'
  &TotalExibido = 0

  for each Pedido
    where PedidoData >= &DataInicio
    where ClienteNome like &FiltroNome + "%"   // LIKE no SQL

    &TotalExibido += PedidoTotal
  endfor
EndEvent

Grids — Listagem de Dados

Um Grid em Web Panel é como uma tabela HTML alimentada por dados do banco.

React com tabela:

<table>
  <thead>
    <tr><th>Nome</th><th>Email</th><th>Status</th></tr>
  </thead>
  <tbody>
    {clientes.map(c => (
      <tr key={c.id}>
        <td>{c.nome}</td>
        <td>{c.email}</td>
        <td>{c.status}</td>
      </tr>
    ))}
  </tbody>
</table>

GeneXus: No designer visual, você arrasta um Grid para o layout e define as colunas (atributos). O GeneXus gera a query e a renderização automaticamente.

Colunas do Grid:

ClienteId | ClienteNome | ClienteEmail | ClienteStatus

Conditions (filtros do grid):

ClienteNome like &FiltroNome + "%" when not &FiltroNome.IsEmpty();
ClienteStatus = &FiltroStatus when not &FiltroStatus.IsEmpty();

As Conditions são o equivalente a cláusulas WHERE dinâmicas. O when funciona como um filtro condicional — só é aplicado se a variável não estiver vazia.

Exemplo Completo: Tela de Listagem com Filtro

Uma Web Panel WPClientes com:

  • Campo de filtro por nome (&FiltroNome)
  • Botão "Buscar"
  • Grid com clientes filtrados

Layout (designer):

┌─────────────────────────────────────────┐
│  Filtro: [&FiltroNome    ] [Buscar]     │
├─────────────────────────────────────────┤
│  ID  │  Nome       │  Email     │  Ação │
│  1   │  João Silva │  joao@...  │  [Ver]│
│  2   │  Maria...   │  maria@... │  [Ver]│
└─────────────────────────────────────────┘

Events:

Event Start
  &FiltroNome = ""
EndEvent

Event 'Buscar'
  Grid1.Search()
EndEvent

Event 'Ver'
  ClienteDetalhe(ClienteId)
EndEvent

Conditions:

ClienteNome like &FiltroNome + "%" when not &FiltroNome.IsEmpty();

Na Interface — Criando uma Web Panel

  1. No KB Explorer, clique com botão direito → New → Web Panel
  2. Dê um nome (ex: WPClientes)
  3. Na aba Layout (designer visual):
    • Arraste uma Table para organizar o layout
    • Arraste campos de variáveis (&FiltroNome) para criar inputs
    • Arraste um Button e defina o caption (ex: "Buscar")
    • Arraste um Grid e adicione colunas (atributos: ClienteId, ClienteNome, etc.)
  4. Na aba Events, escreva os event handlers
  5. Na aba Conditions, defina os filtros dinâmicos do grid
  6. Na aba Variables, defina variáveis locais usadas nos filtros e lógica
  7. Salve com Ctrl+S e execute com F5

Capítulo 9 — Work With Pattern

O que é o Work With Pattern?

O Work With Pattern é o scaffold/generator mais poderoso do GeneXus. A partir de uma Transaction, ele gera automaticamente uma aplicação completa com:

  • Tela de listagem com paginação e ordenação
  • Filtros de busca
  • Formulário de edição/inserção
  • Botões de ações (novo, editar, excluir)
  • Navegação entre listagem e formulário

Se você já usou nest generate resource (NestJS) ou rails generate scaffold (Ruby on Rails), o conceito é similar — mas o Work With vai além, gerando a interface completa funcional.

O que é gerado automaticamente

Para a Transaction Cliente:

Funcionalidade Equivalente web tradicional
Tela de listagem Página com <table> + paginação + filtros
Formulário de edição Form com todos os campos + validação
Botão "Novo" Link para form vazio
Botão "Editar" Link para form preenchido
Botão "Excluir" Ação com confirmação
Filtros por campo Inputs de busca com query dinâmica
Ordenação por coluna Click no header para sort

Quando usar vs. quando customizar

  • Use Work With para CRUDs padrão onde a interface gerada atende
  • Customize quando precisar de layout diferente, lógica extra ou fluxo não convencional
  • Use Web Panel manual quando a tela não é um CRUD (dashboards, relatórios, wizards)

Dica para devs: O Work With é um ponto de partida. Ele gera objetos Web Panel que você pode abrir e customizar depois. Não é uma caixa-preta.

Na Interface — Aplicando Work With Pattern

  1. Abra a Transaction desejada (ex: Cliente)
  2. No menu, vá em Work With → Apply Pattern
  3. O GeneXus gerará automaticamente:
    • WorkWithCliente — Web Panel principal (listagem)
    • ClienteDetail — Web Panel de visualização
    • Formulários de inserção/edição
  4. Para customizar, abra os objetos gerados e modifique:
    • Layout no designer visual
    • Events para lógica customizada
    • Conditions para filtros adicionais
  5. Execute com F5 para ver o resultado

Atalho: Você pode aplicar Work With a todas as Transactions de uma vez via Work With → Apply Pattern to All.


Capítulo 10 — APIs REST

Expondo APIs REST

No GeneXus, qualquer Procedure ou Data Provider pode ser exposto como endpoint REST. Não é preciso criar controllers, rotas ou middleware — basta configurar uma propriedade.

Exemplo lado a lado — Endpoint GET

Express.js (TypeScript):

// routes/clientes.ts
import { Router } from "express";
import { prisma } from "../db";

const router = Router();

router.get("/clientes", async (req, res) => {
  const { status } = req.query;

  const clientes = await prisma.cliente.findMany({
    where: status ? { clienteStatus: String(status) } : undefined,
    select: {
      clienteId: true,
      clienteNome: true,
      clienteEmail: true,
    },
  });

  res.json(clientes);
});

export default router;

GeneXus — Procedure APIListarClientes:

Rules:

Parm(in: &Status, out: &Clientes);

Source:

for each Cliente
  where ClienteStatus = &Status when not &Status.IsEmpty()

  &Cliente = new()
  &Cliente.ClienteId    = ClienteId
  &Cliente.ClienteNome  = ClienteNome
  &Cliente.ClienteEmail = ClienteEmail
  &Clientes.Add(&Cliente)
endfor

Propriedades do objeto:

Main Program:     True
REST Protocol:    True
HTTP Method:      GET
REST URL:         /api/clientes

Com essas configurações, o GeneXus expõe automaticamente:

GET /api/clientes?status=Ativo

Exemplo lado a lado — Endpoint POST

Express.js:

router.post("/clientes", async (req, res) => {
  const { nome, email } = req.body;

  try {
    const cliente = await prisma.cliente.create({
      data: { clienteNome: nome, clienteEmail: email, clienteStatus: "Ativo" },
    });
    res.status(201).json({ id: cliente.clienteId });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

GeneXus — Procedure APICriarCliente:

Rules:

Parm(in: &ClienteInput, out: &Resultado);

Source:

&Cliente = new()
&Cliente.ClienteNome   = &ClienteInput.Nome
&Cliente.ClienteEmail  = &ClienteInput.Email
&Cliente.ClienteStatus = "Ativo"
&Cliente.Save()

if &Cliente.Success()
  &Resultado.Sucesso = true
  &Resultado.Id      = &Cliente.ClienteId
else
  &Resultado.Sucesso = false
  for &Msg in &Cliente.GetMessages()
    &Resultado.Mensagem = &Msg.Description
  endfor
endif

Propriedades:

REST Protocol:    True
HTTP Method:      POST
REST URL:         /api/clientes

Consumindo APIs Externas

Para consumir APIs de terceiros, use o objeto HttpClient — equivalente ao fetch ou axios no TypeScript.

TypeScript com fetch:

const response = await fetch("https://viacep.com.br/ws/01001000/json/");
const data = await response.json();
console.log(data.logradouro); // "Praça da Sé"

GeneXus com HttpClient:

&HttpClient.Host    = "viacep.com.br"
&HttpClient.BaseUrl = "/ws/"
&HttpClient.Secure  = 1   // HTTPS

&HttpClient.Execute("GET", "01001000/json/")

if &HttpClient.StatusCode = 200
  &JsonResponse = &HttpClient.ToString()

  // Parsear JSON para SDT
  &Endereco.FromJson(&JsonResponse)
  msg(&Endereco.Logradouro)  // "Praça da Sé"
else
  msg("Erro: " + &HttpClient.StatusCode.ToString())
endif
fetch/axios HttpClient GeneXus
fetch(url) &HttpClient.Execute("GET", path)
response.status &HttpClient.StatusCode
response.json() &HttpClient.ToString() + .FromJson()
response.headers &HttpClient.GetHeader(name)
Request body &HttpClient.AddString(json)
Headers &HttpClient.AddHeader(name, value)

Na Interface — Configurando APIs REST

Para expor uma API:

  1. Abra o Procedure ou Data Provider
  2. No painel Properties, configure:
    • Main Program: True
    • REST Protocol: True
    • HTTP Method: GET, POST, PUT ou DELETE
    • REST URL: o caminho do endpoint (ex: /api/clientes)
  3. Salve e faça Build
  4. O GeneXus gera automaticamente documentação Swagger/OpenAPI
  5. Acesse {url-da-app}/swagger para testar os endpoints

Para consumir uma API:

  1. No Procedure, crie uma variável do tipo HttpClient
  2. Use os métodos Host, BaseUrl, Execute, AddHeader, AddString
  3. Crie um SDT para parsear a resposta JSON com FromJson()

Capítulo 10.1 — APIs REST Avançado: Autenticação, Headers e Fluxo de Requisições

Este módulo aprofunda o trabalho com APIs REST no GeneXus, cobrindo cenários reais que todo desenvolvedor backend enfrenta: autenticação por token, headers customizados, middleware de validação, tratamento de erros HTTP e orquestração de fluxos.


Autenticação em APIs REST

Autenticação com Token Bearer (JWT)

No mundo Express/NestJS, autenticação por JWT segue este padrão:

Express.js (TypeScript):

// Middleware de autenticação
const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Token não fornecido" });
  }

  const token = authHeader.split(" ")[1];
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch {
    return res.status(401).json({ error: "Token inválido" });
  }
};

router.get("/pedidos", authMiddleware, pedidoController);

GeneXus — Procedure APIMiddlewareAuth:

No GeneXus, você captura o header Authorization usando o objeto HttpRequest (equivalente ao objeto req do Express):

// Procedure: APIMiddlewareAuth
// Rules: Parm(out: &UsuarioId, out: &Autorizado, out: &MensagemErro);

// Capturar o header Authorization
&HttpRequest.GetHeader("Authorization", &AuthHeader)

if &AuthHeader.IsEmpty()
  &Autorizado   = false
  &MensagemErro = "Token não fornecido"
  return
endif

// Extrair o token (remover "Bearer ")
if &AuthHeader.Substring(0, 7) = "Bearer "
  &Token = &AuthHeader.Substring(7, &AuthHeader.Length() - 7)
else
  &Autorizado   = false
  &MensagemErro = "Formato de token inválido. Esperado: Bearer <token>"
  return
endif

// Validar o token contra o banco ou serviço de autenticação
&SessaoUsuario.Load(&Token)  // BC que armazena sessões ativas

if &SessaoUsuario.Fail()
  &Autorizado   = false
  &MensagemErro = "Token inválido ou expirado"
  return
endif

// Verificar expiração
if &SessaoUsuario.SessaoExpiracao < &Now
  &Autorizado   = false
  &MensagemErro = "Token expirado"
  return
endif

&UsuarioId  = &SessaoUsuario.UsuarioId
&Autorizado = true

Endpoint Protegido usando o Middleware

Express.js:

router.get("/pedidos", authMiddleware, async (req, res) => {
  const pedidos = await getPedidos(req.user.id);
  res.json(pedidos);
});

GeneXus — Procedure APIListarPedidosProtegido:

// Rules: Parm(out: &Resultado);
// Properties: REST Protocol = True, HTTP Method = GET, REST URL = /api/pedidos

// Chamar middleware de autenticação
APIMiddlewareAuth(&UsuarioId, &Autorizado, &MensagemErro)

if not &Autorizado
  // Retornar HTTP 401
  &HttpResponse.StatusCode = 401
  &Resultado.Sucesso  = false
  &Resultado.Mensagem = &MensagemErro
  return
endif

// Lógica do endpoint (usuário autenticado)
for each Pedido
  where ClienteId = &UsuarioId

  &Pedido = new()
  &Pedido.PedidoId     = PedidoId
  &Pedido.PedidoData   = PedidoData
  &Pedido.PedidoStatus = PedidoStatus
  &Pedido.PedidoTotal  = PedidoTotal
  &Resultado.Pedidos.Add(&Pedido)
endfor

&Resultado.Sucesso = true
Express.js GeneXus
req.headers.authorization &HttpRequest.GetHeader("Authorization", &var)
res.status(401) &HttpResponse.StatusCode = 401
req.user (do middleware) &UsuarioId (parâmetro de saída do middleware)
Middleware como função Procedure chamado no início do endpoint

Endpoint de Login (Gerar Token)

Express.js:

router.post("/auth/login", async (req, res) => {
  const { email, senha } = req.body;

  const usuario = await prisma.usuario.findUnique({ where: { email } });
  if (!usuario || !bcrypt.compareSync(senha, usuario.senhaHash)) {
    return res.status(401).json({ error: "Credenciais inválidas" });
  }

  const token = jwt.sign({ id: usuario.id }, process.env.JWT_SECRET, {
    expiresIn: "24h",
  });
  res.json({ token, expiresIn: 86400 });
});

GeneXus — Procedure APILogin:

// Rules: Parm(in: &LoginInput, out: &LoginResponse);
// Properties: REST Protocol = True, HTTP Method = POST, REST URL = /api/auth/login

// Buscar usuário por email
for each Usuario
  where UsuarioEmail = &LoginInput.Email
  where UsuarioStatus = "Ativo"

  // Validar senha (usando função de hash)
  if HashSHA256(&LoginInput.Senha + UsuarioSalt) = UsuarioSenhaHash
    // Gerar token único
    &Token = NewGuid().ToString() + "-" + &Now.ToString()
    &TokenHash = HashSHA256(&Token)

    // Salvar sessão no banco
    &Sessao = new()
    &Sessao.UsuarioId      = UsuarioId
    &Sessao.SessaoToken    = &TokenHash
    &Sessao.SessaoExpiracao = &Now.AddHours(24)
    &Sessao.Save()

    if &Sessao.Success()
      &LoginResponse.Sucesso   = true
      &LoginResponse.Token     = &TokenHash
      &LoginResponse.ExpiraEm  = 86400
      &LoginResponse.UsuarioNome = UsuarioNome
    else
      &HttpResponse.StatusCode = 500
      &LoginResponse.Sucesso  = false
      &LoginResponse.Mensagem = "Erro ao criar sessão"
    endif
    return
  else
    &HttpResponse.StatusCode = 401
    &LoginResponse.Sucesso  = false
    &LoginResponse.Mensagem = "Credenciais inválidas"
    return
  endif
endfor

// Se chegou aqui, email não encontrado
&HttpResponse.StatusCode = 401
&LoginResponse.Sucesso  = false
&LoginResponse.Mensagem = "Credenciais inválidas"

Autenticação com GAM via API REST

Se você já habilitou o GAM (Capítulo 12), pode usar os serviços REST nativos dele em vez de implementar autenticação manual:

// Login via GAM — retorna token automaticamente
&GAMLoginResult = GAMRepository.Login(&Email, &Senha)

if &GAMLoginResult.Success
  &Token = &GAMLoginResult.Token
  // O GAM gera e valida tokens automaticamente
else
  &HttpResponse.StatusCode = 401
  &MensagemErro = &GAMLoginResult.ErrorMessage
endif
// Validar token via GAM em endpoints protegidos
&GAMSession = GAMRepository.GetSessionFromToken(&Token)

if &GAMSession.IsValid
  &UsuarioId = &GAMSession.UserId
  // Continuar com a lógica do endpoint
else
  &HttpResponse.StatusCode = 401
endif

Quando usar GAM vs. autenticação manual: Use GAM quando quiser autenticação completa com roles, permissões, OAuth2 e backoffice pronto. Use autenticação manual quando precisar de controle total ou integrar com sistema de auth externo específico.


Headers Customizados

Recebendo Headers Customizados na API

Em APIs reais, é comum receber headers customizados como X-API-Key, X-Tenant-Id, X-Request-Id, X-Correlation-Id, etc.

Express.js:

router.get("/dados", (req, res) => {
  const apiKey     = req.headers["x-api-key"];
  const tenantId   = req.headers["x-tenant-id"];
  const requestId  = req.headers["x-request-id"] || uuidv4();
  const contentType = req.headers["content-type"];

  if (!apiKey || apiKey !== process.env.API_KEY) {
    return res.status(403).json({ error: "API Key inválida" });
  }

  // Usar tenantId para filtrar dados por tenant
  const dados = getDadosPorTenant(tenantId);

  // Retornar headers customizados na resposta
  res.set("X-Request-Id", requestId);
  res.set("X-Response-Time", `${Date.now() - start}ms`);
  res.json(dados);
});

GeneXus — Procedure APIDadosMultiTenant:

// Rules: Parm(out: &Resultado);
// Properties: REST Protocol = True, HTTP Method = GET, REST URL = /api/dados

// ===== RECEBER HEADERS =====
&HttpRequest.GetHeader("X-API-Key", &ApiKey)
&HttpRequest.GetHeader("X-Tenant-Id", &TenantId)
&HttpRequest.GetHeader("X-Request-Id", &RequestId)
&HttpRequest.GetHeader("Content-Type", &ContentType)

// Gerar Request-Id se não veio no header
if &RequestId.IsEmpty()
  &RequestId = NewGuid().ToString()
endif

// ===== VALIDAR API KEY =====
if &ApiKey.IsEmpty() or &ApiKey <> &ApiKeyEsperada
  &HttpResponse.StatusCode = 403
  &Resultado.Sucesso  = false
  &Resultado.Mensagem = "API Key inválida"

  // Mesmo em erro, retornar Request-Id para rastreabilidade
  &HttpResponse.AddHeader("X-Request-Id", &RequestId)
  return
endif

// ===== VALIDAR TENANT =====
if &TenantId.IsEmpty()
  &HttpResponse.StatusCode = 400
  &Resultado.Sucesso  = false
  &Resultado.Mensagem = "Header X-Tenant-Id é obrigatório"
  &HttpResponse.AddHeader("X-Request-Id", &RequestId)
  return
endif

// ===== LÓGICA DO ENDPOINT =====
&HoraInicio = Now()

for each Cliente
  where ClienteTenantId = &TenantId

  &Item = new()
  &Item.ClienteId   = ClienteId
  &Item.ClienteNome = ClienteNome
  &Resultado.Dados.Add(&Item)
endfor

&Resultado.Sucesso = true

// ===== RETORNAR HEADERS NA RESPOSTA =====
&HttpResponse.AddHeader("X-Request-Id", &RequestId)
&HttpResponse.AddHeader("X-Tenant-Id", &TenantId)
&HttpResponse.AddHeader("X-Total-Count", &Resultado.Dados.Count.ToString())
&HttpResponse.AddHeader("X-Response-Time", (&Now - &HoraInicio).ToString() + "ms")

Referência Completa: HttpRequest e HttpResponse

Lendo dados da requisição (HttpRequest):

Express.js (req) GeneXus (&HttpRequest)
req.headers["x-custom"] &HttpRequest.GetHeader("X-Custom", &var)
req.query.param &HttpRequest.GetVariable("param", &var)
req.method &HttpRequest.Method
req.url &HttpRequest.RequestURL
req.ip &HttpRequest.RemoteAddress
req.body (raw) &HttpRequest.ToString()
req.cookies["name"] &HttpRequest.GetCookieValue("name")

Escrevendo na resposta (HttpResponse):

Express.js (res) GeneXus (&HttpResponse)
res.status(404) &HttpResponse.StatusCode = 404
res.set("Header", "val") &HttpResponse.AddHeader("Header", "val")
res.type("application/json") &HttpResponse.ContentType = "application/json"
res.cookie("name", "val") &HttpResponse.SetCookie("name", "val")
res.redirect("/url") &HttpResponse.Redirect("/url")
res.send(body) &HttpResponse.AddString(body)

Enviando Headers em Requisições para APIs Externas

Quando seu GeneXus consome uma API externa que exige headers:

TypeScript com fetch:

const response = await fetch("https://api.externa.com/dados", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
    "Content-Type": "application/json",
    "X-API-Key": "minha-chave-123",
    "X-Correlation-Id": correlationId,
    "Accept-Language": "pt-BR",
  },
  body: JSON.stringify({ filtro: "ativo" }),
});

GeneXus com HttpClient:

&HttpClient.Host    = "api.externa.com"
&HttpClient.BaseUrl = "/dados"
&HttpClient.Secure  = 1   // HTTPS

// Configurar headers
&HttpClient.AddHeader("Authorization", "Bearer " + &Token)
&HttpClient.AddHeader("Content-Type", "application/json")
&HttpClient.AddHeader("X-API-Key", "minha-chave-123")
&HttpClient.AddHeader("X-Correlation-Id", &CorrelationId)
&HttpClient.AddHeader("Accept-Language", "pt-BR")

// Body da requisição
&BodyJson = '{"filtro": "ativo"}'
&HttpClient.AddString(&BodyJson)

// Executar
&HttpClient.Execute("POST", "")

// Ler headers da resposta
&HttpClient.GetHeader("X-RateLimit-Remaining", &RateLimitRestante)
&HttpClient.GetHeader("X-Request-Id", &RequestIdExterno)

Fluxo de Requisições REST: Pipeline Completo

Arquitetura de Pipeline

Em aplicações reais, cada requisição REST passa por várias camadas antes de chegar à lógica de negócio. No Express.js isso é feito com middleware em cadeia. No GeneXus, o padrão é compor Procedures.

Express.js — Pipeline de middlewares:

router.post(
  "/pedidos",
  rateLimiter,           // 1. Rate limiting
  authMiddleware,        // 2. Autenticação
  validateBody(schema),  // 3. Validação do body
  logRequest,            // 4. Logging
  pedidoController       // 5. Lógica de negócio
);

GeneXus — Pipeline equivalente com Procedures:

// Procedure: APICriarPedido
// Properties: REST Protocol = True, HTTP Method = POST, REST URL = /api/pedidos

// ===== 1. RATE LIMITING =====
APIRateLimiter(&HttpRequest, &Permitido, &MensagemRL)
if not &Permitido
  &HttpResponse.StatusCode = 429
  &HttpResponse.AddHeader("Retry-After", "60")
  &Resultado.Sucesso  = false
  &Resultado.Mensagem = &MensagemRL
  return
endif

// ===== 2. AUTENTICAÇÃO =====
APIMiddlewareAuth(&UsuarioId, &Autorizado, &MensagemAuth)
if not &Autorizado
  &HttpResponse.StatusCode = 401
  &Resultado.Sucesso  = false
  &Resultado.Mensagem = &MensagemAuth
  return
endif

// ===== 3. VALIDAÇÃO DO BODY =====
&BodyJson = &HttpRequest.ToString()
&PedidoInput.FromJson(&BodyJson)

APIValidarPedidoInput(&PedidoInput, &Valido, &ErrosValidacao)
if not &Valido
  &HttpResponse.StatusCode = 400
  &Resultado.Sucesso  = false
  &Resultado.Mensagem = "Dados inválidos"
  &Resultado.Erros    = &ErrosValidacao
  return
endif

// ===== 4. LOGGING =====
APILogRequest("POST", "/api/pedidos", &UsuarioId, &BodyJson)

// ===== 5. LÓGICA DE NEGÓCIO =====
CriarPedido(&PedidoInput, &UsuarioId, &PedidoCriado, &Sucesso, &MensagemNeg)

if &Sucesso
  &HttpResponse.StatusCode = 201
  &HttpResponse.AddHeader("Location", "/api/pedidos/" + &PedidoCriado.PedidoId.ToString())
  &Resultado.Sucesso = true
  &Resultado.Pedido  = &PedidoCriado
else
  &HttpResponse.StatusCode = 422
  &Resultado.Sucesso  = false
  &Resultado.Mensagem = &MensagemNeg
endif

// ===== 6. LOG DE RESPOSTA =====
APILogResponse("POST", "/api/pedidos", &HttpResponse.StatusCode, &UsuarioId)

Procedure de Validação do Body

Zod (TypeScript):

const pedidoSchema = z.object({
  clienteId: z.number().positive("ClienteId deve ser positivo"),
  itens: z.array(z.object({
    produtoId: z.number().positive(),
    quantidade: z.number().min(1, "Quantidade mínima é 1"),
  })).min(1, "Pedido deve ter pelo menos 1 item"),
});

// Uso no middleware
const data = pedidoSchema.parse(req.body); // throw se inválido

GeneXus — Procedure APIValidarPedidoInput:

// Rules: Parm(in: &PedidoInput, out: &Valido, out: &Erros);

&Valido = true

// Validar clienteId
if &PedidoInput.ClienteId <= 0
  &Erro = new()
  &Erro.Campo    = "clienteId"
  &Erro.Mensagem = "ClienteId deve ser positivo"
  &Erros.Add(&Erro)
  &Valido = false
endif

// Verificar se cliente existe
for each Cliente
  where ClienteId = &PedidoInput.ClienteId
  // Encontrou — ok
endfor
if &Valido and not found
  &Erro = new()
  &Erro.Campo    = "clienteId"
  &Erro.Mensagem = "Cliente não encontrado"
  &Erros.Add(&Erro)
  &Valido = false
endif

// Validar itens
if &PedidoInput.Itens.Count = 0
  &Erro = new()
  &Erro.Campo    = "itens"
  &Erro.Mensagem = "Pedido deve ter pelo menos 1 item"
  &Erros.Add(&Erro)
  &Valido = false
else
  &ItemIndex = 0
  for &Item in &PedidoInput.Itens
    &ItemIndex += 1

    if &Item.ProdutoId <= 0
      &Erro = new()
      &Erro.Campo    = "itens[" + &ItemIndex.ToString() + "].produtoId"
      &Erro.Mensagem = "ProdutoId deve ser positivo"
      &Erros.Add(&Erro)
      &Valido = false
    endif

    if &Item.Quantidade < 1
      &Erro = new()
      &Erro.Campo    = "itens[" + &ItemIndex.ToString() + "].quantidade"
      &Erro.Mensagem = "Quantidade mínima é 1"
      &Erros.Add(&Erro)
      &Valido = false
    endif
  endfor
endif

Procedure de Rate Limiting

// Procedure: APIRateLimiter
// Rules: Parm(in: &HttpRequest, out: &Permitido, out: &Mensagem);

&IP = &HttpRequest.RemoteAddress
&Agora = &Now

// Contar requisições do IP nos últimos 60 segundos
&ContadorReqs = 0
for each APIRateLimitLog
  where RateLimitIP = &IP
  where RateLimitTimestamp >= &Agora.AddSeconds(-60)

  &ContadorReqs += 1
endfor

if &ContadorReqs >= 100   // limite: 100 req/min
  &Permitido = false
  &Mensagem  = "Rate limit excedido. Tente novamente em 60 segundos."
  return
endif

// Registrar esta requisição
new
  RateLimitIP        = &IP
  RateLimitTimestamp  = &Agora
  RateLimitEndpoint   = &HttpRequest.RequestURL
endnew

&Permitido = true

Procedure de Logging

// Procedure: APILogRequest
// Rules: Parm(in: &Metodo, in: &Endpoint, in: &UsuarioId, in: &Body);

new
  LogTimestamp  = &Now
  LogMetodo     = &Metodo
  LogEndpoint   = &Endpoint
  LogUsuarioId  = &UsuarioId
  LogBody       = &Body.Substring(0, 2000)  // Limitar tamanho
  LogTipo       = "REQUEST"
endnew

Tratamento de Erros HTTP — Respostas Padronizadas

Padrão de Resposta de Erro

Em APIs profissionais, erros seguem uma estrutura consistente. No Express, normalmente se cria um error handler global.

Express.js:

// Error handler global
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  const status = err instanceof HttpException ? err.status : 500;
  res.status(status).json({
    sucesso: false,
    erro: {
      codigo: status,
      mensagem: err.message,
      timestamp: new Date().toISOString(),
      path: req.url,
    },
  });
});

GeneXus — Procedure APIRetornarErro (helper reutilizável):

// Rules: Parm(in: &StatusCode, in: &Mensagem, in: &Detalhes, out: &ErroResponse);

&HttpResponse.StatusCode = &StatusCode

&ErroResponse.Sucesso   = false
&ErroResponse.Erro.Codigo    = &StatusCode
&ErroResponse.Erro.Mensagem  = &Mensagem
&ErroResponse.Erro.Detalhes  = &Detalhes
&ErroResponse.Erro.Timestamp = &Now.ToString()
&ErroResponse.Erro.RequestId = &RequestId

Uso nos endpoints:

// Em qualquer endpoint, reutilizar o helper
if not &Autorizado
  APIRetornarErro(401, "Não autorizado", "Token expirado ou inválido", &Resultado)
  return
endif

if not &Valido
  APIRetornarErro(400, "Dados inválidos", &ErrosValidacao.ToJson(), &Resultado)
  return
endif

// Erro interno inesperado
if &AlgoFalhou
  APIRetornarErro(500, "Erro interno", "Falha ao processar pedido", &Resultado)
  return
endif

SDT para resposta de erro padronizada:

SDT: APIErroResponse
  Sucesso       Boolean
  Erro
    Codigo      Numeric(3)
    Mensagem    Character(500)
    Detalhes    Character(2000)
    Timestamp   Character(30)
    RequestId   Character(50)

Mapa de Status Codes

Situação Status Code Quando usar
Sucesso 200 GET, PUT, PATCH com resposta
Criado 201 POST que cria recurso
Sem conteúdo 204 DELETE bem-sucedido
Dados inválidos 400 Validação do body falhou
Não autenticado 401 Token ausente ou inválido
Não autorizado 403 Sem permissão (API Key inválida, role insuficiente)
Não encontrado 404 Recurso não existe
Conflito 409 Duplicidade (email já existe)
Entidade não processável 422 Regra de negócio violada
Rate limit 429 Muitas requisições
Erro interno 500 Erro inesperado do servidor

Consumindo API Externa com Autenticação Completa

Exemplo real: consumir uma API externa que exige OAuth2 Client Credentials (machine-to-machine).

TypeScript com axios:

// 1. Obter token
const tokenRes = await axios.post("https://auth.api.com/oauth/token", {
  grant_type: "client_credentials",
  client_id: process.env.CLIENT_ID,
  client_secret: process.env.CLIENT_SECRET,
  scope: "read:dados",
});
const accessToken = tokenRes.data.access_token;

// 2. Usar token na requisição
const dadosRes = await axios.get("https://api.com/v1/dados", {
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "X-Request-Id": uuidv4(),
  },
});

GeneXus — Procedure ConsumirAPIExternaOAuth:

// ===== PASSO 1: Obter Token OAuth2 =====
&HttpClientAuth.Host    = "auth.api.com"
&HttpClientAuth.BaseUrl = "/oauth/"
&HttpClientAuth.Secure  = 1

&HttpClientAuth.AddHeader("Content-Type", "application/x-www-form-urlencoded")

&AuthBody = "grant_type=client_credentials"
&AuthBody += "&client_id=" + &ClientId
&AuthBody += "&client_secret=" + &ClientSecret
&AuthBody += "&scope=read:dados"
&HttpClientAuth.AddString(&AuthBody)

&HttpClientAuth.Execute("POST", "token")

if &HttpClientAuth.StatusCode <> 200
  msg("Erro ao obter token: " + &HttpClientAuth.StatusCode.ToString())
  return
endif

// Parsear resposta do token
&TokenResponse.FromJson(&HttpClientAuth.ToString())
&AccessToken = &TokenResponse.AccessToken

// ===== PASSO 2: Chamar API com o Token =====
&HttpClient.Host    = "api.com"
&HttpClient.BaseUrl = "/v1/"
&HttpClient.Secure  = 1

&HttpClient.AddHeader("Authorization", "Bearer " + &AccessToken)
&HttpClient.AddHeader("X-Request-Id", NewGuid().ToString())
&HttpClient.AddHeader("Accept", "application/json")

&HttpClient.Execute("GET", "dados")

if &HttpClient.StatusCode = 200
  &Dados.FromJson(&HttpClient.ToString())
  // Processar dados...
else
  if &HttpClient.StatusCode = 401
    msg("Token expirado ou inválido")
  else
    msg("Erro na API: " + &HttpClient.StatusCode.ToString())
  endif
endif

Na Interface — Configurando APIs REST Avançadas

Criando o SDT para requests/responses:

  1. No KB Explorer → botão direito → New → Structured Data Type
  2. Defina os campos aninhados (ex: APIErroResponse com sub-item Erro)
  3. Para listas, marque a propriedade Collection no item

Configurando o HttpRequest/HttpResponse:

  1. Na aba Variables do Procedure, crie:
    • &HttpRequest do tipo HttpRequest (para ler dados da requisição recebida)
    • &HttpResponse do tipo HttpResponse (para customizar a resposta)
  2. Esses objetos são preenchidos automaticamente pelo runtime quando o Procedure é chamado via REST

Testando com Swagger:

  1. Após Build, acesse {url-da-app}/swagger
  2. No Swagger, clique no botão Authorize para configurar o token Bearer
  3. Teste cada endpoint com headers customizados via a interface do Swagger
  4. Verifique os headers de resposta na seção "Response headers"

Testando com ferramentas externas:

  • Use Postman ou Insomnia para testar endpoints com headers customizados
  • Configure uma Collection no Postman com variáveis de ambiente para token e API Key
  • Use a aba Pre-request Script no Postman para automatizar a obtenção do token antes de cada request

Capítulo 11 — Deploy e Ambientes

Generators

No GeneXus, um Generator define a tecnologia-alvo para a qual seu código será gerado. É o equivalente a escolher seu stack de deploy.

Decisão tradicional Generator GeneXus
Backend Node.js/Express Generator Java ou .NET
Frontend React/Angular Generator Angular ou Web (server-side)
Mobile React Native Generator Android / iOS
tsconfig.json (target) Propriedades do Generator

Os generators mais comuns no GeneXus 18:

  • Java — gera aplicação Java (Tomcat, Spring)
  • .NET — gera aplicação .NET (IIS, Kestrel)
  • .NET Core — gera aplicação .NET 6+
  • Angular — gera frontend Angular

O mesmo modelo (KB) pode gerar para múltiplas plataformas. Você define Transactions e lógica uma vez, e gera para Java e Angular ao mesmo tempo.

Environments

Um Environment no GeneXus é uma configuração completa de deploy — equivalente a arquivos .env + configuração por ambiente no mundo Node.js.

Node.js tradicional:

.env.development     → DB local, debug ligado
.env.staging         → DB de staging, logs ativados
.env.production      → DB de produção, otimizações

GeneXus — Environments:

Cada Environment define:

  • Generator (Java, .NET, Angular)
  • DBMS (SQL Server, MySQL, PostgreSQL, Oracle)
  • String de conexão com o banco
  • URL base da aplicação
  • Configurações de deploy (servidor, porta, etc.)

Você pode ter:

  • Desenvolvimento → SQL Server local, .NET, debug
  • Homologação → PostgreSQL em servidor de staging
  • Produção → SQL Server em cloud, otimizado

Build e Reorganize

No mundo Node.js/TypeScript, o fluxo de build é:

npm run build      # compila TypeScript → JavaScript
npx prisma migrate # aplica migrations no banco
npm start          # inicia a aplicação

No GeneXus, os equivalentes são:

Comando Node.js GeneXus
npm run build Build → Build All
prisma migrate / typeorm migration:run Build → Reorganize (aplica alterações no banco)
npm start Build → Run (F5)
  • Build All — gera o código-fonte (Java/.NET) a partir do modelo
  • Reorganize — compara o modelo atual com o banco e aplica as alterações (cria tabelas, adiciona colunas, etc.) — como uma migration automática
  • Run (F5) — compila e executa a aplicação

Para devs SQL: O Reorganize é como se o GeneXus gerasse e aplicasse migrations automaticamente. Ele nunca perde dados — se uma coluna muda de tipo, ele faz a conversão.

Deploy em Cloud

O GeneXus 18 suporta deploy em:

  • GeneXus Cloud — plataforma PaaS da GeneXus (mais simples)
  • AWS, Azure, GCP — via empacotamento do artefato gerado
  • Docker — o código gerado pode ser containerizado
  • Tomcat/IIS — deploy tradicional em servidores

Na Interface — Configurando Environments e Generators

  1. No KB Explorer, expanda o nó Preferences
  2. Você verá os Environments existentes (ex: Default)
  3. Para criar um novo Environment:
    • Botão direito em Preferences → New → Environment
    • Defina nome, Generator e DBMS
  4. Para configurar um Environment:
    • Expanda o Environment e clique no Generator (ex: .NET)
    • No painel Properties, configure:
      • Data Store: conexão com o banco
      • Server name, Database name: dados de conexão
      • Target path: onde o código gerado será salvo
  5. Para executar Build/Reorganize:
    • Menu Build → Build All (ou Ctrl+F5)
    • Menu Build → Reorganize (para aplicar mudanças no banco)
    • Menu Build → Run (ou F5) para executar

Capítulo 12 — Segurança (GAM)

O que é o GAM?

O GeneXus Access Manager (GAM) é o módulo de segurança integrado do GeneXus. Ele fornece autenticação, autorização, gestão de usuários e controle de acesso — tudo que no mundo Node.js você faria com Passport.js, NextAuth, JWT e middleware customizado.

Conceito Node.js GAM GeneXus
Passport.js / NextAuth GAM (autenticação)
JWT / Session tokens GAM tokens
Middleware isAuthenticated GAM integrado (automático)
Roles (admin, user, etc.) GAM Roles
Permissões por rota GAM Permissions por objeto
Tabelas de usuário customizadas GAM User / GAM Role (tabelas gerenciadas)
OAuth2 (Google, GitHub) GAM Authentication Types

O que o GAM fornece

  • Login/Logout com tela pronta
  • Gestão de usuários (criar, editar, bloquear)
  • Roles e permissões (quem acessa o quê)
  • Autenticação externa (OAuth2 com Google, Facebook, etc.)
  • Tokens de sessão (similar a JWT)
  • Auditoria (logs de acesso)

Autenticação

Quando o GAM está habilitado, todas as páginas são protegidas automaticamente — como um middleware global de autenticação.

Express.js com middleware:

// Middleware global
app.use(authMiddleware);

// Ou por rota
router.get("/admin", requireRole("admin"), adminController);

GeneXus com GAM: Após habilitar o GAM, todas as Web Panels e APIs exigem autenticação automaticamente. Para tornar algo público, você marca explicitamente.

Roles e Permissões

Node.js (manual):

// Verificar role no código
if (user.role !== "admin") {
  throw new ForbiddenException("Acesso negado");
}

GeneXus com GAM: Roles e permissões são configurados na interface administrativa do GAM (um backoffice gerado automaticamente). Você associa permissões a objetos GeneXus e atribui roles aos usuários — sem código.

Na Interface — Habilitando e Configurando GAM

  1. No menu, vá em Knowledge Manager → GeneXus Access Manager
  2. Selecione Enable GAM → o GeneXus adiciona os objetos de segurança à KB
  3. Faça Build → Build All e Reorganize para criar as tabelas do GAM no banco
  4. Execute a aplicação — a tela de login aparecerá automaticamente
  5. Use o GAM Backoffice (gerado automaticamente) para:
    • Criar usuários (admin padrão: admin / admin123)
    • Criar roles (ex: Administrador, Vendedor, Cliente)
    • Atribuir permissões (quais objetos cada role pode acessar)
    • Configurar autenticação externa (Google, Facebook, etc.)
  6. Para tornar um objeto público (sem login):
    • Abra o objeto → Properties → GAM Object VisibilityPublic

Nota: O GAM é um módulo robusto com muitas configurações. Para a maioria dos projetos, habilitar o GAM e configurar roles no backoffice é suficiente. Configurações avançadas (OAuth2, SAML, federação) estão documentadas no wiki oficial.


Capítulo 13 — Projeto Prático: Sistema de Vendas

Agora vamos aplicar tudo que aprendemos construindo um sistema completo de vendas. Cada passo referencia os capítulos anteriores.

Modelo de Dados (Cap. 2-3)

Transaction Cliente

{ClienteId*       Numeric(4)     Autonumber}
 ClienteNome      Character(100)
 ClienteEmail     Character(200)
 ClienteTelefone  Character(20)
 ClienteStatus    Character(10)    // Domínio: Status ("Ativo", "Inativo")

Rules:

ClienteNome.Required("Nome é obrigatório");
ClienteEmail.Required("Email é obrigatório");
Default(ClienteStatus, "Ativo");

Transaction Produto

{ProdutoId*       Numeric(4)     Autonumber}
 ProdutoNome      Character(100)
 ProdutoPreco     Numeric(10.2)    // Domínio: Preco
 ProdutoEstoque   Numeric(6)
 ProdutoStatus    Character(10)

Rules:

ProdutoNome.Required("Nome é obrigatório");
Error("Preço deve ser maior que zero") if ProdutoPreco <= 0;
Default(ProdutoStatus, "Ativo");
Default(ProdutoEstoque, 0);

Transaction Pedido com nível PedidoItem

{PedidoId*           Numeric(4)     Autonumber}
 ClienteId           Numeric(4)              // FK → Cliente
 ClienteNome         Character(100)          // Inferido (somente leitura)
 PedidoData          Date
 PedidoStatus        Character(15)           // "Pendente", "Aprovado", "Cancelado"
 PedidoTotal         Numeric(12.2)

 // Nível: Itens do pedido
 {PedidoItemId*       Numeric(4)     Autonumber}
  ProdutoId           Numeric(4)              // FK → Produto
  ProdutoNome         Character(100)          // Inferido
  ProdutoPreco        Numeric(10.2)           // Inferido
  PedidoItemQtd       Numeric(6)
  PedidoItemSubtotal  Numeric(12.2)

Rules:

ClienteId.Required("Selecione um cliente");
Default(PedidoData, &Today);
Default(PedidoStatus, "Pendente");
Noaccept(PedidoTotal);
Noaccept(PedidoItemSubtotal);
Noaccept(ClienteNome);
Noaccept(ProdutoNome);
Noaccept(ProdutoPreco);

// Cálculos automáticos
PedidoItemSubtotal = PedidoItemQtd * ProdutoPreco;
PedidoTotal = sum(PedidoItemSubtotal);

// Validações
Error("Quantidade deve ser maior que zero") if PedidoItemQtd <= 0;
Error("Selecione um produto") if ProdutoId.IsEmpty();

Lógica de Backend (Cap. 5, 7)

Procedure AprovarPedido

Rules:

Parm(in: &PedidoId, out: &Sucesso, out: &Mensagem);

Source:

// Usar Business Component para atualizar
&Pedido.Load(&PedidoId)

if &Pedido.Fail()
  &Sucesso  = false
  &Mensagem = "Pedido não encontrado"
  return
endif

if &Pedido.PedidoStatus <> "Pendente"
  &Sucesso  = false
  &Mensagem = "Pedido não está pendente"
  return
endif

// Verificar estoque de cada item
for each PedidoItem
  where PedidoId = &PedidoId

  if PedidoItemQtd > ProdutoEstoque
    &Sucesso  = false
    &Mensagem = "Estoque insuficiente para: " + ProdutoNome
    return
  endif
endfor

// Aprovar e baixar estoque
&Pedido.PedidoStatus = "Aprovado"
&Pedido.Save()

// Baixar estoque de cada item
for each PedidoItem
  where PedidoId = &PedidoId

  &Produto.Load(ProdutoId)
  &Produto.ProdutoEstoque = &Produto.ProdutoEstoque - PedidoItemQtd
  &Produto.Save()
endfor

&Sucesso  = true
&Mensagem = "Pedido aprovado com sucesso"

Procedure RelatorioPedidosPorPeriodo

Rules:

Parm(in: &DataInicio, in: &DataFim, out: &Relatorio);

Source:

for each Pedido
  where PedidoData >= &DataInicio
  where PedidoData <= &DataFim
  order PedidoData

  &Linha = new()
  &Linha.PedidoId     = PedidoId
  &Linha.ClienteNome  = ClienteNome
  &Linha.PedidoData   = PedidoData
  &Linha.PedidoStatus = PedidoStatus
  &Linha.PedidoTotal  = PedidoTotal
  &Relatorio.Add(&Linha)
endfor

Interface (Cap. 8-9)

Work With para CRUD

Aplique Work With Pattern nas Transactions Cliente e Produto para gerar CRUDs completos automaticamente.

Web Panel customizada: Dashboard de Pedidos

// WPDashboard - Events

Event Start
  &DataInicio = &Today.AddMonths(-1)
  &DataFim    = &Today
EndEvent

Event 'Filtrar'
  Grid1.Search()
EndEvent

Event 'Aprovar'
  AprovarPedido(PedidoId, &Sucesso, &Mensagem)
  if &Sucesso
    msg(&Mensagem)
    Grid1.Search()  // Recarregar lista
  else
    msg(&Mensagem)
  endif
EndEvent

Conditions do Grid:

PedidoData >= &DataInicio when not &DataInicio.IsEmpty();
PedidoData <= &DataFim when not &DataFim.IsEmpty();
PedidoStatus = &FiltroStatus when not &FiltroStatus.IsEmpty();

API REST (Cap. 10)

Exponha os Procedures como API:

Endpoint Procedure Método
GET /api/pedidos RelatorioPedidosPorPeriodo GET
POST /api/pedidos/aprovar AprovarPedido POST
GET /api/clientes APIListarClientes GET
POST /api/clientes APICriarCliente POST

Checklist Final

  • Transactions criadas: Cliente, Produto, Pedido
  • Rules de validação em todas as Transactions
  • Business Components habilitados em Cliente, Produto, Pedido
  • Procedures de lógica: AprovarPedido, RelatorioPedidosPorPeriodo
  • Work With aplicado em Cliente e Produto
  • Web Panel customizada: Dashboard de Pedidos
  • APIs REST expostas
  • Build + Reorganize executados
  • Teste: criar cliente, produto, pedido com itens, aprovar pedido

Capítulo 14 — Próximos Passos

Recursos Avançados

Após dominar os fundamentos cobertos neste ebook, explore:

  • Panels para Mobile — gere apps nativos Android/iOS a partir do mesmo modelo
  • GeneXus BPM — fluxos de trabalho (workflows) para processos de negócio
  • GeneXus BI — dashboards e relatórios analíticos integrados
  • Chatbots/Conversational — crie assistentes conversacionais com NLP integrado
  • Módulos e Marketplace — reutilize componentes prontos da comunidade

Comunidade e Documentação

  • GeneXus Wiki — documentação oficial com exemplos detalhados de cada objeto e propriedade
  • GeneXus Training — cursos online oficiais (gratuitos e pagos)
  • GeneXus Community — fórum com perguntas e respostas da comunidade
  • Encuentro GeneXus — conferência anual com workshops e palestras
  • YouTube GeneXus — vídeos tutoriais e webinars

Certificação GeneXus

A GeneXus oferece certificações oficiais que validam seu conhecimento:

  • GeneXus Analyst — nível inicial, cobre Transactions, Rules, Procedures
  • GeneXus Senior Analyst — nível avançado, cobre arquitetura, performance, deploy
  • GeneXus Technical — focado em configuração de ambientes e infraestrutura

Dica final: O maior erro de quem vem de linguagens tradicionais é tentar "programar" tudo no GeneXus como faria em TypeScript. A mentalidade correta é: declare o que você quer, não como fazer. Deixe o GeneXus resolver a implementação. Quanto mais você confia no modelo declarativo, mais produtivo se torna.

Comentários