Skip to content

Commit 5b0346e

Browse files
authoredJun 28, 2024
Add files via upload
App Agenda.
1 parent 6deba32 commit 5b0346e

File tree

2 files changed

+939
-0
lines changed

2 files changed

+939
-0
lines changed
 

‎Contacts-Projetcts.py

+831
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,831 @@
1+
# Importa o módulo principal da biblioteca Dash para criar a aplicação web.
2+
import dash
3+
4+
# Importa componentes específicos da biblioteca Dash para criar elementos
5+
# HTML e dashboards interativos.
6+
from dash import dcc, html, Input, Output, State, dash_table, ALL
7+
8+
# Importa a classe PreventUpdate do Dash, que é usada para evitar atualizações
9+
# desnecessárias dos callbacks.
10+
from dash.exceptions import PreventUpdate
11+
12+
# Importa a biblioteca pandas.
13+
# A biblioteca pandas é extensivamente usada para manipulação e análise de dados.
14+
# Ela oferece estruturas de dados como DataFrames e Series para lidar com dados
15+
# tabulares de forma eficiente.
16+
import pandas as pd
17+
18+
# Importa a biblioteca numpy.
19+
# Numpy é uma biblioteca para a linguagem Python que suporta arrays e matrizes multidimensionais.
20+
# Ela também oferece uma coleção de funções matemáticas para operar nesses arrays.
21+
import numpy as np
22+
23+
# Importa a biblioteca requests para fazer requisições HTTP.
24+
import requests
25+
26+
# Importa o módulo win32com.client para interagir com o Microsoft Outlook.
27+
import win32com.client as win32
28+
29+
# Importa o módulo pythoncom para inicialização do COM.
30+
import pythoncom
31+
32+
# Importa o módulo os para operações relacionadas ao sistema operacional,
33+
# como manipulação de arquivos.
34+
import os
35+
36+
# Define a função buscar_cep que recebe um código postal como argumento.
37+
def buscar_cep(codigo_postal):
38+
39+
# O bloco try tenta executar as instruções contidas nele.
40+
try:
41+
42+
# Usa a biblioteca requests para fazer uma requisição HTTP GET ao serviço de CEP.
43+
# A URL final será algo como "https://viacep.com.br/ws/01001000/json/".
44+
resposta = requests.get(f"https://viacep.com.br/ws/{codigo_postal}/json/")
45+
46+
# Converte a resposta JSON para um dicionário Python usando o método .json() da resposta.
47+
dados = resposta.json()
48+
49+
# Verifica se a chave "erro" está presente no dicionário.
50+
# Se estiver, significa que o CEP é inválido ou não foi encontrado.
51+
if "erro" in dados:
52+
return None # Retorna None para indicar que o CEP é inválido ou não foi encontrado.
53+
54+
# Monta uma string de endereço completo usando os dados recebidos.
55+
# dados['logradouro'], dados['bairro'], etc., são campos no dicionário retornado pela API.
56+
endereco_completo = f"{dados['logradouro']}, {dados['bairro']}, {dados['localidade']}, {dados['uf']}"
57+
58+
# Retorna a string do endereço completo.
59+
return endereco_completo
60+
61+
# O bloco except captura qualquer exceção que ocorra durante a execução do bloco try.
62+
except Exception as e:
63+
64+
# Imprime a mensagem "Exceção: " seguida dos detalhes da exceção.
65+
print("Exceção: ", e)
66+
67+
# Retorna None para indicar que ocorreu um erro durante a busca do CEP.
68+
return None
69+
70+
71+
# Define a função enviar_email_com_outlook, que recebe um objeto
72+
# linha (geralmente uma linha de um DataFrame).
73+
def enviar_email_com_outlook(linha):
74+
75+
# Inicializa o mecanismo COM (Component Object Model) do Windows.
76+
# Isso é necessário para usar a automação COM, especialmente em ambientes multithread.
77+
pythoncom.CoInitialize()
78+
79+
# Cria um objeto Dispatch para a aplicação Microsoft Outlook.
80+
# Isso inicia o Outlook se ele ainda não estiver em execução.
81+
outlook = win32.Dispatch('outlook.application')
82+
83+
# Cria um novo item de e-mail no Outlook.
84+
# O argumento 0 indica que um e-mail será criado (outros números
85+
# representam outros tipos de itens, como compromissos).
86+
email = outlook.CreateItem(0)
87+
88+
# Define o campo "Assunto" do e-mail usando a chave 'Nome' do objeto linha.
89+
email.Subject = f"Assunto do E-mail para {linha['Nome']}"
90+
91+
# Define o corpo do e-mail usando várias chaves do objeto linha.
92+
# Está sendo usado um string multilinha para formatar o corpo do e-mail de forma mais legível.
93+
email.Body = f"""Olá {linha['Nome']},
94+
95+
Estamos muito felizes em ter você como nosso cliente. Abaixo seguem algumas informações úteis:
96+
97+
- Endereço: {linha['Endereco']}
98+
- Celular: {linha['Celular']}
99+
- Telefone: {linha['Telefone']}
100+
101+
Atenciosamente,
102+
Equipe da Empresa
103+
"""
104+
105+
# Define o campo "Para" do e-mail usando a chave 'Email' do objeto linha.
106+
email.To = linha['Email']
107+
108+
# Salva o e-mail no Outlook.
109+
# Isso não envia o e-mail, apenas o salva no rascunho.
110+
# email.Send()
111+
# email.Display()
112+
email.Save()
113+
114+
115+
# O bloco try tenta executar o código dentro dele.
116+
try:
117+
118+
# Usa a biblioteca pandas para ler uma planilha Excel chamada 'Dados_Agenda.xlsx' e a aba 'Dados'.
119+
# O DataFrame resultante é armazenado na variável df.
120+
df = pd.read_excel('Dados_Agenda.xlsx', sheet_name='Dados')
121+
122+
# O bloco except captura uma exceção específica (FileNotFoundError) caso o
123+
# arquivo 'Dados_Agenda.xlsx' não seja encontrado.
124+
except FileNotFoundError:
125+
126+
# Se o arquivo não for encontrado, um novo DataFrame vazio com as colunas especificadas é criado.
127+
df = pd.DataFrame(columns=['CEP', 'Nome', 'Endereco', 'Celular', 'Telefone', 'Email'])
128+
129+
# Faz uma cópia profunda do DataFrame df e armazena na variável dataframe_original.
130+
# Isso é feito para manter uma versão original dos dados, que pode ser útil para operações futuras.
131+
dataframe_original = df.copy()
132+
133+
# Inicializa uma nova aplicação Dash e armazena na variável app.
134+
# O argumento __name__ é usado para indicar o nome do script em execução como ponto de entrada para a aplicação.
135+
app = dash.Dash(__name__)
136+
137+
# Define o layout da aplicação Dash. O layout é a estrutura visual da página web.
138+
# O html.Div é um container que pode conter outros componentes HTML e Dash.
139+
app.layout = html.Div([
140+
141+
# html.H1 cria um cabeçalho de nível 1 na página web.
142+
# O texto "Agenda de Contatos" será o conteúdo desse cabeçalho.
143+
# O argumento style é um dicionário que contém estilos CSS. Aqui, ele centraliza o texto do cabeçalho.
144+
html.H1("Agenda de Contatos", style={'textAlign': 'center', 'color': 'blue'}),
145+
146+
# Contêiner principal com estilo flex para alinhar os itens horizontalmente.
147+
html.Div(children=[
148+
149+
# Contêiner para os campos de entrada com estilo flex para alinhar os itens na mesma linha.
150+
html.Div(children=[
151+
152+
# Início do contêiner para o campo "CEP".
153+
# Este contêiner é um 'Div', que é um contêiner genérico para conteúdo de fluxo e não possui significado semântico específico.
154+
# Dentro deste 'Div', temos dois elementos filhos: um rótulo e um campo de entrada.
155+
html.Div(children=[
156+
157+
# O primeiro filho é um rótulo 'Label' para o campo de entrada.
158+
# O rótulo é um elemento inline que representa uma legenda para um elemento de entrada de formulário.
159+
# A propriedade 'children' do rótulo é simplesmente o texto que será exibido ao usuário, neste caso, "CEP:".
160+
# Este texto ajuda o usuário a entender o que o campo de entrada subsequente espera como informação.
161+
html.Label("CEP:"),
162+
163+
# O segundo filho é um campo de entrada 'Input'.
164+
# Este é um componente do Dash que representa um campo de entrada HTML onde os usuários podem inserir dados.
165+
# 'id' é um identificador único para o campo de entrada, que pode ser usado para referenciar o campo em callbacks no Dash.
166+
# O atributo 'type' especifica o tipo de dados que o campo de entrada aceitará, neste caso, 'text' significa que ele aceita texto.
167+
dcc.Input(id='cep', type='text')
168+
169+
# Fechamento do 'Div' para o campo "CEP".
170+
# 'style' é um dicionário de estilos CSS aplicados a este 'Div'.
171+
# 'marginRight' define uma margem à direita do 'Div'. Isso é usado para criar espaço entre este 'Div' e qualquer elemento vizinho à direita.
172+
# Aqui, '10px' é a quantidade de espaço entre este contêiner de campo de entrada e o próximo.
173+
], style={'marginRight': '10px'}),
174+
175+
# Início do contêiner para o campo "Nome".
176+
# A estrutura é idêntica ao contêiner "CEP", com uma 'Div' contendo um 'Label' e um 'Input'.
177+
html.Div(children=[
178+
179+
# Rótulo para o campo de entrada "Nome". Funciona da mesma forma que o rótulo para "CEP".
180+
html.Label("Nome:"),
181+
182+
# Campo de entrada para o "Nome". 'id' e 'type' funcionam da mesma forma que no campo "CEP".
183+
dcc.Input(id='nome', type='text')
184+
185+
# Fechamento do 'Div' para o campo "Nome".
186+
# A propriedade 'style' com 'marginRight' também é usada aqui para manter a consistência no layout,
187+
# separando este contêiner do próximo contêiner de campo de entrada.
188+
], style={'marginRight': '10px'}),
189+
190+
# Contêiner para o campo "Endereço".
191+
# A 'Div' é um bloco de contêiner genérico para outros elementos HTML. Aqui, serve como contêiner para o rótulo e o campo de entrada de "Endereço".
192+
html.Div(children=[
193+
194+
# Cria um rótulo para o campo de entrada do endereço.
195+
# 'Label' representa uma etiqueta para o campo de entrada seguinte, facilitando a identificação do usuário sobre o que deve ser preenchido.
196+
html.Label("Endereço:"),
197+
198+
# Cria um campo de entrada de texto.
199+
# 'Input' é um componente interativo onde os usuários podem digitar o endereço. 'id' é o identificador único para este campo no Dash.
200+
dcc.Input(id='endereco', type='text')
201+
202+
# Define o estilo do contêiner Div.
203+
# 'marginRight' adiciona uma margem à direita do contêiner para separá-lo de elementos adjacentes, garantindo espaço entre os campos de entrada.
204+
], style={'marginRight': '10px'}),
205+
206+
# Contêiner para o campo "Celular".
207+
# Esta 'Div' atua como contêiner para o rótulo e campo de entrada do celular, seguindo o mesmo padrão do contêiner de "Endereço".
208+
html.Div(children=[
209+
210+
# Rótulo para o campo de entrada de celular.
211+
# Fornece uma indicação clara para o usuário de que o dado a ser inserido é o número de celular.
212+
html.Label("Celular:"),
213+
214+
# Campo de entrada para o número de celular.
215+
# Permite ao usuário inserir um número de celular. O 'id' associado a este campo permite que seja referenciado unicamente na aplicação.
216+
dcc.Input(id='celular', type='text')
217+
218+
# Estilo para o contêiner 'Div' de "Celular".
219+
# Mantém a margem à direita consistente com os outros campos de entrada para alinhamento e espaçamento adequado no layout geral.
220+
], style={'marginRight': '10px'}),
221+
222+
# Contêiner para o campo "Telefone".
223+
# Este 'Div' atua como um contêiner para agrupar visualmente o rótulo e o campo de entrada de "Telefone", facilitando a organização do layout.
224+
html.Div(children=[
225+
226+
# Cria um rótulo 'Label' para o campo de entrada de "Telefone".
227+
# 'Label' serve como uma legenda textual para o campo de entrada, indicando ao usuário que informações são esperadas neste campo.
228+
html.Label("Telefone:"),
229+
230+
# Define o campo de entrada 'Input' para o número de telefone.
231+
# 'id' é um identificador único no DOM que permite ao Dash identificar e interagir com este campo de entrada em callbacks.
232+
# 'type' é definido como 'text', que especifica que o usuário pode inserir texto neste campo.
233+
dcc.Input(id='telefone', type='text')
234+
235+
# Aplica estilos CSS ao 'Div' do campo "Telefone".
236+
# 'marginRight' define uma margem à direita, criando um espaçamento entre este campo de entrada e qualquer elemento vizinho à direita.
237+
], style={'marginRight': '10px'}),
238+
239+
# Contêiner para o campo "Email".
240+
# Similarmente, este 'Div' é utilizado para encapsular o rótulo e o campo de entrada de "Email".
241+
html.Div(children=[
242+
243+
# Rótulo 'Label' para o campo de entrada de "Email".
244+
# O texto "Email:" serve como uma indicação clara do propósito do campo de entrada seguinte.
245+
html.Label("Email:"),
246+
247+
# Campo de entrada 'Input' para o endereço de email.
248+
# Com o 'id' 'email', esse campo é identificado unicamente na aplicação para referência em operações de interação ou estilização.
249+
dcc.Input(id='email', type='text')
250+
251+
# Neste caso, não estamos aplicando uma margem à direita.
252+
# Isso pode é o último campo de entrada na linha ou para manter um alinhamento específico dentro do layout.
253+
], style={}),
254+
255+
], style={
256+
'display': 'flex', # Alinha os itens horizontalmente.
257+
'align-items': 'center', # Centraliza os itens verticalmente.
258+
'width': '100%', # Largura ajustada para 100% do contêiner pai.
259+
'padding': '15px', # Preenchimento interno.
260+
'flex-wrap': 'wrap' # Permite que os itens se envolvam conforme necessário.
261+
}),
262+
263+
], style={'width': '100%'}),
264+
# Alinhamento central com flexbox
265+
266+
267+
# Este é um novo container Div que contém a tabela de dados e os botões de ação.
268+
html.Div([
269+
270+
# Contêiner Div filho que abriga exclusivamente a tabela de dados.
271+
html.Div([
272+
273+
# Criando a tabela usando o componente dash_table.DataTable.
274+
dash_table.DataTable(
275+
276+
# ID único para a tabela, útil para referenciar em callbacks.
277+
id='tabela',
278+
279+
# Definindo as colunas da tabela com base nas colunas do DataFrame df.
280+
columns=[{"name": i, "id": i} for i in df.columns],
281+
282+
# Preenchendo os dados na tabela usando os registros do DataFrame df.
283+
data=df.to_dict('records'),
284+
285+
# Tornando as células da tabela editáveis.
286+
editable=True,
287+
288+
# Permitindo que apenas uma linha seja selecionável de cada vez.
289+
row_selectable="single",
290+
291+
# Nenhuma linha está selecionada inicialmente.
292+
selected_rows=[],
293+
294+
# Permitindo que as linhas sejam deletáveis.
295+
row_deletable=True,
296+
297+
# Estilos para o contêiner da tabela.
298+
style_table={
299+
'overflowX': 'scroll', # Rolagem horizontal se o conteúdo exceder a largura.
300+
'border': 'thin lightgrey solid' # Borda fina cinza claro ao redor da tabela.
301+
},
302+
303+
# Estilos para o cabeçalho da tabela.
304+
style_header={
305+
'backgroundColor': '#007BFF', # Cor de fundo azul.
306+
'color': 'white', # Texto branco.
307+
'fontWeight': 'bold', # Texto em negrito.
308+
'border': '1px solid white' # Borda branca ao redor das células do cabeçalho.
309+
},
310+
311+
# Estilos para as células da tabela.
312+
style_cell={
313+
'textAlign': 'left', # Alinhamento do texto à esquerda.
314+
'padding': '8px', # Preenchimento interno de 8px.
315+
'border': '1px solid lightgrey' # Borda cinza claro ao redor das células.
316+
},
317+
318+
# Estilos condicionais para as células da tabela.
319+
style_data_conditional=[
320+
{
321+
'if': {'row_index': 'odd'}, # Condição para linhas ímpares.
322+
'backgroundColor': 'lightgrey' # Cor de fundo cinza claro para linhas ímpares.
323+
}
324+
],
325+
)
326+
327+
# Estilos para o contêiner Div que contém a tabela.
328+
], style={
329+
'width': '75%', # Ocupa 75% da largura total disponível.
330+
'display': 'inline-block', # Disposto como um bloco inline.
331+
'vertical-align': 'top', # Alinhamento vertical ao topo.
332+
'padding': '15px' # Preenchimento de 15px em todos os lados.
333+
}),
334+
335+
336+
# Este trecho de código cria um novo container Div que conterá apenas botões de ação.
337+
# html.Div é um componente que cria um novo bloco ou seção na página web.
338+
html.Div([
339+
340+
# Criando um botão com o texto "Adicionar".
341+
html.Button(
342+
343+
"Adicionar", # Texto exibido no botão.
344+
345+
# ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
346+
id='botao_adicionar',
347+
348+
# Definindo o estilo CSS do botão.
349+
style={
350+
'width': '100%', # Ocupa 100% da largura do contêiner pai.
351+
'marginBottom': '10px', # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
352+
'backgroundColor': '#007BFF', # Cor de fundo azul.
353+
'color': 'white', # Cor do texto branco.
354+
'fontSize': '20px'
355+
# Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
356+
}
357+
),
358+
359+
# Um novo botão é criado com o texto "Alterar".
360+
# Assim como o botão anterior, este botão tem um id único e um estilo específico.
361+
html.Button(
362+
363+
"Alterar", # Texto exibido no botão.
364+
365+
# ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
366+
id='botao_alterar',
367+
368+
# Definindo o estilo CSS do botão.
369+
style={
370+
'width': '100%', # Ocupa 100% da largura do contêiner pai.
371+
'marginBottom': '10px', # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
372+
'backgroundColor': '#007BFF', # Cor de fundo azul.
373+
'color': 'white', # Cor do texto branco.
374+
'fontSize': '20px'
375+
# Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
376+
}
377+
),
378+
379+
# Mais um botão é criado com o texto "Pesquisar".
380+
html.Button(
381+
382+
"Pesquisar", # Texto exibido no botão.
383+
384+
# ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
385+
id='botao_pesquisar',
386+
387+
# Definindo o estilo CSS do botão.
388+
style={
389+
'width': '100%', # Ocupa 100% da largura do contêiner pai.
390+
'marginBottom': '10px', # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
391+
'backgroundColor': '#007BFF', # Cor de fundo azul.
392+
'color': 'white', # Cor do texto branco.
393+
'fontSize': '20px'
394+
# Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
395+
}
396+
),
397+
398+
# O botão "Excluir" é criado a seguir.
399+
html.Button(
400+
401+
"Excluir", # Texto exibido no botão.
402+
403+
# ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
404+
id='botao_excluir',
405+
406+
# Definindo o estilo CSS do botão.
407+
style={
408+
'width': '100%', # Ocupa 100% da largura do contêiner pai.
409+
'marginBottom': '10px', # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
410+
'backgroundColor': '#007BFF', # Cor de fundo azul.
411+
'color': 'white', # Cor do texto branco.
412+
'fontSize': '20px'
413+
# Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
414+
}
415+
),
416+
417+
# O botão "Limpar Filtro" é para redefinir quaisquer filtros aplicados.
418+
html.Button(
419+
420+
"Limpar Filtro", # Texto exibido no botão.
421+
422+
# ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
423+
id='botao_limpar',
424+
425+
# Definindo o estilo CSS do botão.
426+
style={
427+
'width': '100%', # Ocupa 100% da largura do contêiner pai.
428+
'marginBottom': '10px', # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
429+
'backgroundColor': '#007BFF', # Cor de fundo azul.
430+
'color': 'white', # Cor do texto branco.
431+
'fontSize': '20px'
432+
# Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
433+
}
434+
),
435+
436+
# O botão "Exportar para Excel" serve para iniciar um processo de exportação de dados.
437+
html.Button(
438+
439+
"Exportar para Excel", # Texto exibido no botão.
440+
441+
# ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
442+
id='botao_exportar',
443+
444+
# Definindo o estilo CSS do botão.
445+
style={
446+
'width': '100%', # Ocupa 100% da largura do contêiner pai.
447+
'marginBottom': '10px', # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
448+
'backgroundColor': '#007BFF', # Cor de fundo azul.
449+
'color': 'white', # Cor do texto branco.
450+
'fontSize': '20px'
451+
# Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
452+
}
453+
),
454+
455+
# O último botão, "Criar E-mail", servirá para enviar e-mails.
456+
html.Button(
457+
458+
"Criar E-mail", # Texto exibido no botão.
459+
460+
# ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
461+
id='botao_email',
462+
463+
# Definindo o estilo CSS do botão.
464+
style={
465+
'width': '100%', # Ocupa 100% da largura do contêiner pai.
466+
'marginBottom': '10px', # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
467+
'backgroundColor': '#007BFF', # Cor de fundo azul.
468+
'color': 'white', # Cor do texto branco.
469+
'fontSize': '20px'
470+
# Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
471+
}
472+
),
473+
474+
# O estilo desta Div é configurado para que ela ocupe 25% da largura total disponível da página.
475+
# Ela é exibida como um bloco inline para que possa ficar ao lado de outros elementos.
476+
# O alinhamento vertical é definido como 'top', o que significa que ela se alinhará ao topo do seu container pai.
477+
# Um padding de 15 pixels é adicionado para algum espaço extra ao redor do conteúdo.
478+
# Por fim, o texto dentro desta Div é centralizado usando 'textAlign': 'center'.
479+
], style={'width': '25%', 'display': 'inline-block', 'vertical-align': 'top', 'padding': '15px',
480+
'textAlign': 'center'}),
481+
482+
# Este é o estilo da Div principal que contém a tabela e os botões.
483+
# Ela é configurada para ocupar 100% da largura disponível e para dispor seus elementos filhos em uma linha (flex).
484+
], style={'width': '100%', 'display': 'flex'}),
485+
])
486+
487+
# O layout agora está completo. Todos os componentes HTML e Dash estão aninhados em Divs,
488+
# o que permite um controle preciso sobre o layout e o estilo da aplicação.
489+
490+
491+
# @app.callback é um decorador que define uma função de callback em uma aplicação Dash.
492+
# A função decorada será chamada sempre que os componentes especificados nos argumentos
493+
# Input, Output e State forem modificados.
494+
@app.callback(
495+
496+
# A lista de Outputs define os componentes cujas propriedades serão atualizadas pelo callback.
497+
# Cada Output é um objeto que leva dois argumentos: o id do componente e a propriedade que será atualizada.
498+
[Output('tabela', 'data'), # Atualiza os dados na tabela.
499+
Output('cep', 'value'), # Atualiza o valor do campo CEP.
500+
Output('nome', 'value'), # Atualiza o valor do campo Nome.
501+
Output('endereco', 'value'), # Atualiza o valor do campo Endereço.
502+
Output('celular', 'value'), # Atualiza o valor do campo Celular.
503+
Output('telefone', 'value'), # Atualiza o valor do campo Telefone.
504+
Output('email', 'value')], # Atualiza o valor do campo Email.
505+
506+
# A lista de Inputs define os componentes que, quando interagidos, irão disparar o callback.
507+
# Semelhante ao Output, cada Input é um objeto que leva dois argumentos: o id do componente e a propriedade que irá disparar o callback.
508+
[Input('botao_adicionar', 'n_clicks'), # Número de cliques no botão Adicionar.
509+
Input('botao_alterar', 'n_clicks'), # Número de cliques no botão Alterar.
510+
Input('botao_pesquisar', 'n_clicks'), # Número de cliques no botão Pesquisar.
511+
Input('botao_excluir', 'n_clicks'), # Número de cliques no botão Excluir.
512+
Input('botao_limpar', 'n_clicks'), # Número de cliques no botão Limpar Filtro.
513+
Input('botao_exportar', 'n_clicks'), # Número de cliques no botão Exportar para Excel.
514+
Input('botao_email', 'n_clicks'), # Número de cliques no botão Criar E-mail.
515+
Input('cep', 'value'), # Valor atual do campo CEP.
516+
Input('tabela', 'active_cell'), # Informações sobre a célula ativa na tabela.
517+
Input('tabela', 'data_previous')], # Dados anteriores da tabela antes da última edição.
518+
519+
# A lista de States contém os componentes cujos estados atuais serão passados para o callback, mas que não disparam o callback.
520+
# Assim como em Output e Input, cada State é um objeto que leva dois argumentos: o id do componente e a propriedade que será passada.
521+
[State('cep', 'value'), # Valor atual do campo CEP.
522+
State('nome', 'value'), # Valor atual do campo Nome.
523+
State('endereco', 'value'), # Valor atual do campo Endereço.
524+
State('celular', 'value'), # Valor atual do campo Celular.
525+
State('telefone', 'value'), # Valor atual do campo Telefone.
526+
State('email', 'value'), # Valor atual do campo Email.
527+
State('tabela', 'data'), # Dados atuais da tabela.
528+
State('tabela', 'selected_rows')] # Índices das linhas selecionadas na tabela.
529+
)
530+
531+
# A função atualizar_tabela é definida como o callback que será disparado quando os Inputs e States especificados forem alterados.
532+
def atualizar_tabela(cliques_adicionar, cliques_alterar, cliques_pesquisar, cliques_excluir, cliques_limpar,
533+
cliques_exportar, cliques_email, valor_cep, celula_ativa, dados_anteriores, cep, nome, endereco,
534+
celular, telefone, email, dados_tabela, linhas_selecionadas):
535+
536+
# A palavra-chave global é usada para indicar que a variável dataframe_original é uma variável global.
537+
# Isso permite que a função modifique a variável fora do seu escopo local.
538+
global dataframe_original
539+
540+
# dash.callback_context fornece informações sobre o componente que disparou o callback.
541+
# Isso é útil quando você tem múltiplos Inputs e precisa saber qual deles foi acionado.
542+
contexto = dash.callback_context
543+
544+
# Se nenhum Input foi disparado (por exemplo, na inicialização da aplicação), a função raise PreventUpdate é chamada.
545+
# Isso evita que o callback atualize qualquer Output, efetivamente "cancelando" a execução do callback.
546+
if not contexto.triggered:
547+
raise PreventUpdate
548+
549+
# O Input que disparou o callback é identificado e o seu id é armazenado na variável input_ativado.
550+
# A propriedade 'prop_id' contém o id e a propriedade do Input que disparou o callback, separados por um ponto.
551+
# O método split('.') é usado para separar o id do nome da propriedade, e somente o id é usado.
552+
input_ativado = contexto.triggered[0]['prop_id'].split('.')[0]
553+
554+
# Um novo DataFrame df é criado a partir dos dados atuais da tabela.
555+
# Isso é feito para que qualquer modificação nos dados possa ser feita neste DataFrame local, sem afetar os dados originais.
556+
df = pd.DataFrame(dados_tabela)
557+
558+
# Este bloco de código é executado se o Input que disparou o callback é o campo 'cep'.
559+
# A variável input_ativado contém o id do componente que disparou o callback, que neste caso é 'cep'.
560+
if input_ativado == 'cep':
561+
562+
# https://www.consultarcep.com.br/rj/rio-de-janeiro/
563+
564+
# A função buscar_cep é chamada com o valor atual do campo 'cep' (valor_cep) como argumento.
565+
# Esta função é responsável por buscar informações de endereço com base no CEP fornecido.
566+
endereco = buscar_cep(valor_cep)
567+
568+
# Se a função buscar_cep retornar None (o que significa que o CEP não foi encontrado),
569+
# a variável endereco é atualizada com a string "CEP não encontrado".
570+
if endereco is None:
571+
endereco = "CEP não encontrado"
572+
573+
# O callback então retorna as atualizações para os Outputs.
574+
# Neste caso, apenas o valor do campo 'endereco' é atualizado com o valor da variável endereco.
575+
# Todos os outros Outputs são mantidos como estão, o que é indicado por dash.no_update.
576+
return dash.no_update, dash.no_update, dash.no_update, endereco, dash.no_update, dash.no_update, dash.no_update
577+
578+
# Este bloco de código é executado se o Input que disparou o callback é a 'tabela'.
579+
# A variável input_ativado contém o id do componente que disparou o callback, que neste caso é 'tabela'.
580+
elif input_ativado == 'tabela':
581+
582+
# Verifica se a variável dados_anteriores não é None.
583+
# dados_anteriores contém o estado anterior dos dados da tabela antes de qualquer edição.
584+
if dados_anteriores is not None:
585+
586+
# Compara o tamanho dos dados anteriores e dos dados atuais da tabela.
587+
# Se os dados anteriores são maiores em tamanho, isso indica que uma linha foi excluída.
588+
if len(dados_anteriores) > len(dados_tabela):
589+
590+
# Salva o DataFrame df atualizado como um arquivo Excel.
591+
df.to_excel('Dados_Agenda.xlsx', sheet_name='Dados', index=False)
592+
593+
# Retorna os dados atualizados da tabela e limpa todos os outros campos.
594+
return df.to_dict('records'), '', '', '', '', '', ''
595+
596+
# Verifica se a variável celula_ativa não é None.
597+
# celula_ativa contém informações sobre a célula que está atualmente ativa (selecionada) na tabela.
598+
599+
if celula_ativa is not None:
600+
601+
# Extrai o índice da linha da célula ativa.
602+
linha = celula_ativa['row']
603+
604+
# Usa o índice da linha para obter os dados da linha correspondente no DataFrame df.
605+
dados_selecionados = df.iloc[linha]
606+
607+
# Retorna os valores dos campos CEP, Nome, Endereço, Celular, Telefone e Email dessa linha específica.
608+
# dash.no_update é usado para os outros Outputs que não precisam ser atualizados.
609+
return dash.no_update, dados_selecionados['CEP'], dados_selecionados['Nome'], dados_selecionados[
610+
'Endereco'], dados_selecionados['Celular'], dados_selecionados['Telefone'], dados_selecionados['Email']
611+
612+
613+
614+
# Este bloco de código é executado se o Input que disparou o callback é o botão 'botao_adicionar'.
615+
# A variável input_ativado contém o id do componente que disparou o callback, que neste caso é 'botao_adicionar'.
616+
elif input_ativado == 'botao_adicionar':
617+
618+
# Um novo DataFrame chamado nova_linha é criado para armazenar os valores dos campos de entrada: cep, nome, endereco, celular, telefone e email.
619+
# As colunas deste novo DataFrame são as mesmas que as do DataFrame df existente.
620+
nova_linha = pd.DataFrame([[cep, nome, endereco, celular, telefone, email]], columns=df.columns)
621+
622+
# O novo DataFrame nova_linha é concatenado ao DataFrame df existente.
623+
# O parâmetro ignore_index=True reindexa o DataFrame resultante para que os índices sejam contínuos.
624+
df = pd.concat([df, nova_linha], ignore_index=True)
625+
626+
# O DataFrame df atualizado é salvo como um arquivo Excel com o nome 'Dados_Agenda.xlsx' e a aba 'Dados'.
627+
# O parâmetro index=False evita que os índices do DataFrame sejam salvos no arquivo Excel.
628+
df.to_excel('Dados_Agenda.xlsx', sheet_name='Dados', index=False)
629+
630+
# O callback retorna os dados atualizados da tabela e limpa todos os outros campos.
631+
# O DataFrame df é convertido para um dicionário de registros para ser compatível com a tabela Dash.
632+
# Todos os outros campos são limpos, retornando strings vazias.
633+
return df.to_dict('records'), '', '', '', '', '', ''
634+
635+
636+
# Este bloco de código é ativado quando o Input que disparou o callback é o botão 'botao_alterar'.
637+
# A variável input_ativado contém o id do componente que disparou o callback, neste caso, 'botao_alterar'.
638+
elif input_ativado == 'botao_alterar':
639+
640+
# Verifica se alguma linha foi selecionada na tabela.
641+
# Se nenhuma linha foi selecionada (ou seja, linhas_selecionadas é None ou sua extensão é 0),
642+
# a atualização é impedida usando o comando PreventUpdate.
643+
if linhas_selecionadas is None or len(linhas_selecionadas) == 0:
644+
raise PreventUpdate
645+
646+
# Itera sobre cada índice de linha selecionada na variável linhas_selecionadas.
647+
for i in linhas_selecionadas:
648+
649+
# Atualiza o valor da coluna 'CEP' na linha i do DataFrame df se o valor de cep não for None.
650+
if cep is not None:
651+
df.loc[i, 'CEP'] = cep
652+
653+
# Similarmente, atualiza os valores das outras colunas com base nas entradas fornecidas, se elas não forem None.
654+
if nome is not None:
655+
df.loc[i, 'Nome'] = nome
656+
if endereco is not None:
657+
df.loc[i, 'Endereco'] = endereco
658+
if celular is not None:
659+
df.loc[i, 'Celular'] = celular
660+
if telefone is not None:
661+
df.loc[i, 'Telefone'] = telefone
662+
if email is not None:
663+
df.loc[i, 'Email'] = email
664+
665+
# Após realizar todas as alterações, o DataFrame df atualizado é salvo como um arquivo Excel.
666+
df.to_excel('Dados_Agenda.xlsx', sheet_name='Dados', index=False)
667+
668+
# Finalmente, o callback retorna os dados atualizados da tabela e limpa todos os outros campos de entrada.
669+
# O DataFrame df é convertido para um dicionário de registros para atualizar a tabela da interface do usuário.
670+
return df.to_dict('records'), '', '', '', '', '', ''
671+
672+
673+
# Este bloco de código é ativado quando o botão 'Pesquisar' é clicado.
674+
elif input_ativado == 'botao_pesquisar':
675+
676+
# Cria uma máscara booleana inicial com todos os valores como True.
677+
# Essa máscara terá o mesmo número de linhas que o DataFrame 'df'.
678+
# A ideia é que, inicialmente, todos os registros são elegíveis para serem retornados pela pesquisa.
679+
final_mask = np.array([True] * len(df))
680+
681+
# Para cada campo de entrada, atualizamos a máscara booleana para refletir os registros que satisfazem a condição.
682+
# Utilizamos a operação 'logical_and' para combinar a máscara atual com a nova condição.
683+
# Assim, a máscara final só conterá 'True' para os registros que satisfazem todas as condições.
684+
685+
# Atualiza a máscara se o campo 'Nome' não estiver vazio.
686+
if nome:
687+
final_mask = np.logical_and(final_mask, df['Nome'].str.contains(nome, case=False))
688+
689+
# Atualiza a máscara se o campo 'CEP' não estiver vazio.
690+
if cep:
691+
final_mask = np.logical_and(final_mask, df['CEP'].str.contains(cep, case=False))
692+
693+
# Atualiza a máscara se o campo 'Endereco' não estiver vazio.
694+
if endereco:
695+
final_mask = np.logical_and(final_mask, df['Endereco'].str.contains(endereco, case=False))
696+
697+
# Atualiza a máscara se o campo 'Celular' não estiver vazio.
698+
if celular:
699+
final_mask = np.logical_and(final_mask, df['Celular'].str.contains(celular, case=False))
700+
701+
# Atualiza a máscara se o campo 'Telefone' não estiver vazio.
702+
if telefone:
703+
final_mask = np.logical_and(final_mask, df['Telefone'].str.contains(telefone, case=False))
704+
705+
# Atualiza a máscara se o campo 'Email' não estiver vazio.
706+
if email:
707+
final_mask = np.logical_and(final_mask, df['Email'].str.contains(email, case=False))
708+
709+
# Aplica a máscara booleana final ao DataFrame para filtrar os registros.
710+
# Isso resulta em um novo DataFrame que contém apenas os registros que satisfazem todas as condições de pesquisa.
711+
resultado_pesquisa = df[final_mask]
712+
713+
# Retorna o resultado da pesquisa como um dicionário de registros.
714+
# Os argumentos 'dash.no_update' indicam que os outros outputs do callback não devem ser atualizados.
715+
return resultado_pesquisa.to_dict(
716+
'records'), dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
717+
718+
719+
# Este bloco de código é ativado quando o botão 'botao_excluir' é pressionado.
720+
# A variável 'input_ativado' conterá o valor 'botao_excluir', indicando que este botão disparou o callback.
721+
elif input_ativado == 'botao_excluir':
722+
723+
# Primeiramente, verifica se qualquer linha foi selecionada na tabela.
724+
# 'linhas_selecionadas' é uma lista dos índices das linhas selecionadas.
725+
# Se nenhuma linha for selecionada, o código impede qualquer atualização adicional usando 'raise PreventUpdate'.
726+
if linhas_selecionadas is None or len(linhas_selecionadas) == 0:
727+
raise PreventUpdate
728+
729+
# O método 'drop' do DataFrame é utilizado para excluir as linhas selecionadas.
730+
# 'axis=0' especifica que queremos excluir linhas (e não colunas).
731+
# 'inplace=True' modifica o DataFrame original.
732+
df.drop(linhas_selecionadas, axis=0, inplace=True)
733+
734+
# O índice do DataFrame é redefinido e o antigo índice é descartado.
735+
df.reset_index(drop=True, inplace=True)
736+
737+
# O DataFrame modificado é salvo em um arquivo Excel.
738+
df.to_excel('Dados_Agenda.xlsx', sheet_name='Dados', index=False)
739+
740+
# Retorna os dados atualizados para preencher a tabela na interface do usuário.
741+
# O DataFrame 'df' é convertido para um formato de dicionário de registros.
742+
# Todos os outros campos de entrada são limpos e retornam strings vazias.
743+
return df.to_dict('records'), '', '', '', '', '', ''
744+
745+
746+
# Este bloco é ativado quando o botão 'botao_limpar' é clicado.
747+
# A variável input_ativado conterá o valor 'botao_limpar', indicando que este botão disparou o callback.
748+
elif input_ativado == 'botao_limpar':
749+
750+
# Um bloco try-except é utilizado para capturar qualquer erro que
751+
# possa ocorrer durante a leitura do arquivo Excel.
752+
try:
753+
754+
# Tenta ler o arquivo 'Dados_Agenda.xlsx' na planilha 'Dados' para um novo DataFrame chamado novo_df.
755+
novo_df = pd.read_excel('Dados_Agenda.xlsx', sheet_name='Dados')
756+
757+
# Cria uma cópia profunda do novo DataFrame para a variável global dataframe_original.
758+
# Isso é feito para que qualquer operação futura não afete o DataFrame original.
759+
dataframe_original = novo_df.copy()
760+
761+
# Retorna os dados atualizados para preencher a tabela na interface do usuário.
762+
# O DataFrame dataframe_original é convertido em um dicionário de registros para ser compatível com a tabela Dash.
763+
# Todos os outros campos de entrada são limpos e retornam strings vazias.
764+
return dataframe_original.to_dict('records'), '', '', '', '', '', ''
765+
766+
# Se ocorrer um FileNotFoundError (por exemplo, se o arquivo Excel não for encontrado),
767+
# o código entrará no bloco except.
768+
except FileNotFoundError:
769+
770+
# Neste caso, retorna um dash.no_update para a tabela, indicando que os dados da tabela não devem ser atualizados.
771+
# Todos os outros campos de entrada são limpos e retornam strings vazias.
772+
return dash.no_update, '', '', '', '', '', ''
773+
774+
775+
# Este bloco de código é ativado quando o botão 'botao_exportar' é clicado.
776+
# A variável 'input_ativado' conterá o valor 'botao_exportar', sinalizando que
777+
# este botão disparou o callback.
778+
elif input_ativado == 'botao_exportar':
779+
780+
# Um bloco try-except é usado para capturar qualquer exceção que possa
781+
# ocorrer durante a exportação dos dados para o Excel.
782+
try:
783+
784+
# A função os.path.abspath é usada para obter o caminho absoluto onde o arquivo Excel exportado será salvo.
785+
caminho_exportacao = os.path.abspath('Dados_Agenda_Exportado.xlsx')
786+
787+
# O método to_excel do DataFrame 'df' é usado para salvar os dados no arquivo Excel.
788+
# O parâmetro 'sheet_name' especifica o nome da aba do Excel e 'index=False' evita que os índices do DataFrame sejam exportados.
789+
df.to_excel(caminho_exportacao, sheet_name='Dados', index=False)
790+
791+
# O bloco except captura qualquer tipo de exceção e imprime uma mensagem de erro.
792+
except Exception as e:
793+
print(f"Erro ao exportar: {e}")
794+
795+
# Independentemente de a exportação ser bem-sucedida ou não, este
796+
# retorno garante que a tabela e os campos de entrada não sejam atualizados.
797+
return dash.no_update, '', '', '', '', '', ''
798+
799+
800+
# Este bloco de código é ativado quando o botão 'botao_email' é pressionado na interface do usuário.
801+
# A variável 'input_ativado' terá o valor 'botao_email', indicando que este botão é o responsável por disparar o callback.
802+
elif input_ativado == 'botao_email':
803+
804+
# Um bloco try-except é usado para capturar qualquer exceção que possa ocorrer durante o envio de e-mails.
805+
try:
806+
807+
# O método 'iterrows()' do DataFrame 'df' é usado para iterar sobre todas as linhas do DataFrame.
808+
# Cada 'linha' é uma Series do Pandas que contém todos os dados de uma linha individual do DataFrame.
809+
for _, linha in df.iterrows():
810+
811+
# A função 'enviar_email_com_outlook' é chamada para cada linha do DataFrame.
812+
# Esta função foi definida anteriormente no script para enviar um e-mail usando a API do Outlook.
813+
enviar_email_com_outlook(linha)
814+
815+
# O bloco 'except' captura qualquer tipo de exceção que possa ocorrer durante o envio de e-mails.
816+
# A exceção é armazenada na variável 'e', e uma mensagem de erro é impressa no console.
817+
except Exception as e:
818+
print(f"Erro ao criar e-mails: {e}")
819+
820+
# Independentemente do sucesso ou falha do envio de e-mails, o código retorna 'dash.no_update' para todos os Outputs.
821+
# Isso significa que a interface do usuário não será atualizada.
822+
return dash.no_update, '', '', '', '', '', ''
823+
824+
# Este é o retorno padrão se nenhum dos blocos 'elif' anteriores for ativado.
825+
# Ele evita qualquer atualização na interface do usuário.
826+
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
827+
828+
# Este é o ponto de entrada principal do script.
829+
# Ele inicia o servidor Dash na porta 8055 e com a depuração ativada.
830+
if __name__ == '__main__':
831+
app.run_server(debug=True, port=8055)

‎app.py

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Importando módulos necessários do Flask e o módulo random
2+
from flask import Flask, render_template, request, session
3+
import random
4+
5+
# Criando uma instância da aplicação Flask.
6+
# O argumento __name__ ajuda a Flask a identificar a raiz do projeto.
7+
app = Flask(__name__)
8+
9+
# Definindo uma chave secreta para a sessão.
10+
# Esta chave é utilizada para manter as sessões do usuário seguras.
11+
# É importante definir uma chave secreta forte e mantê-la protegida.
12+
app.secret_key = 'sua_chave_secreta'
13+
14+
# Uma lista de palavras para o jogo de anagramas.
15+
# Estas são as palavras que serão embaralhadas e apresentadas aos jogadores.
16+
palavras = ['PYTHON', 'FLASK', 'DESENVOLVIMENTO', 'WEB', 'PROGRAMAÇÃO', 'DJANGO', 'CURSO', 'CARRO', 'JANELA']
17+
18+
# Definição da função gerar_anagrama, que aceita uma palavra como argumento.
19+
def gerar_anagrama(palavra):
20+
21+
# A palavra é convertida em uma lista de letras.
22+
palavra_embaralhada = list(palavra)
23+
24+
# A função shuffle do módulo random é usada para embaralhar as letras da palavra.
25+
random.shuffle(palavra_embaralhada)
26+
27+
# A lista de letras embaralhadas é unida de volta em uma string e retornada.
28+
# Isso resulta no anagrama da palavra original.
29+
return ''.join(palavra_embaralhada)
30+
31+
32+
33+
# A linha abaixo define uma rota em uma aplicação Flask.
34+
# '@app.route('/')' é um decorador que diz ao Flask que sempre que
35+
# um navegador acessar o endereço raiz da aplicação (indicado por '/'),
36+
# a função 'inicio' deve ser chamada.
37+
@app.route('/')
38+
def inicio():
39+
40+
# Esta função 'inicio' é chamada quando a rota raiz é acessada.
41+
42+
# 'session' é um dicionário especial do Flask que permite armazenar
43+
# informações que são específicas para um usuário de uma sessão para outra.
44+
# Aqui, uma palavra aleatória é escolhida da lista 'palavras' definida anteriormente.
45+
# Esta palavra é armazenada na sessão sob a chave 'palavra'.
46+
session['palavra'] = random.choice(palavras)
47+
48+
# A função 'gerar_anagrama' é chamada com a palavra escolhida.
49+
# O resultado, que é um anagrama da palavra, é armazenado na sessão
50+
# sob a chave 'anagrama'.
51+
session['anagrama'] = gerar_anagrama(session['palavra'])
52+
53+
# A função 'render_template' é usada para renderizar um template HTML.
54+
# 'inicio.html' é o nome do arquivo de template que será renderizado.
55+
# O segundo argumento passado para 'render_template' é uma variável chamada 'anagrama'.
56+
# Esta variável está disponível no template HTML e contém o anagrama da palavra escolhida,
57+
# que é retirado da sessão.
58+
return render_template('inicio.html', anagrama=session['anagrama'])
59+
60+
61+
62+
# O decorador '@app.route' é usado para associar a função 'verificar'
63+
# à rota '/verificar' no servidor web Flask. O parâmetro 'methods=['POST']'
64+
# especifica que esta rota aceitará apenas solicitações POST, que são
65+
# tipicamente usadas para enviar dados de formulários.
66+
@app.route('/verificar', methods=['POST'])
67+
def verificar():
68+
69+
# Esta função é chamada quando um usuário envia um formulário para a rota '/verificar'.
70+
71+
# 'request.form.get('resposta', '').strip().upper()' faz várias coisas:
72+
# 1. 'request.form' é um dicionário que contém os dados enviados pelo usuário.
73+
# 2. '.get('resposta', '')' obtém o valor associado à chave 'resposta' no formulário.
74+
# Se 'resposta' não estiver presente, retorna uma string vazia ('').
75+
# 3. '.strip()' remove espaços em branco antes e depois da string obtida.
76+
# 4. '.upper()' converte a string para letras maiúsculas.
77+
resposta = request.form.get('resposta', '').strip().upper()
78+
79+
# Aqui a palavra original armazenada na sessão (sob a chave 'palavra') é recuperada
80+
# e convertida em letras maiúsculas para garantir uma comparação adequada.
81+
palavra_secreta = session['palavra'].upper()
82+
83+
# Esta estrutura condicional compara a resposta do usuário com a palavra secreta.
84+
if resposta == palavra_secreta:
85+
86+
# Se a resposta for igual à palavra secreta, define uma mensagem de sucesso
87+
# e uma classe CSS para exibir a mensagem com um estilo de sucesso.
88+
mensagem = 'Correto! Você acertou.'
89+
alert_class = 'alert-success'
90+
91+
else:
92+
93+
# Se a resposta não for igual à palavra secreta, define uma mensagem de erro
94+
# e uma classe CSS para exibir a mensagem com um estilo de erro.
95+
mensagem = 'Incorreto. Tente novamente.'
96+
alert_class = 'alert-danger'
97+
98+
# 'render_template' renderiza o template 'resultado.html', passando as variáveis
99+
# 'mensagem' e 'alert_class' para ele. Estas variáveis podem ser usadas no template
100+
# para mostrar ao usuário se ele acertou ou errou a resposta.
101+
return render_template('resultado.html', mensagem=mensagem, alert_class=alert_class)
102+
103+
104+
# Verifica se este script está sendo executado como o principal e não como um módulo importado.
105+
if __name__ == '__main__':
106+
107+
# Inicia a aplicação Flask com o modo de depuração ativado.
108+
app.run(debug=True)

0 commit comments

Comments
 (0)
Please sign in to comment.