Impressão de Nota Promissória via AdvPL

Hoje será demonstrado um relatório para impressão de notas promissórias no sistema.

Para quem precisa imprimir notas promissórias no sistema, o Gabriel Dahm criou uma customização onde é aberto uma tela de parâmetros para informar o tipo (pagar ou receber), cliente / fornecedor e período.

Após confirmar é aberto um MarkBrowse para selecionar os títulos a serem impressos.

Marcando os registros e confirmando a operação, é processado o relatório e gerado. Abaixo um print de exemplo:

Exemplo da impressão
Exemplo da impressão

E abaixo o código fonte desenvolvido pelo Gabriel Dahm (LinkedIn):

// Bibliotecas
#include "TOTVS.ch"
#include "FWMVCDef.ch"
#include "TopConn.ch"
#Include "RPTDef.ch"

// Alinhamentos
#Define PAD_LEFT    0
#Define PAD_RIGHT   1
#Define PAD_CENTER  2

// Cores
#Define COR_PRETO   RGB(0,0,0)
#Define COR_CINZA   RGB(180,180,180)

/*/{Protheus.doc} User Function zPromiEmit
Função que realiza tela de parambox para receber parametro digitado pelo usuario e exibir um browse com os dados filtrados
@type  Function
@author Gabriel Dahm 
@since 25/08/2025
@version 1.0
/*/

User Function zPromiEmit()
    Local aArea    := FWGetArea()
    Local aPergs   := {}
    Private nTipo        := 3
    Private cForCliDe    := Space(TamSX3("A1_COD")[1])
    Private cForCliAte   := StrTran(cForCliDe, " ", "Z")
    Private dDataDe      := FirstDate(Date())
    Private dDataAt      := LastDate(dDataDe)

    aAdd(aPergs, {2, "Tipo do Filtro", nTipo, {"1=A Pagar", "2=A Receber", "3=Ambos"}, 090, ".T.", .F.}) // MV_PAR01
    aAdd(aPergs, {1, "Cliente / Fornecedor De",  cForCliDe,  "", ".T.", "", ".T.", 80,  .F.})           // MV_PAR02
    aAdd(aPergs, {1, "Cliente / Fornecedor Até", cForCliAte, "", ".T.", "", ".T.", 80,  .T.})           // MV_PAR03
    aAdd(aPergs, {1, "Data De",  dDataDe, "", ".T.", "", ".T.", 80, .F.})                               // MV_PAR04
    aAdd(aPergs, {1, "Data Até", dDataAt, "", ".T.", "", ".T.", 80, .T.})                               // MV_PAR05

    If ParamBox(aPergs, "Informe o tipo de filtro")
        nTipo      := Val(cValToChar(MV_PAR01))
        cForCliDe  := MV_PAR02
        cForCliAte := MV_PAR03
        dDataDe    := MV_PAR04
        dDataAt    := MV_PAR05
        fMontaTela()
    EndIf

    FWRestArea(aArea)
Return

/*/{Protheus.doc} User Function fMontaTela
Função que realiza a montagem da tela com browse para marcação dos dados
@type  Function
@author Gabriel Dahm 
@since 25/08/2025
@version 1.0
/*/

Static Function fMontaTela()
    Local aCampos    := {}
    Local aColunas   := {}
    Local cFontPad   := "Tahoma"
    Local oFontGrid  := TFont():New(cFontPad,,-14)
    Local oTempTable

    // Janela e componentes
    Private oDlgMark
    Private oPanGrid
    Private oMarkBrowse
    Private cAliasTmp := GetNextAlias()
    Private aRotina := MenuDef()

    // Tamanho da janela
    Private aTamanho := MsAdvSize()
    Private nJanLarg := aTamanho[5]
    Private nJanAltu := aTamanho[6]

    // Define campos da temporária
    aAdd(aCampos, { "OK",        "C",  1,  0 })
    aAdd(aCampos, { "Tipo",      "C", 17,  0 })
    aAdd(aCampos, { "Numero",    "C",  9,  0 })
    aAdd(aCampos, { "Prefixo",   "C",  3,  0 })
    aAdd(aCampos, { "Codigo",    "C",  6,  0 })
    aAdd(aCampos, { "Nome",      "C", 50,  0 })
    aAdd(aCampos, { "Valor",     "N", 16,  2 })
    aAdd(aCampos, { "Historico", "C", 50,  0 })
    aAdd(aCampos, { "Razao",       "C", 50,  0 })
    aAdd(aCampos, { "Endereco",    "C", 60,  0 })
    aAdd(aCampos, { "Vencimento", "D",  8,  0 })
    aAdd(aCampos, { "Complemen", "C", 60,  0 })
    aAdd(aCampos, { "Cnpj",        "C", 18,  0 })
    aAdd(aCampos, { "Filial",    "C",  4,  0 }) // Adicione na definição da temporária

    // Cria e popula a tabela temporária
    oTempTable := FWTemporaryTable():New(cAliasTmp)
    oTempTable:SetFields(aCampos)
    oTempTable:Create()

    Processa({|| fPopula()}, "Processando...")

    // Define colunas do browse
    aColunas := fCriaCols()

    // Cria a janela
    DEFINE MSDIALOG oDlgMark TITLE "Tela para Marcação de Títulos" FROM 000, 000 TO nJanAltu, nJanLarg COLORS 0, 16777215 PIXEL

        oPanGrid := tPanel():New(001, 001, "", oDlgMark, , , , RGB(000,000,000), RGB(254,254,254), nJanLarg - 2, nJanAltu - 60)

        //Dados
        oPanGrid := tPanel():New(001, 001, '', oDlgMark, /*oFont*/, /*lCentered*/, /*uParam7*/, RGB(000,000,000), RGB(254,254,254), (nJanLarg/2) - 1, (nJanAltu/2) - 1)
        oMarkBrowse := FWMarkBrowse():New()
        oMarkBrowse:SetAlias(cAliasTmp)                
        oMarkBrowse:SetDescription('Títulos Financeiros')
        oMarkBrowse:DisableFilter()
        oMarkBrowse:DisableConfig()
        oMarkBrowse:DisableSeek()
        oMarkBrowse:DisableSaveConfig()
        oMarkBrowse:SetFontBrowse(oFontGrid)
        oMarkBrowse:SetFieldMark('OK')
        oMarkBrowse:SetTemporary(.T.)
        oMarkBrowse:SetColumns(aColunas)
        oMarkBrowse:SetOwner(oPanGrid)

        // Legendas
        oMarkBrowse:AddLegend({|| (cAliasTmp)->Tipo == "Contas a Receber"}, "BLUE",  "Receber")
        oMarkBrowse:AddLegend({|| (cAliasTmp)->Tipo == "Contas a Pagar"  }, "GREEN", "Pagar")

        oMarkBrowse:Activate()

    ACTIVATE MSDIALOG oDlgMark CENTERED

    // Limpeza
    oTempTable:Delete()
    oMarkBrowse:DeActivate()
    If Type("oDlgMark") == "O" .And. oDlgMark != NIL
        // Adicione aqui qualquer limpeza adicional se necessário
    EndIf

Return

/*/{Protheus.doc} User Function fPopula
Função que popula a tabela temporária com os dados filtrados
@type  Function
@author Gabriel Dahm 
@since 25/08/2025
@version 1.0
/*/

Static Function fPopula()
    Local cQryDados := ""
    Local nTotal    := 0
    Local nAtual    := 0

    // Monta a query
    If nTipo == 2 .Or. nTipo == 3
        cQryDados += "SELECT" + CRLF
        cQryDados += "    'Contas a Receber' AS Tipo, " + CRLF
        cQryDados += "    E1_NUM AS Numero, " + CRLF
        cQryDados += "    E1_PREFIXO AS Prefixo, " + CRLF
        cQryDados += "    E1_CLIENTE AS Codigo, " + CRLF
        cQryDados += "    A1_NOME AS Nome, " + CRLF
        cQryDados += "    CAST(E1_VALOR AS NUMERIC(16,2)) AS Valor, " + CRLF
        cQryDados += "    E1_HIST AS Historico, " + CRLF
        cQryDados += "    A1_NOME AS Razao, " + CRLF
        cQryDados += "    A1_END AS Endereco, " + CRLF
        cQryDados += "    E1_VENCREA AS Vencimento, " + CRLF
        cQryDados += "    CONCAT(RTRIM(A1_END), ', ', RTRIM(A1_MUN), '/', RTRIM(A1_EST)) AS Complemen, " + CRLF
        cQryDados += "    CASE WHEN RTRIM(A1_CGC) = '' THEN '' " + CRLF
        cQryDados += "         WHEN LEN(A1_CGC) = 14 THEN " + CRLF
        cQryDados += "             CONCAT(SUBSTRING(A1_CGC,1,2),'.',SUBSTRING(A1_CGC,3,3),'.',SUBSTRING(A1_CGC,6,3),'/',SUBSTRING(A1_CGC,9,4),'-',SUBSTRING(A1_CGC,13,2)) " + CRLF
        cQryDados += "         WHEN LEN(A1_CGC) = 11 THEN " + CRLF
        cQryDados += "             CONCAT(SUBSTRING(A1_CGC,1,3),'.',SUBSTRING(A1_CGC,4,3),'.',SUBSTRING(A1_CGC,7,3),'-',SUBSTRING(A1_CGC,10,2)) " + CRLF
        cQryDados += "         ELSE A1_CGC END AS Cnpj, " + CRLF
        cQryDados += "    E1_FILIAL AS Filial " + CRLF // Adicione na query de contas a receber
        cQryDados += " FROM " + RetSQLName("SE1") + " SE1 " + CRLF
        cQryDados += " INNER JOIN " + RetSQLName("SA1") + " SA1 ON (" + CRLF
        cQryDados += "    A1_FILIAL = '" + FWxFilial("SA1") + "' " + CRLF
        cQryDados += "    AND A1_COD = E1_CLIENTE " + CRLF
        cQryDados += "    AND A1_LOJA = E1_LOJA " + CRLF
        cQryDados += "    AND SA1.D_E_L_E_T_ = ' ' ) " + CRLF
        cQryDados += "WHERE E1_TIPO = 'NP' " + CRLF
        cQryDados += "  AND E1_FILIAL = '" + FWxFilial("SE1") + "' " + CRLF
        cQryDados += "  AND E1_EMISSAO BETWEEN '" + dToS(dDataDe) + "' AND '" + dToS(dDataAt) + "' " + CRLF
        cQryDados += "  AND E1_CLIENTE BETWEEN '" + cForCliDe + "' AND '" + cForCliAte + "' " + CRLF
        cQryDados += "  AND E1_BAIXA = '' " + CRLF
        cQryDados += "  AND SE1.D_E_L_E_T_ = ' ' " + CRLF
    EndIf

    If nTipo == 3
        cQryDados += " UNION ALL " + CRLF
    EndIf

    If nTipo == 1 .Or. nTipo == 3
        cQryDados += "SELECT" + CRLF
        cQryDados += "    'Contas a Pagar' AS Tipo, " + CRLF
        cQryDados += "    E2_NUM AS Numero, " + CRLF
        cQryDados += "    E2_PREFIXO AS Prefixo, " + CRLF
        cQryDados += "    E2_FORNECE AS Codigo, " + CRLF
        cQryDados += "    A2_NOME AS Nome, " + CRLF
        cQryDados += "    CAST(E2_VALOR AS NUMERIC(16,2)) AS Valor, " + CRLF
        cQryDados += "    E2_HIST AS Historico, " + CRLF
        cQryDados += "    A2_NOME AS Razao, " + CRLF
        cQryDados += "    A2_END AS Endereco, " + CRLF
        cQryDados += "    E2_VENCREA AS Vencimento, " + CRLF
        cQryDados += "    CONCAT(RTRIM(A2_END), ', ', RTRIM(A2_MUN), '/', RTRIM(A2_EST)) AS Complemen, " + CRLF
        cQryDados += "    CASE WHEN RTRIM(A2_CGC) = '' THEN '' " + CRLF
        cQryDados += "         WHEN LEN(A2_CGC) = 14 THEN " + CRLF
        cQryDados += "             CONCAT(SUBSTRING(A2_CGC,1,2),'.',SUBSTRING(A2_CGC,3,3),'.',SUBSTRING(A2_CGC,6,3),'/',SUBSTRING(A2_CGC,9,4),'-',SUBSTRING(A2_CGC,13,2)) " + CRLF
        cQryDados += "         WHEN LEN(A2_CGC) = 11 THEN " + CRLF
        cQryDados += "             CONCAT(SUBSTRING(A2_CGC,1,3),'.',SUBSTRING(A2_CGC,4,3),'.',SUBSTRING(A2_CGC,7,3),'-',SUBSTRING(A2_CGC,10,2)) " + CRLF
        cQryDados += "         ELSE A2_CGC END AS Cnpj, " + CRLF
        cQryDados += "    E2_FILIAL AS Filial " + CRLF // Adicione na query de contas a pagar
        cQryDados += " FROM " + RetSQLName("SE2") + " SE2 " + CRLF
        cQryDados += " INNER JOIN " + RetSQLName("SA2") + " SA2 ON (" + CRLF
        cQryDados += "    A2_FILIAL = '" + FWxFilial("SA2") + "' " + CRLF
        cQryDados += "    AND A2_COD = E2_FORNECE " + CRLF
        cQryDados += "    AND A2_LOJA = E2_LOJA " + CRLF
        cQryDados += "    AND SA2.D_E_L_E_T_ = ' ' ) " + CRLF
        cQryDados += "WHERE E2_TIPO = 'NP' " + CRLF
        cQryDados += "  AND E2_FILIAL = '" + FWxFilial("SE2") + "' " + CRLF
        cQryDados += "  AND E2_EMISSAO BETWEEN '" + dToS(dDataDe) + "' AND '" + dToS(dDataAt) + "' " + CRLF
        cQryDados += "  AND E2_FORNECE BETWEEN '" + cForCliDe + "' AND '" + cForCliAte + "' " + CRLF
        cQryDados += "  AND E2_BAIXA = '' " + CRLF
        cQryDados += "  AND SE2.D_E_L_E_T_ = ' ' " + CRLF
    EndIf

    // Executa e popula
    PLSQuery(cQryDados, "QRYDADTMP")
    DbSelectArea("QRYDADTMP")
    Count To nTotal
    ProcRegua(nTotal)
    QRYDADTMP->(DbGoTop())

    While ! QRYDADTMP->(EoF())
        nAtual++
        IncProc("Analisando registro " + cValToChar(nAtual) + " de " + cValToChar(nTotal))
        RecLock(cAliasTmp, .T.)
            (cAliasTmp)->OK          := " "
            (cAliasTmp)->Tipo        := QRYDADTMP->Tipo
            (cAliasTmp)->Numero      := QRYDADTMP->Numero
            (cAliasTmp)->Prefixo     := QRYDADTMP->Prefixo
            (cAliasTmp)->Codigo      := QRYDADTMP->Codigo
            (cAliasTmp)->Nome        := QRYDADTMP->Nome
            (cAliasTmp)->Valor       := QRYDADTMP->Valor
            (cAliasTmp)->Historico   := QRYDADTMP->Historico
            (cAliasTmp)->Razao       := QRYDADTMP->Razao
            (cAliasTmp)->Endereco    := QRYDADTMP->Endereco
            (cAliasTmp)->Vencimento  := STOD(QRYDADTMP->Vencimento) // Converta string para data
            (cAliasTmp)->Complemen   := QRYDADTMP->Complemen
            (cAliasTmp)->Cnpj        := QRYDADTMP->Cnpj
            (cAliasTmp)->Filial      := QRYDADTMP->Filial // Popule o campo filial
        (cAliasTmp)->(MsUnlock())
        QRYDADTMP->(DbSkip())
    EndDo

    QRYDADTMP->(DbCloseArea())
    (cAliasTmp)->(DbGoTop())
Return

/*/{Protheus.doc} User Function fCriaCols
Função fCriaCols que cria as colunas do browse
@type  Function
@author Gabriel Dahm 
@since 25/08/2025
@version 1.0
/*/

Static Function fCriaCols()
    Local aColunas := {}
    Local aEstrut  := {}
    Local oColumn
    Local nI

    aAdd(aEstrut, { "Tipo",      "Tipo",       "C", 20, 0, "" })
    aAdd(aEstrut, { "Numero",    "Número",     "C", 10, 0, "" })
    aAdd(aEstrut, { "Prefixo",   "Prefixo",    "C",  5, 0, "" })
    aAdd(aEstrut, { "Codigo",    "Código",     "C", 10, 0, "" })
    aAdd(aEstrut, { "Nome",      "Nome",       "C", 50, 0, "" })
    aAdd(aEstrut, { "Valor",     "Valor",      "N", 16, 2, "@E 999,999,999.99" })
    aAdd(aEstrut, { "Historico", "Histórico",  "C", 50, 0, "" })
    
    For nI := 1 To Len(aEstrut)
        oColumn := FWBrwColumn():New()
        oColumn:SetData( &( "{|| " + cAliasTmp + "->" + aEstrut[nI][1] + " }" ) )
        oColumn:SetTitle(aEstrut[nI][2])
        oColumn:SetType(aEstrut[nI][3])
        oColumn:SetSize(aEstrut[nI][4])
        oColumn:SetDecimal(aEstrut[nI][5])
        oColumn:SetPicture(aEstrut[nI][6])
        aAdd(aColunas, oColumn)
    Next

Return aColunas

/*/{Protheus.doc} User Function MenuDef
Função Cria botão continuar no menu do browse
@type  Function
@author Gabriel Dahm 
@since 25/08/2025
@version 1.0
/*/

Static Function MenuDef()
    Local aRotina := {}

    ADD OPTION aRotina TITLE "Continuar" ACTION "u_zImprimeProm" OPERATION 2 ACCESS 0

Return aRotina


// Função para quebra automática por palavra
Static Function MyWordWrap(cTexto, nTam)
    Local aRet := {}
    Local cLinha := ""
    Local aPalavras := StrTokArr(cTexto, " ")
    Local nI

    For nI := 1 To Len(aPalavras)
        If Len(cLinha) + Len(aPalavras[nI]) + 1 <= nTam
            If Empty(cLinha)
                cLinha := aPalavras[nI]
            Else
                cLinha += " " + aPalavras[nI]
            EndIf
        Else
            aAdd(aRet, cLinha)
            cLinha := aPalavras[nI]
        EndIf
    Next
    If !Empty(cLinha)
        aAdd(aRet, cLinha)
    EndIf
Return aRet

/*/{Protheus.doc} User Function zImprimeProm
    Função feita para realizar a impressão da promissória conforme layout solicitado
    @type user function
    @author Gabriel Dahm
    @version 1.0
/*/
User Function zImprimeProm()
    Local oPrint
    Local nPageWidth := 600
    Local nMargin := 60
    Local nBoxWidth := nPageWidth - (2 * nMargin)
    Local nLogoWidth := 110
    Local nLogoHeight := 80
    Local nLinTop := 50
    Local nColLeft := nMargin
    Local nColTexto := nColLeft + nLogoWidth + 20
    Local nTextoWidth := nBoxWidth - nLogoWidth - 30
    Local dVenc, cNome, cEnd, cCnpj, nValor, cValorExt, cData, cDia, cMes, cAno, cComplemen
    Local nLinhaAtual, nBoxHeight
    Local cTexto, aLinhas, nI
    Local cFilAnt := FWFilial() // ou ajuste conforme sua filial
    Local cAssinatura
    Local cLogoEmp  := fLogoEmp()
    Local aSM0Data := FWSM0Util():GetSM0Data(, cFilAnt, {'M0_NOME', 'M0_CGC', 'M0_ENDENT', 'M0_ESTENT', 'M0_CIDENT'})
    Local cEmpNome   := aSM0Data[1][2] // Nome da empresa
    Local cEmpCNPJ   := aSM0Data[2][2] // CNPJ
    Local cEmpEnd    := aSM0Data[3][2] // Endereço
    Local cEmpEstado := aSM0Data[4][2] // Estado
    Local cEmpCidade := aSM0Data[5][2] // Cidade



    oPrint := FWMSPrinter():New("Promissoria.pdf", IMP_PDF, .F., "", .T.)
    oPrint:SetPortrait()
    oPrint:SetPaperSize(DMPAPER_A4)
    oPrint:SetMargin(10, 10, 10, 10)

    DbSelectArea(cAliasTmp)
    (cAliasTmp)->(DbGoTop())

    While !(cAliasTmp)->(EoF())
        If oMarkBrowse:IsMark()
            oPrint:StartPage()
            nBoxHeight := 120
            oPrint:Box(nLinTop, nColLeft, nLinTop + nBoxHeight, nColLeft + nBoxWidth)
            oPrint:Line(nLinTop, nColLeft + nLogoWidth, nLinTop + nBoxHeight, nColLeft + nLogoWidth)

            // Imprimindo logo Milênio
            If !Empty(cLogoEmp) .And. File(cLogoEmp)
                oPrint:SayBitmap(nLinTop + 10, nColLeft + 10, cLogoEmp, nLogoWidth - 20, nLogoHeight - 20)
            Else
                oPrint:Say(nLinTop + 40, nColLeft + 20, "Logo da Empresa", TFont():New("Arial",,10), nLogoWidth - 20, 20, COR_PRETO, PAD_CENTER, 0)
            EndIf

            dVenc      := (cAliasTmp)->Vencimento
            cNome      := (cAliasTmp)->Nome
            cEnd       := (cAliasTmp)->Endereco
            cCnpj      := (cAliasTmp)->Cnpj
            nValor     := (cAliasTmp)->Valor
            cValorExt  := Extenso(nValor)
            cData      := DtoC(dVenc)
            cDia       := SubStr(cData,1,2)
            cMes       := SubStr(cData,4,2)
            cAno       := SubStr(cData,7,4)
            cComplemen := (cAliasTmp)->Complemen
            cValorFormat := Transform(nValor, "999,999,999.99")

            If AllTrim(Upper((cAliasTmp)->Tipo)) == "CONTAS A PAGAR"
                // Modelo para contas a pagar: nome e cnpj da empresa, assinatura da empresa
                cTexto := "No dia " + cDia + " do mês de " + cMes + " do ano " + cAno + ", " + ;
                cEmpNome + ", com endereço a " + cEmpEnd + ", compromete-se pagar por esta via de nota promissória na praça " + cEmpEnd + ", " + cEmpCidade + "/" + cEmpEstado +" a " + ;
                cEmpNome + ", CPF/CNPJ de número " + cEmpCNPJ + ", a quantia de valor " + cValorFormat + " (" + cValorExt + ")."
                cAssinatura := "Assinatura da empresa"
            Else // Contas a Receber
                // Modelo padrão: nome e cnpj do cliente, assinatura do cliente
                cTexto := "No dia " + cDia + " do mês de " + cMes + " do ano " + cAno + ", " + ;
                    cNome + ", com endereço a " + cEnd + ", compromete-se pagar por esta via de nota promissória na praça " + cComplemen + " a " + ;
                    cNome + ", CPF/CNPJ de número " + cCnpj + ", a quantia de valor " + cValorFormat + " (" + cValorExt + ")."
                cAssinatura := "Assinatura do cliente"
            EndIf

            aLinhas := MyWordWrap(cTexto, 70)
            nLinhaAtual := nLinTop + 10

            For nI := 1 To Len(aLinhas)
                oPrint:Say(nLinhaAtual, nColTexto, aLinhas[nI], TFont():New("Arial",,12), nTextoWidth, 20, COR_PRETO, PAD_LEFT, 0)
                nLinhaAtual += 15
            Next

            nLinhaAtual += 15
            oPrint:Say(nLinhaAtual, nColTexto, "____________________")
            nLinhaAtual += 15
            oPrint:Say(nLinhaAtual, nColTexto, cAssinatura)

            oPrint:EndPage()
        EndIf
        (cAliasTmp)->(DbSkip())
    EndDo

    oPrint:Preview()
    If Type("oDlgMark") == "O" .And. oDlgMark != NIL
        oDlgMark:End() // Fecha o browser/tela após a impressão
    EndIf
Return

/*/{Protheus.doc} Static Function fLogoEmp
Função que retorna o logo da empresa (igual a DANFE)
@author Daniel Atilio
@since 19/06/2016
@version 1.0
@type function
/*/
Static Function fLogoEmp()
    Local cLogo  := ""
    Local cStart := GetSrvProfString("Startpath", "")
    Local cFilAnt := FWFilial() // ou ajuste conforme sua filial

    // Pega a imagem
    cLogo := cStart + "LGMID01" + cFilAnt + ".png"

Return cLogo

Bom pessoal, por hoje é só.

Abraços e até a próxima.

Dan (Daniel Atilio)
Cristão de ramificação protestante. Especialista em Engenharia de Software pela FIB, graduado em Banco de Dados pela FATEC Bauru e técnico em informática pelo CTI da Unesp. Entusiasta de soluções Open Source e blogueiro nas horas vagas. Autor e mantenedor do portal Terminal de Informação.

Deixe uma resposta

Terminal de Informação