Sei sulla pagina 1di 24

Agenda - Parte I (Criao do Banco de Dados e seus Objetos) Estamos aqui para, como eu disse no artigo anterior, vermos

a utilizao da TStringList, mas no apenas para isso. Vamos aproveitar a oportunidade e aprender outras coisas, como segue abaixo:. Bem, nesse artigo vamos criar uma simples agenda para guardarmos as informaes de: NOME, TELEFONE RESIDENCIAL, CELULAR e E-MAIL. No entanto, vamos aproveitar para vermos a utilizao de Triggers, Procedures, Select Procedures (Procedures Selecionveis), Generators e Domains no Firebird. Ento, vamos colocar a mo na massa e iniciarmos nossa pequena agenda atravs da criao do banco de dados. partir do Editor SQL da ferramenta de administrao/utilizao do firebird de sua preferncia (IBOConsole, IBExpert, etc) crie o banco de dados para ser utilizado na aplicao. CREATE DATABASE 'C:\Agenda\dados\AGENDA.FDB' USER 'SYSDBA' PASSWORD 'masterkey' PAGE_SIZE 1024 DEFAULT CHARACTER SET NONE; Dessa forma, estamos criando nosso banco de dados AGENDA.FDB no caminho especificado usando o usurio e senha padro do Interbase/Firebird (caso no seu banco esteja diferente, faa as alteraes devidas). Para no entrarmos em maiores detalhes neste momento, criamos um banco de dados com o PAGE_SIZE Default, e sem nenhuma definio de CHARACTER SET. (num prximo artigo poderemos tratar essas particularidades.) Feito isso, vamos entrar no nosso banco de dados recm-criado e vamos definir alguns DOMNIOS (Domains) que sero utilizados. Domnios no Firebird so semelhantes ao conceito de "tipos de dados definidos pelo usurio". Eles tornam possvel empacotar um conjunto de atributos com um tipo de dados j existente, dar-lhe um nome (identificador) e depois us-lo no lugar do tipo de dado para definir colunas em uma tabela. Quando definimos uma coluna de uma tabela, baseada em um domnio, esta coluna herda do domnio todos os atributos, como: Tipo de Dado, Valor Default, Status NULL. Ainda partir do editor SQL criemos os domnios que utilizaremos: CREATE DOMAIN DATAHORA AS TIMESTAMP; CREATE DOMAIN EMAIL AS VARCHAR(100); CREATE DOMAIN ID AS INTEGER NOT NULL; CREATE DOMAIN NOME AS VARCHAR(30) NOT NULL; CREATE DOMAIN TELEFONE AS VARCHAR(8); Aqui, definimos 5 domnios que sero utilizados em nosso banco de dados: Um domnio com nome DATAHORA do tipo Timestamp; Um domnio com nome EMAIL do tipo varchar com tamanho 100; Um domnio com nome ID do tipo inteiro e no nulo (not null);

Um domnio com nome NOME do tipo varchar com tamanho 30 e no nulo (not null); E um domnio com nome TELEFONE do tipo varchar com tamanho 8; Observe que, para alguns domnios definimos apenas o tipo de dados, e para outros definimos o tipo de dados e o status NULL. Poderamos ainda ter definido mais atributos. Vamos agora, criar a tabela que armazenar os dados da nossa agenda. Ainda no editor SQL execute o cdigo abaixo: CREATE TABLE AGENDA ( ID ID PRIMARY KEY, NOME NOME, TELEFONE_RES TELEFONE, CELULAR TELEFONE, EMAIL EMAIL, DATAHORA_INC DATAHORA, DATAHORA_ALT DATAHORA ); Observem que, na definio do tipo de dados da coluna, no usamos os tipos padro do Firebird, mas sim os "tipos de dados definidos pelo usurio" (Domnios) que criamos. Assim, como j dissemos que as colunas definidas a partir de domnios herdam os atributos destes, temos a certeza que a coluna ID ser do tipo inteiro e no nulo, como definimos na criao do domnio. Da mesma forma as demais colunas herdam os atributos de seus respectivos domnios. Definimos tambm que a coluna ID ser a chave primria de nossa tabela (PRIMARY KEY). Observe que nessa tabela temos duas colunas diferentes chamadas DATAHORA_INC e DATAHORA_ALT. Essas colunas sero utilizadas para sabermos a data e hora em que um registro foi includo e a data e hora em que o registro foi alterado pela ltima vez. Criaremos agora, uma TRIGGER (Gatilho) que ser utitlizada em nosso banco de dados. Mas entendamos brevemente o que so Triggers (Gatilhos). Uma trigger um mdulo auto-contido que executado automaticamente quando uma solicitao executada. Normalmente tem a finalidade de alterar o estado dos dados de uma tabela. No nosso caso, utilizaremos um trigger que ser executada em dois casos distintos, na insero (INSERT) de dados e na atualizao (UPDATE) de dados, porm, o Firebird nos permite utilizar uma nica trigger para esta tarefa. No editor SQL criemos a seguinte trigger conforme o cdigo abaixo: SET TERM ^ ; /* Trigger: AGENDA_ID_DTHR_INS_UPD */ CREATE TRIGGER AGENDA_DTHR_INS_UPD FOR AGENDA ACTIVE BEFORE INSERT OR UPDATE POSITION 0 as begin if (inserting) then begin new.DATAHORA_INC = current_timestamp; new.DATAHORA_ALT = current_timestamp; end

else if (updating) then begin new.DATAHORA_ALT = current_timestamp; end end ^ SET TERM ; ^ Entendendo o que foi feito: 01. Definimos, atravs da instruo SET TERM que, o caracter terminador de declarao passa a ser o "^" e no mais o ";" que o padro do SQL. Isso porque, como o SQL interpreta o ponto e vrgula como o terminador de instruo, nossa trigger no seria executada at o final e, possivelmente retornaria em erro. Dessa forma o ISQL (Interpretador SQL do Firebird) pr-analisa qualquer declarao e envia qualquer declarao terminada direto para o servidor como um nico comando. SET TERM tem a funo de informar ao servidor para interpretar os terminadores de forma diferente, de modo a no causar um erro de execuo no banco de dados. 02. Usamos o comando CREATE TRIGGER para criar a nossa trigger, na sequncia definimos um nome para ela, no nosso caso AGENDA_DTHR_INS_UPD e com a clusula FOR informamos a tabela qual essa trigger est relacionada. Com a instruo ACTIVE definimos a situao da trigger que no nosso exemplo encontrase ativa. Com a instruo BEFORE informamos que ela deve ser executada "Antes" de alguma solicitao, no nosso caso, antes da instrues "INSERT" ou "UPDATE" como mostrado acima. Como podemos definir mais de uma trigger para uma mesma tabela, podemos tambm definir a ordem/sequncia em que as mesmas sero processadas. Neste caso usamos a instruo POSITION 0 para informar que esta dever ser a primeira trigger a ser executada, caso existam outras. Dois elementos especiais do PSQL podem ser usadas com gatilhos: as variveis de contexto INSERTING, UPDATING e DELETING, e as variveis de contexto NEW e OLD, sendo as 3 primeiras do tipo booleano. As 3 primeiras so variveis de evento e informam que tipo de solicitao est sendo processada. As 2 ltimas (NEW e OLD) fazem referncia aos valores novo e velho de uma determinada coluna. Assim sendo, utilizamos estas variveis de contexto para determinar a ao a ser executada para cada tipo de solicitao. Desta forma, determinamos que, se estivermos fazendo uma insero, definiremos os novos valores (usanso a varivel de contexto NEW) das colunas DATAHORA_INC e DATAHORA_ALT como sendo a data e hora corrente (data e hora do servidor Firebird), usando a varivel de contexto de data e hora CURRENT_TIMESTAMP. No caso de uma alterao (UPDATE) apenas definimos e data e hora de alterao do registro. Ao final, usamos novamente SET TERM para retornar a definio do caracter de terminao para o ponto e vrgula que o padro SQL. Obs.: Essa uma explicao superficial sobre os gatilhos e seus elementos, especificamente aplicada ao nosso artigo. Bem, como no Firebird no dispomos de um tipo de dado "Auto-numerao" ou "Auto-Incremento" precisamos de uma estrutura chamada GENERATORS (Geradores).

Geradores so ideais para simular um campo auto-incremento. Eles so declarados usando a declarao CREATE como qualquer outro objeto de banco de dados. Para criar o nosso GENERATOR usemos o cdigo abaixo: CREATE GENERATOR AGENDA_ID; Assim, criamos um GENERATOR de nome AGENDA_ID. Esse GENERATOR ser o responsvel por controlar os ID's gerados para a tabela AGENDA. Vamos criar agora, duas procedures. A primeira para fazer a insero, atualizao e deleo de dados na tabela AGENDA e a segunda, uma procedure selecionvel que ir nos retornar o prximo ID a ser utilizado na nossa tabela. Vamos criao das procedures ento. No editor SQL execute os seguintes cdigos, um por vez. Cdigo 01 - Procedure de Insero/Atualizao/Deleo SET TERM ^ ; CREATE PROCEDURE INS_UPD_DEL_CONTATO ( PID INTEGER, PNOME VARCHAR(30), PTELEFONE_RES VARCHAR(8), PCELULAR VARCHAR(8), PEMAIL VARCHAR(100), POPERACAO CHAR(1)) AS begin if (:pOperacao = 'I') then begin insert into agenda(id,nome,telefone_res,celular,email) values(:pid,:pnome,:ptelefone_res,:pcelular,:pemail); end else if (:pOperacao = 'A') then begin update agenda set nome = :pnome, telefone_res = :ptelefone_res, celular = :pcelular, email = :pemail where id = :pid; end else if (:pOperacao = 'D') then begin delete from agenda where id = :pid; end end ^ SET TERM ; ^ Criamos aqui a procedure INS_UPD_DEL_CONTATO que recebe 6 parmetros de entrada para preencher adequadamente os campos da tabela, e, um deles denominado POPERACAO que servir para informar qual a operao que desejamos realizar: I - Inserir, A - Alterar, D - Deletar.

Para cada umas das opes de operao, uma instruo SQL adequada ser executada. Obs.: Em declaraes PSQL, para utilizar os parmetros passados procedure como valores de entrada para as instrues SQL devemos preced-los do marcador : (dois pontos). Por isso as construes values(:pid,:pnome,:ptelefone_res,:pcelular,:pemail); e nome = :pnome,.... etc, etc, etc. Cdigo 02 - Procedure Selecionvel - Retorna prximo ID da tabela AGENDA. SET TERM ^ ; CREATE PROCEDURE SP_NEW_ID ( PTABELA VARCHAR(30)) RETURNS (POUT_ID INTEGER) AS begin if (:pTABELA = 'AGENDA') then pOUT_ID = GEN_ID(AGENDA_ID,1); end ^ SET TERM ; ^ Aqui criamos a procedure SP_NEW_ID que recebe um parmetro de entrada, que o nome da tabela da qual desejamos que nos seja retornado o ID e nos retorna um valor POUT_ID que justamente o valor do ID que desejamos. Entendendo: Passamos para a procedure o nome da tabela da qual desejamos descobrir o prximo ID. Com base no nome da tabela, o procedimento executa o trecho de cdigo adequado e, usando a funo SQL GEN_ID nos retorna o prximo valor do GENERATOR que responsvel por controlar os ID's da tabela, no nosso caso o generator AGENDA_ID. A funo GEN_ID recebe dois parmetros de entrada o primeiro o nome do generator que se deseja usar e o segundo o tamanho do passo. No nosso caso ele nos retorna um valor que 1 nmero maior que o ltimo nmero gerado e incrementa o gerador para o nmero recm-gerado. Obs.: Usando uma procedure selecionvel para retornar ID's podemos us-la para qualquer que seja a tabela, bastando para isso adicionarmos uma clusula "if" para a tabela desejada e usar a funo GEN_ID com o generator adequado a cada tabela.

Caros amigos, at aqui devemos ter um banco de dados de nome AGENDA, com os seguintes objetos: 01 05 02 01 01 Tabela de nome AGENDA Domnios de nomes: ID,NOME,TELEFONE,EMAIL e DATAHORA Procedimentos de nomes: INS_UPD_DEL_CONTATO e SP_NEW_ID Gatilho de nome AGENDA_DTHR_INS_UPD Gerador de nome AGENDA_ID

Por hora, ficamos por aqui. Na continuao (Parte II) faremos a aplicao em delphi para utilizarmos adequadamente esta estrutura de banco de dados que criamos.

Para baixar o arquivo de banco de dados deste artigo, clique aqui. Alessandro Alves planetadelphi@oppus.eti.br www.oppus.eti.br

Agenda - Parte II (Criao da aplicao em Delphi) Salve, Salve companheiros !! Estamos aqui novamente para dar continuidade ao nosso projeto iniciado no artigo anterior. Bem, na primeira parte deste artigo, criamos o banco de dados de nossa aplicao e seus objetos (Procedures, Triggers, Domains e Generators), agora vamos partir para o Delphi e unir as duas coisas. Ento, criemos uma nova aplicao e no formulrio principal vamos adicionar os seguintes componentes: 7 Edits, 7 Labels, 6 Botes, 1 Groupbox, 1 DBGrid, 1 OpenDialog (paleta Dialogs), 1 DataSource (paleta Data Access) e 4 componentes da paleta Interbase - 1 IBDatabase, 1 IBTransaction, 2 IBQuery e 1 IBStoredProc. Nosso formulrio dever ter a aparncia abaixo:

Vou deixar a nomeao de componentes a critrio de vocs, e vou usar imagens para determinar quais componentes iremos codificar, exceto os que seguem abaixo

para evitar confuses durante o processo de codificao. D um duplo clique no IBDatabase, e ento abrir o "Database Component Editor". Faa as configuraes conforme segue abaixo:

No Object Inspector as propriedades do IBDatabase dever estar assim:

Configuraes do IBTransaction no Object Inspector:

Agora, no primeiro componente IBQuery, acesse a propriedade SQL. O Editor SQL ser exibido. Adicione o SQL que segue:

Adicionado o SQL, ajuste as seguintes propriedades no object inspector:

Clicando na propriedade Params (circulada de azul) devemos ter a seguinte viso:

No outro componente IBQuery, acesse a propriedade SQL. O Editor SQL ser exibido. Adicione o SQL que segue:

Vamos analisar rapidamente esse SQL. Lembram-se que, no nosso banco de dados criamos uma procedure selecionvel de nome SP_NEW_ID? Pois bem, agora estamos fazendo um SELECT partir de uma Stored Procedure, por isso chamamos de procedure selecionvel. Estamos passando para a stored procedure uma parmetro, que neste caso ser o nome da tabela e ela nos retornar o pOUT_ID que ser o valor que desejamos. Lembram-se da definio da procedure no banco de dados?

Pois a est, passamos para a procedure um parmetro de entrada que o nome da tabela, e ela nos retorna no parmetro de sada POUT_ID o valor que nos interessa. Continuando as configuraes do componente, no object inspector faa as configuraes como abaixo:

Clicando na propriedade Params (circulada de azul) devemos ter a seguinte viso:

Configuremos agora o componente IBStoredProc conforme segue:

Uma vez configuradas estas propriedades, clicamos na propriedade PARAMS que dever exibir a seguinte aparncia:

Lembram-se da definio da Stored Procedure INS_UPD_DEL_CONTATO no banco de dados??

Vejam que o Delphi reconhece automaticamente os parmetros de entrada exigidos pela Stored Procedure definida no banco de dados. Agora, configure o componente Data Source da seguinte forma:

Pois bem pessoal, agora configure a propriedade DataSource do DBGrid como

dsAgenda e pronto. Essas so as configuraes bsicas que devem ser feitas para que o nosso exemplo funcione adequadamente. Obs.: O edit que aparece em "amarelo" nomeie como edtID, ele ser utilizado para receber o identificador nico (chave primria) de nossa tabela. Este pode ser at setado como Visible False, mas deixarei como True para que possamos ver seu comportamento. Agora vamos codificar nossa aplicao. Primeiramente vamos definir duas funes. Na seo private do formulrio declare-as como segue:

Teclamos SHIFT+CTRL+C para o delphi criar para ns o corpo das funes na seo implementation. Assim, definimos para a funo fCamposPreenchidos o seguinte cdigo:

Trata-se de uma funo extremamente simples que, retorna true se o campo nome e, pelo menos um dos telefones estiver preenchido. Afinal de contas no faz sentido inserir na agenda um registro sem nome, ou, um registro que tenha apenas um nome e no tenha nenhum telefone, no mesmo?? O cdigo da funo fGetNewID deve ser o seguinte:

Pois bem, como j definimos um SQL para a query "qryID" e definimos tambm um parmetro de entrada para ela, nesse cdigo apenas: fechamos a query, atribumos o valor do parmetro de entrada da funo "pTab" ao parmetro de entrada da query, abrimos a query e atribuimos ao resultado da funo o valor retornado no Fields[0] da query. Dessa forma, a funo fGetNewID faz com que a query "qryID" com o select que l adicionamos, v at a SP_NEW_ID no banco de dados e nos retorne o prximo ID da tabela desejada. Agora vamos ao evento "OnChange" do edit logo frente do label "Localizar Nome" e vamos adicionar o seguinte cdigo:

Cdigo:

Como j definimos um SQL para a query "qryAgenda" e um parmetro de entrada, o que fazemos aqui tambm bastante simples. Apenas fechamos a query, atribuimos um valor ao parmetro, no caso, o texto existente no edit "edtlocalizar" e abrimos a query. Dessa forma, como a query est ligada ao Data Source "dsAgenda" que por sua vez est ligado ao DBGrid, o resultado da query ser exibido no DBGrid. No evento "OnCreate" do form, adicione o seguinte cdigo:

Com este cdigo, logo na criao do formulrio a query ir retornar todos os registros j existentes na tabela AGENDA do nosso banco de dados. Vamos agora definir uma procedure que servir para fazer as alteraes no banco de dados. Vamos declarar na seo private o seguinte procedimento:

Na implementao do procedimento, adicionamos o seguinte cdigo:

Aqui, atravs do componente IBStrProc (prcIns_Upd_Del_Contato) atribumos os valores passados ao procedimento aos valores de entrada da Stored Procedure do banco de dados e executamos a Stored Procedure. O banco se encarrega de fazer o

trabalho adequado, conforme seja o valor passado no parmetro "pOperacao". Vamos agora, codificar os botes de nossa aplicao. No evento "OnClick" do boto com caption "Salvar - Novo" adicione o cdigo:

Ento, o que fazemos aqui? Bem, usando a funo por ns definida "fCamposPreenchidos" verificamos se os campos de preenchimento obrigatrio esto de fato preenchidos, em caso negativo, informamos ao usurio que os campos obrigatrios devem ser preenchidos. Satisfeita a condio de campos obrigatrios preenchidos, retornamos na propriedade "text" do edtID o novo ID a ser utilizado para a tabela AGENDA, usando para isso a funo por ns definida "fGetNewID" passando como parmetro da funo o nome da tabela que desejamos. Retornado o ID, chamamos a funo InsUpdDelContato passando os valores contidos nos edit's como parmetros de entrada para a funo, sendo que, o ltimo parmetro o que define qual a operao que desejamos realizar. Neste caso, "I" que representa a operao de Insero (Insert). Colocamos a chamada ao procedimento dentro de um bloco Try..Except para dar um retorno ao usurio no caso de erro na execuo do procedimento. Ao final, disparamos o evento "OnChange" do edtlocalizar. No evento "OnClick" do boto com caption "Salvar - Edio" adicione o

cdigo:

Da mesma forma que no cdigo anterior, verificamos o preenchimento dos campos obrigatrios e, fazemos a chamada ao procedimento InsUpdDelContato para efetuar a operao desejada, neste caso "A" que representa a alterao do registro (Update). No evento "OnClick" do boto com caption "Excluir" adicione o cdigo:

Aqui, o que muda que, antes de chamar o procedimento verificamos se existe um registro selecionado, se existe um valor no campo edtID. Existindo, chamamos o procedimento passando os parmetros. Neste caso, no precisamos passar todos os parmetros, j que, para a excluso de um registro necessitamos apenas de seu ID. Sendo assim, passamos os demais parmetros como strings vazias. No evento "OnClick" do boto com caption "Limpar Campos " adicione o cdigo:

Aqui, sem mistrios, apenas limpamos os textos dos edit's. No evento "OnCellClick" do DBGrid adicione o seguinte cdigo:

Simplesmente atribumos propriedade "text" dos edits correspondentes o valor existente em cada campo da query conforme o registro que selecionamos no DBGrid. No evento "OnKeyPress" dos edits relacionados a telefones, adicione o seguinte cdigo, para que os mesmos aceitem apenas nmeros e back space.

Agora, partimos para a parte de importao de TXT/CSV. No evento "OnClick" do boto com 3 pontinhos (...) adicione o seguinte cdigo:

Aqui, apenas atribumos ao edit "edtOrigem" o caminho completo + o nome de arquivo selecionado a partir da Open Dialog. No componente OpenDialog (opndlgOrigem) faa as seguintes

configuraes:

Clique na propriedade Filter e no Filter Editor adicione as opes para que sua aparncia fique como segue abaixo:

No evento "OnClick" do boto com caption "Import TXT/CSV" adicione o seguinte cdigo:

Na seo VAR (destacado em amarelo) definimos variveis que iro armazenar os valores obtidos partir do arquivo texto. Lembram que falaramos da utilizao da classe TStringList?? Pois bem, olha a (destacada em vermelho) a varivel "str" do tipo TStringList. Como vamos fazer uma importao, a primeira providncia ver se o arquivo informado no edit origem, de fato existe. Usamos para isso a funo "FileExists" que recebe como parmetro o caminho do suposto arquivo que utilizaremos. Logo abaixo, criamos nossa StringList, instanciando na varivel str um objeto da classe TStringList, e logo aps isso, definimos que o caracter delimitador de nossa TStringList o ponto-e-vrgula. No cdigo logo abaixo, destacado em azul claro, apenas fazemos a atribuio de nosso arquivo texto. Atribumos varivel "ArqOrigem" do tipo String, o valor contido na propriedade "text" do edit "edtOrigem". A seguir, atribumos varivel "Arq" do tipo TextFile o arquivo existente no caminho que aponta a varivel "ArqOrigem". Usando o procedimento RESET abrimos o arquivo. Usando o procedimento ReadLn fazemos a leitura da primeira linha do arquivo aberto e atribumos o contedo varivel "Linha". Dentro do loop while, enquanto existirem linhas a serem lidas, ou seja, enquanto no chegarmos ao final do arquivo, atribumos propriedade "DelimitedText" da nossa StringList o valor da varivel linha. A que a coisa fica interessante. Lembram que definimos a propriedade delimiter da nossa StringList como ";" ? Pois bem, sabemos que, normalmente um arquivo CSV tem seus valores separados por ponto-e-vrgula, sendo assim, o arquivos que utilizaremos no nosso exemplo possui uma estrutura semelhante que segue abaixo: "LUIZ CARLOS";34377121;98775300;"luizcarlos@qualquermail.com.br" "CELIA CARDOSO";33485211;81985591;"celia@mycompany.com.br" "ZELIA SANTOS";33958752;96325554;"zelia@qualquermail.com.br" Onde, o primeiro campo o nome da pessoa, o segundo o telefone residencial, o terceiro o telefone celular e o quarto o e-mail. Um observao importante deve ser feita aqui. Para que o resultado esperado seja correto no uso da TStringList como vamos fazer, quando no arquivo CSV/TXT, o valor for uma string composta por mais de um valor e, houver espao entre suas partes, como por exemplo no nome LUIZ CARLOS, toda a string deve vir entre aspas duplas, caso contrrio a TStringList entender se tratar de dois valores independentes. Continuando ento....sabendo da estrutura de nosso arquivo CSV/TXT e, tendo definido a propriedade delimiter da nossa StringList como sendo o ";", atribuimos propriedade "DelimitedText" o valor da varivel linha que recebeu uma linha de nosso arquivo CSV/TXT.

Feito isso, podemos tratar nossa StringList como uma matriz de valores, onde, podemos acessar o valor desejado atravs de seu ndice, e exatamente o que fazemos aqui. Utilizando os ndices desejados, resgatamos os valores da StringList e atribumos s variveis que definimos para armazenar os respectivos valores a serem inseridos no nosso banco de dados. Uma vez com os valores nas respectivas variveis, geramos um ID para o registro a ser inserido e fazemos a devida insero no banco de dados chamando a procedure InsUpdDelContato que criamos em nossa aplicao. Em caso de erro, uma mensagem exibida ao usurio. A casa insero uma nova linha do arquivo lida (ReadLn) e inserida no banco de dados at que se chegue ao final do arquivo. Finalizado o arquivo, ele fechado (CloseFile) e uma mensagem de sucesso na importao exibida. isso a galera..... finalizamos assim mais um artigo. Espero que o contedo deste artigo possa auxili-los em suas tarefas do dia-a-dia.

Para baixar todo o contedo deste artigo, clique aqui.