ETL de banco externo pro Protheus

Como importar dados de SQL externo (legacy, CRM, ERP antigo) pro Protheus com transformacao. Padrao Extract-Transform-Load aplicado.

Migracao de sistema legado, integracao com CRM, sincronizacao com filial remota — tudo cai em ETL. Esse e o padrao tecnico pra trazer dados de fora pro Protheus de forma segura.

Fluxo classico ETL

┌────────────┐     ┌────────────┐     ┌────────────┐     ┌────────────┐
│ Source     │ ──→ │ Extract    │ ──→ │ Transform  │ ──→ │ Load       │
│ (SQL ext)  │     │ (TCQuery)  │     │ (mapping)  │     │ (Protheus) │
└────────────┘     └────────────┘     └────────────┘     └────────────┘

Extract: conectar no banco externo

// TCLink permite conectar em outro banco
nConn := TCLink("MyDriverODBC", "external_db", "user", "pass")

If nConn < 0
    ConOut("Erro de conexao: " + cValToChar(TCSqlError()))
    Return
EndIf

TCSetConn(nConn)

cQry := "SELECT * FROM clientes_legado WHERE migrado = 0"
TCQuery cQry New Alias "LEGACY"

// Volta pra conexao padrao Protheus depois
TCSetConn(0)

Transform: mapping de campos

Static Function MapearCliente(oLegacy)
    Local aCab := {}

    // Codigo: gerar novo seguindo padrao SX8
    Local cCodigo := GetSXENum("SA1", "A1_COD")

    aAdd(aCab, {"A1_FILIAL", xFilial("SA1"),                              NIL})
    aAdd(aCab, {"A1_COD",    cCodigo,                                      NIL})
    aAdd(aCab, {"A1_LOJA",   "01",                                         NIL})
    aAdd(aCab, {"A1_NOME",   AllTrim(oLegacy["nome"]),                    NIL})

    // Transform: legado tinha "PJ"/"PF", aqui e "J"/"F"
    aAdd(aCab, {"A1_PESSOA", If(oLegacy["tipo"] == "PJ", "J", "F"),       NIL})

    // Padronizar CNPJ — remover formatacao
    aAdd(aCab, {"A1_CGC",    StrTran(StrTran(StrTran(oLegacy["cnpj"], ".", ""), "/", ""), "-", ""), NIL})

    aAdd(aCab, {"A1_END",    AllTrim(oLegacy["endereco"]),                NIL})
    aAdd(aCab, {"A1_EMAIL",  Lower(AllTrim(oLegacy["email"])),            NIL})

    // Default values
    aAdd(aCab, {"A1_TIPO",   "F",                                          NIL})
    aAdd(aCab, {"A1_INSCR",  "ISENTO",                                     NIL})

Return aCab

Load: inserir via MsExecAuto

User Function ImportClientesLegacy()
    Local nOk := 0, nErro := 0
    Local aErros := {}

    // Conecta no legado
    nConn := TCLink("ODBC_LEGACY", "DB_LEGACY", "user", "pass")
    TCSetConn(nConn)
    TCQuery "SELECT * FROM clientes WHERE migrado = 0" New Alias "L"

    While !L->(Eof())
        // Cache do registro legado
        oLegacy := JsonObject():New()
        oLegacy["nome"]     := L->nome
        oLegacy["cnpj"]     := L->cnpj
        oLegacy["endereco"] := L->endereco
        oLegacy["email"]    := L->email
        oLegacy["tipo"]     := L->tipo
        oLegacy["id_orig"]  := L->id

        // Volta pra conexao Protheus
        TCSetConn(0)

        // Insere via MsExecAuto
        Local aCab := MapearCliente(oLegacy)
        Private lMsErroAuto := .F.

        MsExecAuto({|x,y| Mata030(x,y)}, aCab, 3)  // 3 = incluir

        If lMsErroAuto
            nErro++
            aAdd(aErros, {oLegacy["id_orig"], MostraErro()})
            DisarmTransaction()
        Else
            nOk++
            // Marcar como migrado no legado
            TCSetConn(nConn)
            TCSqlExec("UPDATE clientes SET migrado = 1 WHERE id = " + cValToChar(oLegacy["id_orig"]))
        EndIf

        // Loop: volta pra legado
        TCSetConn(nConn)
        L->(DBSkip())
    EndDo

    L->(DBCloseArea())
    TCSetConn(0)
    TCUnLink(nConn)

    ConOut("Migrados OK: " + cValToChar(nOk))
    ConOut("Erros:        " + cValToChar(nErro))

    // Gera relatorio de erros
    If Len(aErros) > 0
        U_LogErrosETL(aErros)
    EndIf
Return

Padroes essenciais

1. Idempotencia

Rodou 2x? Nao duplica. Use flag no legado (migrado = 1) ou marker no Protheus (A1_LEGACYID).

2. Batch processing

Em base com 100k+ registros, processe em lotes de 1000 com transacao explicita.

Begin Transaction
    // 1000 inclusoes
End Transaction
// Commit, prossegue para proximo lote

3. Logging detalhado

Cada falha → log com contexto pra investigacao posterior.

4. Dry run mode

Private lDryRun := .T.   // Em primeiro teste

If lDryRun
    ConOut("[DRY-RUN] Inseriria: " + oLegacy["nome"])
Else
    MsExecAuto(...)
EndIf

5. Reconciliacao

Apos migracao, conte registros nos dois lados — devem bater.

Pegadinhas comuns

Ferramentas alternativas

Veja também