No vídeo de hoje, vamos demonstrar em como exportar dados do TCloud para usar em um BI de uma base local.
Hoje, a dúvida foi feita pelo grande Bruno, onde havia a necessidade de integrar o TCloud com algum sistema de BI, como o PowerBI.
O procedimento feito, foi criar dois fontes, um que roda exportando os dados do Cloud, e outro que roda na base local, importando esses dados.
Acontece que esse procedimento demonstrado no vídeo, é um paliativo de rotinas de exportação (do Cloud) e importação (uma base local), o ideal mesmo é se quiser usar o BI com Cloud, é assinar com a TOTVS a versão que eles disponibilizam acesso ao banco de dados.
E abaixo o código fonte desenvolvido para exemplificar. Fonte zExporta (exportação dos dados do Cloud):
//Bibliotecas
#Include "TOTVS.ch"
#Include "TopConn.ch"
#Include "FileIO.ch"
/*/{Protheus.doc} User Function zExporta
Função que realiza a exportação das tabelas para arquivos txt dentro da Protheus Data
@type Function
@author Atilio
@since 12/08/2021
@version version
@obs Esse fonte tem que ser compilado e executado na base TCloud
/*/
User Function zExporta()
Local aArea
Local lContinua := .F.
Private lJobPvt := .F.
//Se não tiver ambiente aberto, é job
If Select("SX2") == 0
//Reseta o ambiente e abre ele novamente
RpcClearEnv()
RpcSetEnv("01", "0101", "usuario", "senha", "")
lJobPvt := .T.
lContinua := .T.
Else
lContinua := MsgYesNo("Deseja executar a exportação geral de dados para o Banco Local?", "Atenção")
EndIf
aArea := GetArea()
//Se for continuar, irá chamar a rotina de processamento
If lContinua
If ! LockByName("zExporta", .T., .F.)
Else
Processa({|| fExporta() }, "Exportando...")
UnLockByName("zExporta", .T., .F.)
EndIf
EndIf
RestArea(aArea)
Return
Static Function fExporta()
Local aArea := GetArea()
Local cPasta := "\x_bancolocal\"
Local cArquivo := ""
Local nAtual := 0
Local nTotal := 0
Local cTabAtu := ""
Local aTabelas := {}
Local cQryExp := ""
Local cCabecalho := ""
Local nCampo := 0
Local aEstrutTab := {}
Private cDelim := ''
//Se a pasta não existir, cria a pasta
If ! ExistDir(cPasta)
MakeDir(cPasta)
EndIf
aTabelas := {"SB1", "SBM"} //aqui você pode por as outras tabelas
nTotal := Len(aTabelas)
//Enquanto houver dados
For nAtual := 1 To Len(aTabelas)
//Incrementa a régua
nAtual++
IncProc("Exportando tabela " + Alltrim(cTabAtu) + " (" + cValToChar(nAtual) + " de " + cValToChar(nTotal) + ")...")
//Busca a estrutura da tabela
cTabAtu := aTabelas[nAtual]
DbSelectArea(cTabAtu)
aEstrutTab := (cTabAtu)->(DbStruct())
//Monta a query de exportação
cQryExp := " SELECT " + CRLF
cQryExp += " R_E_C_N_O_ AS RECNO, " + CRLF
cQryExp += " D_E_L_E_T_ AS DELET " + CRLF
For nCampo := 1 To Len(aEstrutTab)
cQryExp += ", " + CRLF
//Se for campo caractere, irá remover as vírgulas para não dar problema na importação
// Por algum motivo, sem o substring, ele estourava o campo, como se
// ele entendesse uma estrutura nova aumentando o tamanho do campo
If aEstrutTab[nCampo][2] == "C"
cQryExp += " SUBSTRING( REPLACE(REPLACE(REPLACE( REPLACE(" + aEstrutTab[nCampo][1] + ", ',', ''), CHAR(13), '\n'), CHAR(10), ''), '''', '') , 1, " + cValToChar(aEstrutTab[nCampo][3]) + ") AS " + aEstrutTab[nCampo][1]
ElseIf aEstrutTab[nCampo][2] == "M"
cQryExp += " REPLACE(REPLACE(REPLACE( REPLACE(ISNULL(CAST(CAST(" + aEstrutTab[nCampo][1] + " AS VARBINARY(8000)) AS VARCHAR(8000)),''), ',', ''), CHAR(13), '\n'), CHAR(10), ''), '''', '') AS " + aEstrutTab[nCampo][1]
//Senão (numérico, lógico ou data), adiciona direto
Else
cQryExp += " " + aEstrutTab[nCampo][1]
EndIf
Next
cQryExp += CRLF
cQryExp += " FROM " + CRLF
cQryExp += " " + RetSQLName(cTabAtu) + " TAB " + CRLF
cQryExp += " WHERE " + CRLF
cQryExp += " D_E_L_E_T_ = '' " + CRLF
cQryExp += " ORDER BY " + CRLF
cQryExp += " RECNO " + CRLF
//Coloco a flag como T de Total, caso você mude a query para gerar poucos registros, ou com filtros de período, use a flag P de parcial
cTipoExp := "T"
//Executa a query e realiza a exportação
PLSQuery(cQryExp, 'QRY_EXP')
If ! QRY_EXP->(EoF())
//Monta o nome do arquivo que será gerado, e se ele existir, já apaga da Protheus Data
cArquivo := cTipoExp + "_" + Alltrim(cTabAtu) + "_" + dToS(Date()) + "_" + StrTran(Time(), ":", "-") + ".txt"
If File(cPasta + cArquivo)
FErase(cPasta + cArquivo)
EndIf
//Realiza a exportação
Copy To (cPasta + cArquivo) DELIMITED WITH (cDelim)
cCabecalho := fCabecalho()
//Insere o cabeçalho com o nome das colunas no arquivo
nHandle := FOpen(cPasta + cArquivo, FO_READWRITE + FO_SHARED )
FSeek(nHandle, 0, FS_SET)
FWrite(nHandle, cCabecalho + CRLF, Len(cCabecalho + CRLF))
FClose(nHandle)
EndIf
QRY_EXP->(DbCloseArea())
Next
RestArea(aArea)
Return
Static Function fCabecalho()
Local nCampo := 0
Local aEstrut := QRY_EXP->(DbStruct())
Local cCabecalho := ""
//Percorre a estrutura de campos e adiciona na linha de cabeçalho
For nCampo := 1 To Len(aEstrut)
If ! Empty(cCabecalho)
cCabecalho += ','
EndIf
cCabecalho += cDelim + Alltrim(aEstrut[nCampo][1]) + cDelim
Next
Return cCabecalho
Fonte zImporta (importação dos dados para base local):
//Bibliotecas
#Include "TOTVS.ch"
#Include "TopConn.ch"
/*/{Protheus.doc} User Function zImporta
Função que realiza a importação dos arquivos da Protheus Data para a Base Local
@type Function
@author Atilio
@since 19/08/2021
@version version
@obs Esse fonte tem que ser compilado e executado na base Local
Para a gravação dos logs, foi necessário criar duas tabelas
Tabela ZZ1 - Log da Integração
ZZ1_CODIGO, Código Sequencial, caractere, tamanho 9
ZZ1_ARQUIV, Nome do Arquivo, caractere, tamanho 50
ZZ1_EXPDAT, Data da exportação, data, tamanho 8
ZZ1_EXPHOR, Hora da exportação, caractere, tamanho 8
ZZ1_IMPDAT, Data da importação, data, tamanho 8
ZZ1_IMPHOR, Hora da importação, caractere, tamanho 8
Tabela ZZ2 - Vinculo de Recnos
ZZ2_CODIGO, Código Sequencial, caractere, tamanho 9
ZZ2_TABELA, Alias da Tabela, caractere, tamanho 3
ZZ2_EXPREC, Recno da exportação, numerico, tamanho 16
ZZ2_IMPREC, Recno da importação, numerico, tamanho 16
ZZ2_DATA, Data, data, tamanho 8
ZZ2_HORA, Hora, caractere, tamanho 8
/*/
User Function zImporta()
Local aArea
Local lContinua := .F.
Private lJobPvt := .F.
//Se não tiver ambiente aberto, é job
If Select("SX2") == 0
RpcSetEnv("99", "01", "usuario", "senha", "GPE")
lJobPvt := .T.
lContinua := .T.
Else
lContinua := MsgYesNo("Deseja executar a importação dos arquivos para o Banco Local?", "Atenção")
EndIf
aArea := GetArea()
//Se for continuar, irá chamar a rotina de processamento
If lContinua
If ! LockByName("zImporta", .T., .F.)
Else
Processa({|| fImporta() }, "Importando...")
UnLockByName("zImporta", .T., .F.)
EndIf
EndIf
RestArea(aArea)
Return
Static Function fImporta()
Local cPasta := "\x_bancolocal\"
Local aArquivos := {}
Local nAtual := 0
Local cArqAtu := ""
Local cTabela := ""
Local oArquivo
Local nLinha := 0
Local nColuna := 0
Local aCabecalho := {}
Local nPosRecno := 0
Local nPosDelet := 0
Local lOperacao
Local nIndAtu
Local lFalhou
Private cCodigoZZ2 := StrTran(Space(TamSX3("ZZ2_CODIGO")[1]), ' ', '0')
//Se a pasta não existir, cria a pasta
If ! ExistDir(cPasta)
MakeDir(cPasta)
EndIf
fBxCloud(cPasta)
//Buscando os arquivos txt
aDir(cPasta + "*.txt", aArquivos)
//Ordena o array, para pegar os totais primeiro T_**** e depois os parciais P_****
aSort(aArquivos,,, { |x, y| x > y })
//Define o tamanho da régua
ProcRegua(Len(aArquivos))
//Se houver arquivos
If Len(aArquivos) != 0
//Busca a última ZZ2 apenas 1 vez
cQryUlt := " SELECT MAX(ZZ2_CODIGO) AS ULTIMO FROM " + RetSQLName("ZZ2") + " ZZ2 "
TCQuery cQryUlt New Alias "QRY_ULT"
//Se houver dados
If ! QRY_ULT->(EoF())
//E o último for válido
If ! Empty(QRY_ULT->ULTIMO)
cCodigoZZ2 := QRY_ULT->ULTIMO
EndIf
EndIf
QRY_ULT->(DbCloseArea())
EndIf
//Percorre os arquivos
For nAtual := 1 To Len(aArquivos)
//Incrementa a régua
IncProc("Importando arquivo " + cValToChar(nAtual) + " de " + cValToChar(Len(aArquivos)) + "...")
//Pega o arquivo
cArqAtu := aArquivos[nAtual]
//Se o arquivo já foi importado anteriormente, pula o registro
If fJaImport(cArqAtu)
FErase(cArqAtu)
Loop
EndIf
//Pega a tabela
cTipoExp := SubStr(cArqAtu, 1, 1)
cTabela := SubStr(cArqAtu, 3, 3)
oArquivo := FWFileReader():New(cPasta + cArqAtu)
//Se for Exportação Total (desde o primeiro recno), executa um ZAP para apagar os dados da tabela
If cTipoExp == "T"
DbSelectArea(cTabela)
cQryDel := " DELETE FROM " + RetSQLName(cTabela)
If TCSqlExec(cQryDel) != 0
nAtual++
Loop
EndIf
EndIf
//Se o arquivo pode ser aberto
If (oArquivo:Open())
//Busca o índice 1 da tabela - Para algumas tabelas, é necessário mudar o índice, por exemplo SD2 usar o índice 3 por causa de chave única da tabela
DbSelectArea(cTabela)
If cTabela == "SD2"
(cTabela)->(DbSetOrder(3))
Else
(cTabela)->(DbSetOrder(1))
EndIf
cIndice := IndexKey(IndexOrd())
aIndice := Separa(cIndice, "+")
aPosInd := {}
//Se não for fim do arquivo
If ! (oArquivo:EoF())
nLinha := 0
nPosRecno := 0
nPosDelet := 0
//Enquanto tiver linhas
While (oArquivo:HasLine())
nLinha++
//Pegando a linha atual e transformando em array
cLinAtu := oArquivo:GetLine()
aLinha := Separa(cLinAtu, ",")
//Se for a linha 1, é cabeçalho
If nLinha == 1
aCabecalho := aClone(aLinha)
nPosRecno := aScan(aCabecalho, {|x| x == "RECNO"})
nPosDelet := aScan(aCabecalho, {|x| x == "DELET"})
For nIndAtu := 1 To Len(aIndice)
If "DTOS" $ aIndice[nIndAtu]
aIndice[nIndAtu] := StrTran(aIndice[nIndAtu], "DTOS(", "")
aIndice[nIndAtu] := StrTran(aIndice[nIndAtu], ")", "")
EndIf
//Posição 1 do aPosInd = campo, posição 2 = número da coluna do array
nPosInd := aScan(aCabecalho, {|x| Alltrim(x) == Alltrim(aIndice[nIndAtu])})
aAdd(aPosInd, {aIndice[nIndAtu], nPosInd})
Next
ElseIf Len(aLinha) > 0
DbSelectArea(cTabela)
lOperacao := .T.
//Se o recno for igual a 0, registro inválido, pula
If Val(aLinha[nPosRecno]) == 0 .Or. fRecInvalid(aLinha[nPosRecno]) .Or. Len(Alltrim(aLinha[nPosRecno])) > 18
Loop
EndIf
aLinha[nPosRecno] := Val(aLinha[nPosRecno])
aLinha[nPosRecno] := cValToChar(aLinha[nPosRecno])
//Se for Parcial
If cTipoExp == "P"
lFalhou := .F.
cQryTab := " SELECT " + CRLF
cQryTab += " ZZ2_IMPREC " + CRLF
cQryTab += " FROM " + CRLF
cQryTab += " " + RetSQLName("ZZ2") + " ZZ2 " + CRLF
cQryTab += " WHERE " + CRLF
cQryTab += " ZZ2_FILIAL = '" + FWxFilial("ZZ2") + "' " + CRLF
cQryTab += " AND ZZ2_TABELA = '" + cTabela + "' " + CRLF
cQryTab += " AND ZZ2_EXPREC = '" + aLinha[nPosRecno] + "' " + CRLF
cQryTab += " AND ZZ2.D_E_L_E_T_ = ' ' " + CRLF
cQryTab += " LIMIT 1 " + CRLF
TCQuery cQryTab New Alias "QRY_TAB"
//Se existir, será alteracao
If ! QRY_TAB->(EoF())
lOperacao := .F.
(cTabela)->(DbGoTo(QRY_TAB->ZZ2_IMPREC))
//Senão, faz uma segunda query pela chave unica para ver se será inclusão ou alteração
Else
//Faz uma query e veja se existe os dados
cQryTab := " SELECT " + CRLF
cQryTab += " R_E_C_N_O_ AS REC " + CRLF
cQryTab += " FROM " + CRLF
cQryTab += " " + RetSQLName(cTabela) + " AS TAB " + CRLF
cQryTab += " WHERE " + CRLF
cQryTab += " 1 = 1 " + CRLF
For nIndAtu := 1 To Len(aPosInd)
cCampo := aPosInd[nIndAtu][1]
nColuna := aPosInd[nIndAtu][2]
If nColuna > Len(aLinha)
lFalhou := .T.
Exit
Else
cConteud := aLinha[nColuna]
If "_FILIAL" $ cCampo
cConteud := fTrataFil(cConteud)
EndIf
cQryTab += " AND " + cCampo + " = '" + cConteud + "' " + CRLF
EndIf
Next
TCQuery cQryTab New Alias "QRY_TAB2"
//Se existir, será alteracao
If ! QRY_TAB2->(EoF())
lOperacao := .F.
(cTabela)->(DbGoTo(QRY_TAB2->REC))
Else
lOperacao := .T.
EndIf
QRY_TAB2->(DbCloseArea())
EndIf
QRY_TAB->(DbCloseArea())
//Se a linha for inválida, pula
If lFalhou
Loop
EndIf
//Senão será inclusão
Else
lOperacao := .T.
EndIf
//Somente se bater o número de colunas da linha com o cabeçalho
If Len(aCabecalho) == Len(aLinha)
//Trava o registro
RecLock(cTabela, lOperacao)
//Percorre as colunas
For nColuna := 1 To Len(aLinha)
//Se a coluna existir
If nColuna <= Len(aCabecalho) .And. FieldPos(aCabecalho[nColuna]) > 0
//Se o tipo dos campos for diferente
If ValType((cTabela)->(&(aCabecalho[nColuna]))) != ValType(aLinha[nColuna])
//Campo numérico
If ValType((cTabela)->(&(aCabecalho[nColuna]))) == "N"
aLinha[nColuna] := Val(aLinha[nColuna])
//Campo data
ElseIf ValType((cTabela)->(&(aCabecalho[nColuna]))) == "D"
aLinha[nColuna] := sToD(aLinha[nColuna])
//Campo caractere
Else
aLinha[nColuna] := cValToChar(aLinha[nColuna])
EndIf
EndIf
//Se for um campo numérico
If ValType((cTabela)->(&(aCabecalho[nColuna]))) == "N"
//Se o conteúdo vindo, for estourar o campo, gravo como -1 para identificar
// depois para aumentar o campo
If "*" $ Alltrim(Transform(aLinha[nColuna], PesqPict(cTabela, aCabecalho[nColuna])))
aLinha[nColuna] := -1
EndIf
EndIf
//Se for um campo MEMO, irá substituir o \n por char 13 e 10
If GetSX3Cache(aCabecalho[nColuna], "X3_TIPO") == "M"
aLinha[nColuna] := StrTran(aLinha[nColuna], "\n", CRLF)
//Se for um campo Numérico, define o tamanho de decimais
ElseIf GetSX3Cache(aCabecalho[nColuna], "X3_TIPO") == "N"
aLinha[nColuna] := NoRound(aLinha[nColuna], TamSX3(aCabecalho[nColuna])[2])
EndIf
//Se for um campo filial, trata o conteudo para gravar
If "_FILIAL" $ aCabecalho[nColuna]
aLinha[nColuna] := fTrataFil(aLinha[nColuna])
EndIf
//Grava o campo
(cTabela)->(&(aCabecalho[nColuna])) := aLinha[nColuna]
EndIf
Next
//Destrava o registro
(cTabela)->(MsUnlock())
//Se existir a posição do RecDel e ele estiver preenchido, deleta o registro
If nPosDelet != 0 .And. Len(aLinha) >= nPosDelet .And. ! Empty(aLinha[nPosDelet])
RecLock(cTabela, .F.)
DbDelete()
(cTabela)->(MsUnlock())
EndIf
//Somente se for inclusão
If lOperacao
fIncLogTab(cTabela, Val(aLinha[nPosRecno]), (cTabela)->(RecNo()))
EndIf
EndIf
EndIf
EndDo
Else
// MsgStop("Arquivo [" + cArqAtu + "] não tem conteúdo!", "Atenção")
EndIf
//Fecha o arquivo e exclui da Protheus Data da base local
oArquivo:Close()
FErase(cPasta + cArqAtu)
fIncLog(cArqAtu)
Else
// MsgStop("Arquivo [" + cArqAtu + "] não pode ser aberto!", "Atenção")
EndIf
Next
Return
Static Function fTrataFil(cFilDado)
Local aArea := GetArea()
Local cFilNov := cFilDado
cFilNov := StrTran(cFilNov, "01", "A")
cFilNov := StrTran(cFilNov, "02", "B")
cFilNov := StrTran(cFilNov, "03", "C")
cFilNov := StrTran(cFilNov, "04", "D")
cFilNov := StrTran(cFilNov, "05", "E")
cFilNov := StrTran(cFilNov, "06", "F")
cFilNov := StrTran(cFilNov, "07", "G")
cFilNov := StrTran(cFilNov, "08", "H")
cFilNov := StrTran(cFilNov, "09", "I")
cFilNov := StrTran(cFilNov, " ", " ")
RestArea(aArea)
Return cFilNov
Static Function fIncLog(cArquivo)
Local aArea := GetArea()
Local cCodigo := GetSXENum("ZZ1", "ZZ1_CODIGO")
Local dDataArq := sToD(SubStr(cArquivo, 7, 8))
Local cHoraArq := StrTran(SubStr(cArquivo, 16, 8), '-', ':')
//Inclui o Log
DbSelectArea("ZZ1")
RecLock("ZZ1", .T.)
ZZ1->ZZ1_FILIAL := FWxFilial("ZZ1")
ZZ1->ZZ1_CODIGO := cCodigo
ZZ1->ZZ1_ARQUIV := cArquivo
ZZ1->ZZ1_EXPDAT := dDataArq
ZZ1->ZZ1_EXPHOR := cHoraArq
ZZ1->ZZ1_IMPDAT := Date()
ZZ1->ZZ1_IMPHOR := Time()
ZZ1->(MsUnlock())
RestArea(aArea)
Return
Static Function fIncLogTab(cTabela, nRecExpor, nRecImpor)
//Local aArea := GetArea()
//Local cCodigo := GetSXENum("ZZ2", "ZZ2_CODIGO")
cCodigoZZ2 := Soma1(cCodigoZZ2)
//Inclui o Log
DbSelectArea("ZZ2")
RecLock("ZZ2", .T.)
ZZ2->ZZ2_FILIAL := FWxFilial("ZZ2")
ZZ2->ZZ2_CODIGO := cCodigoZZ2
ZZ2->ZZ2_TABELA := cTabela
ZZ2->ZZ2_EXPREC := nRecExpor
ZZ2->ZZ2_IMPREC := nRecImpor
ZZ2->ZZ2_DATA := Date()
ZZ2->ZZ2_HORA := Time()
ZZ2->(MsUnlock())
//RestArea(aArea)
Return
Static Function fJaImport(cArquivo)
Local aArea := GetArea()
Local lExiste := .F.
Local cQryArq := ""
//Busca na base se o arquivo já foi importado
cQryArq := " SELECT " + CRLF
cQryArq += " ZZ1_ARQUIV " + CRLF
cQryArq += " FROM " + CRLF
cQryArq += " " + RetSQLName("ZZ1") + " ZZ1 " + CRLF
cQryArq += " WHERE " + CRLF
cQryArq += " ZZ1_FILIAL = '" + FWxFilial("ZZ1") + "' " + CRLF
cQryArq += " AND ZZ1_ARQUIV = '" + cArquivo + "' " + CRLF
cQryArq += " AND ZZ1.D_E_L_E_T_ = ' ' " + CRLF
TCQuery cQryArq New Alias "QRY_EXIST"
//Se existem dados
If ! QRY_EXIST->(EoF())
lExiste := .T.
EndIf
QRY_EXIST->(DbCloseArea())
RestArea(aArea)
Return lExiste
Static Function fRecInvalid(cRecno)
Local lRet := .F.
Local cLetras := "ABCDEFGHIJKLMNOPQRSTUVWXYZ\()Ç"
Local nLetra := 0
//Deixa tudo maiusculo o texto do recno
cRecno := Alltrim(Upper(cRecno))
//Percorre as letras
For nLetra := 1 To Len(cLetras)
//Se a letra atual estive no texto, o recno é inválido
If SubStr(cLetras, nLetra, 1) $ cRecno
lRet := .T.
Exit
EndIf
Next
Return lRet
Static Function fBxCloud(cPastaLoc)
Local cDirLocal := "C:\TOTVS\ERP\Protheus_Data" + cPastaLoc
Local cDirCloud := "C:\TOTVS\Cloud\smartclient\"
Local cPrograma := "u_zCloudBx"
Local cComunica := "tcp"
Local cAmbiente := "PRODUCAO01"
//Executa o smartclient do Cloud para copiar os arquivos para a Protheus Data local
WaitRun(cDirCloud + "smartclient.exe -c=" + cComunica + " -e=" + cAmbiente + " -m -q -p=" + cPrograma + " -a=" + cDirLocal , 1)
Return
/*/{Protheus.doc} User Function zCloudBx
Função que percorre a pasta na Protheus Data do Cloud e copia os arquivos para a máquina local
@type Function
@author Atilio
@since 26/08/2021
@version version
/*/
User Function zCloudBx(cPastaLocal)
Local cPasta := "\x_bancolocal\"
Local cPastaOld := "\x_bancolocal\old\"
Local aArquivos := {}
Local nAtual := 0
Local cDataArqui := ""
Default cPastaLocal := ""
//Se a pasta antiga não existir, cria
If ! ExistDir(cPastaOld)
MakeDir(cPastaOld)
EndIf
//Se tiver pasta local
If ! Empty(cPastaLocal)
//Busca todos os TXT do Cloud
aDir(cPasta + "*.txt", aArquivos)
//Percorre os arquivos
For nAtual := 1 To Len(aArquivos)
cDataArqui := SubStr(aArquivos[nAtual], 7, 8)
//Copia o arquivo para a pasta local, em seguida para a pasta de backup
__CopyFile(cPasta + aArquivos[nAtual], cPastaLocal + aArquivos[nAtual])
__CopyFile(cPasta + aArquivos[nAtual], cPastaOld + aArquivos[nAtual])
//Exclui o arquivo original
FErase(cPasta + aArquivos[nAtual])
Next
EndIf
Return
Bom pessoal, por hoje é só.
Abraços e até a próxima.