advpl-dev
Especialista em desenvolvimento ADVPL/TLPP para o ecossistema TOTVS Protheus. Use esta skill para auxiliar com: escrita e revisão de código ADVPL e TLPP, criação de pontos de entrada (convencional e MVC), desenvolvimento de APIs REST, consultas SQL/Embedded SQL, acesso a tabelas do Protheus, criação de classes OO, estrutura MVC (Model-View-Controller), reports com SetPrintServer/FWMSPrinter, tratamento de erros, boas práticas de nomenclatura, performance e debugging. Ativar sempre que o usuário mencionar: ADVPL, TLPP, Protheus, TDS, ponto de entrada, RecLock, MsUnlock, BeginTran, MVC Protheus, SX (SX3, SX5, SB1 etc.), customização ERP, User Function, rotina MATA/SIGACOM/SIGAFAT/SIGAEST, ou qualquer desenvolvimento para o ecossistema TOTVS. Inclui referências a boas práticas do Guia Oficial TDN TOTVS.
ADVPL / TLPP — Skill de Desenvolvimento Protheus
Documentação de referência: TDN TOTVS (tdn.totvs.com) | Central de Atendimento TOTVS | TOTVS Developers
1. FUNDAMENTOS DA LINGUAGEM
1.1 Tipos de Dados
| Tipo | Prefixo | Exemplos | Notas |
|---|---|---|---|
| Caracter | c | cNome, cCodigo | Strings com aspas duplas |
| Numérico | n | nTotal, nQtd | Int e float unificados |
| Data | d | dEmissao, dVenc | Criada via CToD(), Date() |
| Lógico | l | lAtivo, lOk | .T. / .F. (.Y./.N. também ok) |
| Array | a | aItens, aParam | Índice começa em 1; máx 100.000 elem. |
| Objeto | o | oModel, oGrid | Instâncias de classes |
| Bloco | b | bValid, bWhen | Código armazenado para execução futura |
| NIL | x (genérico) | xRet | Variável não inicializada |
⚠️ ADVPL não é strongly typed — variáveis mudam de tipo dinamicamente. TLPP introduz tipagem forte.
1.2 Notação Húngara (convenção obrigatória)
Local cNome := "João" // c = Caracter
Local nSalario := 2500.00 // n = Numérico
Local dAdmiss := CToD("01/03/25") // d = Data
Local lAtivo := .T. // l = Lógico
Local aItens := {} // a = Array
Local oObj := NIL // o = Objeto
1.3 Escopo de Variáveis
| Comando | Escopo | Boas Práticas |
|---|---|---|
LOCAL | Função atual apenas | ✅ Preferir sempre — não vaza |
STATIC | Arquivo fonte; persiste entre chamadas | ⚠️ Usar para contadores/caches locais |
PRIVATE | Função atual + filhas (herança) | ⚠️ Apenas quando necessário |
PUBLIC | Todo o ambiente (sessão) | 🚫 Evitar; causa efeitos colaterais |
Regra de ouro: declare sempre
LOCALno topo da função; nunca use variáveis não declaradas.
1.4 Limitação de 10 Caracteres (ADVPL clássico)
// ❌ ERRADO — os dois nomes colapsam (mesmos 10 primeiros chars)
Local nTotalGeralAnual := 300
Local nTotalGeralMensal := 100 // MESMO que a anterior!
// ✅ CORRETO — diferenciar nos primeiros caracteres
Local nAnualTotal := 300
Local nMensalTotal := 100
TLPP resolve isso com suporte a nomes longos nativamente.
2. ESTRUTURA DE PROGRAMAS
2.1 Cabeçalho Padrão com Protheus.doc
#INCLUDE "PROTHEUS.CH"
#INCLUDE "TOTVS.CH"
/*/{Protheus.doc} MinhaFuncao
Breve descrição do que a função faz.
@type Function
@author Seu Nome
@since 2025-01-01
@version 1.0
@param cCodigo, Caracter, Código do produto
@param nQtd, Numérico, Quantidade a processar
@return lOk, Lógico, .T. se processou com sucesso
@see MATA010, SB1
/*/
User Function MinhaFuncao( cCodigo, nQtd )
Local lOk := .T.
// ... implementação
Return lOk
2.2 Tipos de Função
// Ponto de entrada — chamada pelo ERP (nome curto, máx 10 chars)
User Function MT010INC() // PE após inclusão em MATA010
// PARAMIXB traz os parâmetros do sistema
Local aParam := PARAMIXB
Return .T.
// Função auxiliar local ao arquivo
Static Function ValidaDados( cCod, nVal )
Return ( !Empty(cCod) .AND. nVal > 0 )
// Função pública (acessível via ExecBlock/CallFunc)
Function U_HelperFn()
Return NIL
3. BANCO DE DADOS — PADRÕES E ARMADILHAS
3.1 Acesso via ISAM (modo ERP)
// ✅ PADRÃO ERP: usar RecLock + MsUnlock + BeginTran/EndTran
BeginTran()
If RecLock("SB1", .F.) // .F. = alteração, .T. = inclusão
SB1->B1_DESC := "Novo Produto"
MsUnlock()
EndIf
EndTran()
// Para inclusão de novo registro:
BeginTran()
RecLock("SB1", .T.) // .T. = append/novo
SB1->B1_COD := "000001"
SB1->B1_DESC := "Produto Teste"
MsUnlock()
EndTran()
🚫 NUNCA misturar funções de Framework (
RecLock,MsUnlock,BeginTran) com funções de baixo nível (DBAppend,DBRUnlock,TCCommit) no mesmo processo.
3.2 Acesso via SQL (TCQuery / Embedded SQL)
Local cQuery := ""
Local cAliasQ := GetNextAlias()
cQuery := "SELECT B1_COD, B1_DESC "
cQuery += " FROM " + RetSqlName("SB1") + " SB1 "
cQuery += " WHERE B1_FILIAL = '" + xFilial("SB1") + "' "
cQuery += " AND SB1.D_E_L_E_T_ = ' ' " // Filtro de exclusão lógica
cQuery += " ORDER BY B1_COD"
TCQuery( cQuery, .T., cAliasQ )
If !(cAliasQ)->(EOF())
While !(cAliasQ)->(EOF())
ConOut("Produto: " + (cAliasQ)->B1_COD + " - " + (cAliasQ)->B1_DESC)
(cAliasQ)->(DBSkip())
EndDo
EndIf
(cAliasQ)->(DBCloseArea())
✅ Sempre filtrar
D_E_L_E_T_ = ' '(registros não excluídos logicamente). ✅ Sempre usarxFilial("ALIAS")no filtro de filial em consultas SQL. ✅ UsarRetSqlName()para obter o nome real da tabela no banco.
3.3 Posicionamento em Tabelas
// Busca com índice (preferível para performance)
If MsSeek( xFilial("SA1") + cCodCli, "SA1", "1" )
// Registro encontrado no índice 1 da SA1
cNomeCli := SA1->A1_NOME
EndIf
// Busca sem índice (evitar em volumes grandes)
SA1->(DBSetOrder(1))
If SA1->(DbSeek( xFilial("SA1") + cCodCli ))
cNomeCli := SA1->A1_NOME
EndIf
4. PONTOS DE ENTRADA (PE)
4.1 PE Convencional
#INCLUDE "PROTHEUS.CH"
/*/{Protheus.doc} MT010INC
Ponto de entrada executado após inclusão de produto em MATA010.
@type User Function
@author Dev
@since 2025-01-01
@param Nenhum (usa PARAMIXB)
@return NIL
/*/
User Function MT010INC()
Local aParam := PARAMIXB
// aParam[1] = código do produto incluído (conforme doc TDN)
ConOut("Produto incluído: " + SB1->B1_COD)
Return NIL
4.2 PE em MVC (padrão moderno — único PE por Model)
#INCLUDE "PROTHEUS.CH"
#INCLUDE "FWMVCDEF.CH"
/*/{Protheus.doc} GPEA010
Ponto de entrada único para fonte MVC GPEA010.
O ID do PE deve ser igual ao ID do Model de dados.
@type User Function
@param aParam, Array, Parâmetros via PARAMIXB
@return xRet, Variado, Depende do IDPonto executado
/*/
User Function GPEA010()
Local aParam := PARAMIXB
Local xRet := NIL
Local cIDPonto := ""
If ValType(aParam) == "A" .AND. Len(aParam) >= 1
cIDPonto := aParam[1] // Identifica qual momento do MVC chamou o PE
EndIf
Do Case
Case cIDPonto == "MODELINIT"
// Inicialização do model
xRet := .T.
Case cIDPonto == "MODELCOMMITTTS"
// Após gravação — dentro da transação
xRet := .T.
Case cIDPonto == "MODELCANCEL"
// Usuário cancelou
xRet := .F.
Case cIDPonto == "VIEWINIT"
// View foi inicializada
EndCase
Return xRet
⚠️ Em MVC: o nome do
User Function(PE) deve ser igual ao ID do Model, mas nunca igual ao nome do arquivo fonte.
5. DESENVOLVIMENTO MVC
5.1 Estrutura de um Fonte MVC Completo
#INCLUDE "PROTHEUS.CH"
#INCLUDE "FWMVCDEF.CH"
#INCLUDE "TOTVS.CH"
// ---- CONTROLLER ----
User Function MYFONT01()
Local aArea := GetArea()
Local oBrowse
oBrowse := FwMBrowse():New()
oBrowse:SetAlias("SB1")
oBrowse:SetDescription("Cadastro de Produtos")
oBrowse:Activate()
RestArea(aArea)
Return NIL
// ---- MODEL ----
Static Function ModelDef()
Local oModel := MPFormModel():New("MYFONT01MDL", /*bPre*/, /*bPost*/, /*bCommit*/, /*bCancel*/)
Local oStruct := FWFormStruct(1, "SB1")
oModel:AddFields("SB1MASTER", /*parent*/, oStruct)
oModel:SetPrimaryKey({"B1_COD"})
Return oModel
// ---- VIEW ----
Static Function ViewDef()
Local oView := FWFormView():New()
Local oModel := FWLoadModel("MYFONT01")
oView:SetModel(oModel)
oView:AddField("VIEW01", FWFormStruct(2, "SB1"), "SB1MASTER")
oView:CreateHorizontalBox("TOP", 100)
oView:SetOwnerView("VIEW01", "TOP")
Return oView
6. REST API (ADVPL / TLPP)
6.1 Endpoint REST com TLPP (recomendado)
// Arquivo: MYAPI.tlpp
#INCLUDE "TLPP-CORE.TH"
#INCLUDE "TLPP-MVC.TH"
Namespace MyApp.Api
@Get("/produtos")
Function u_GetProdutos() As Object
Local jResp As JsonObject
Local cQuery As Character
Local cAliasQ As Character
jResp := JsonObject():New()
cAliasQ := GetNextAlias()
cQuery := "SELECT B1_COD, B1_DESC FROM " + RetSqlName("SB1")
cQuery += " WHERE B1_FILIAL = '" + xFilial("SB1") + "'"
cQuery += " AND D_E_L_E_T_ = ' '"
TCQuery(cQuery, .T., cAliasQ)
Local aItens := {}
While !(cAliasQ)->(EOF())
Local jItem := JsonObject():New()
jItem["codigo"] := AllTrim((cAliasQ)->B1_COD)
jItem["descricao"] := AllTrim((cAliasQ)->B1_DESC)
AAdd(aItens, jItem)
(cAliasQ)->(DBSkip())
EndDo
(cAliasQ)->(DBCloseArea())
jResp["items"] := aItens
oRest:setResponse(jResp:toJson())
Return jResp
6.2 REST com ADVPL clássico (WSRestful)
#INCLUDE "PROTHEUS.CH"
#INCLUDE "APWEBSRV.CH"
// Necessário registrar no appserver.ini: [HTTPServer] / [REST]
WSRestful oMeuServico Description "Serviço de Produtos"
WsData oRest As Object
WSMethod GET Description "Lista produtos" wsSyntax "/produtos"
End WSRestful
WSMethod GET WsReceive oMeuServico
Local jResp := JsonObject():New()
jResp["status"] := "ok"
::SetContentType("application/json")
::SetResponse(jResp:toJson())
Return .T.
7. TRATAMENTO DE ERROS
// Try/Catch nativo do ADVPL
Begin Sequence With {|oErr| BreakOnError(oErr)}
// Código que pode falhar
RecLock("SB1", .F.)
SB1->B1_DESC := "Teste"
MsUnlock()
Recover With oErr
// oErr:Description, oErr:Operation, oErr:SubCode
MsgStop("Erro: " + oErr:Description, "Atenção")
ConOut("[ERRO] " + oErr:Description + " - " + oErr:Operation)
End Sequence
// Alternativa: função ErrorBlock
Local bErr := ErrorBlock({|e| MyErrHandler(e)})
// ... código ...
ErrorBlock(bErr) // Restaura handler anterior
Static Function MyErrHandler(oErr)
ConOut("Erro capturado: " + oErr:Description)
Return Break(oErr) // Interrompe o sequence
8. PERFORMANCE — REGRAS CRÍTICAS
- Evitar macroexecução desnecessária (
&cVar) — tem overhead elevado. PrefiraEval(bBloco). - Fechar áreas abertas — sempre fechar
TCQuerycomDBCloseArea()após uso. - Transações curtas — não deixar
BeginTran()aberto por longos períodos; aumenta locks. - Arrays grandes — evitar arrays com > 10.000 elementos; usar queries paginadas.
- TCQuery vs ISAM — para relatórios e leituras, SQL é mais rápido; para gravação no ERP, use RecLock/MsUnlock.
- ConOut para log — usar
ConOut()em JOBs (sem interface). Nunca usarAlert()em processos servidor. - Filtros SQL — sempre indexar filtros de WHERE com campos indexados; evitar
LIKE '%valor%'. - Não misturar funções de Framework com funções de baixo nível (DBAppend, DBRUnlock, TCCommit) no mesmo processo do ERP.
9. CONVENÇÕES DE NOMENCLATURA
| Elemento | Padrão | Exemplo |
|---|---|---|
| Arquivo fonte | 7 chars maiúsculos + extensão | MATA010.PRW, GPEA010.PRW |
| User Function | U_ + nome (para chamada externa) | U_ValidaCliente() |
| Static Function | PascalCase | ValidaDados() |
| Variável Local | Prefixo tipo + CamelCase | cNomeCli, nValTotal |
| Parâmetros | Mesmo padrão de variáveis | cCodigo, lAtivo |
| Constantes | UPPER_SNAKE_CASE | MAX_TENTATIVAS |
| Classes | PascalCase (sem limite de chars) | FwFormModel |
| Métodos | PascalCase | oModel:GetValue() |
| Includes | UPPER.CH | PROTHEUS.CH, TOTVS.CH |
10. INCLUDES ESSENCIAIS
#INCLUDE "PROTHEUS.CH" // Base do Protheus — sempre incluir
#INCLUDE "TOTVS.CH" // Framework geral
#INCLUDE "FWMVCDEF.CH" // Constantes MVC (MODEL_FIELD_WHEN, etc.)
#INCLUDE "PARMTYPE.CH" // Validação de tipos de parâmetros
#INCLUDE "APWEBSRV.CH" // WebServices REST/SOAP (clássico)
#INCLUDE "TLPP-CORE.TH" // Núcleo TLPP (somente em fontes .tlpp)
#INCLUDE "TLPP-MVC.TH" // MVC em TLPP
11. TLPP — DIFERENÇAS E MODERNIZAÇÃO
| Recurso | ADVPL clássico | TLPP |
|---|---|---|
| Tipagem | Dinâmica | Forte (As Character, As Numeric) |
| Nomes longos | Máx 10 chars | Ilimitado |
| Namespaces | Não | ✅ Namespace MinhaEmpresa.Modulo |
| Parâmetros nomeados | Não | ✅ fn(cNome := "João") |
| Try/Catch | Begin Sequence | ✅ Try / Catch / Finally |
| Annotations REST | Não | ✅ @Get, @Post, @Put |
| Extensão | .prw / .prg / .prx | .tlpp |
| Compatibilidade | — | Chama ADVPL e vice-versa |
12. CHECKLIST DE QUALIDADE
Antes de entregar um fonte ADVPL/TLPP, verificar:
- Todas as variáveis declaradas com
LOCAL(ou escopo correto) no topo da função - Notação Húngara aplicada em todas as variáveis
-
Protheus.docpreenchido (type, author, since, param, return) -
#INCLUDEdos headers necessários no topo - Filtro
D_E_L_E_T_ = ' 'em todas as queries SQL -
xFilial()nos filtros de tabelas multi-empresa/filial - Alias de TCQuery fechado com
DBCloseArea() -
BeginTran/EndTranenvolvendo operações de gravação -
RecLock/MsUnlockem par — nunca um sem o outro - Tratamento de erro com
Begin Sequenceou Try/Catch (TLPP) -
GetArea/RestAreapara preservar posicionamento de tabelas em funções auxiliares -
ConOut()para log em processos servidor/JOB (nuncaAlert/MsgInfo) - Nomes de funções com máximo 10 caracteres significativos (ADVPL clássico)
- PE MVC: ID do model ≠ nome do arquivo fonte
13. REFERÊNCIAS
Para detalhes adicionais, consulte os arquivos em references/:
references/funcoes-essenciais.md— catálogo de funções mais usadasreferences/tabelas-sx.md— estrutura das tabelas SX do dicionárioreferences/erros-comuns.md— erros frequentes e soluçõesreferences/rest-avancado.md— configuração completa do servidor REST
Links TDN:
- Guia de Boas Práticas ADVPL:
tdn.totvs.com/pages/releaseview.action?pageId=22480352 - Convenções da linguagem:
tdn.totvs.com/pages/viewpage.action?pageId=22480357 - REST TLPP:
tdn.totvs.com/display/tec/REST - Tipos de Dados:
tdn.totvs.com/display/tec/Tipos+de+Dados - Funções ADVPL:
tdn.totvs.com/display/tec/AdvPL+-+Functions