Grafos implementado

This commit is contained in:
Joao Monezi 2025-06-21 20:01:29 +00:00
parent 4ff6ee2e42
commit 53d57e4fa3
5 changed files with 459 additions and 61 deletions

Binary file not shown.

View File

@ -0,0 +1,141 @@
# Consultme/app/dialog_flow/graph_definition.py
from typing import Dict, Any, List
# --- DEFINIÇÃO DOS ESTADOS DO SEU CHATBOT (O GRAFO) ---
DIALOG_GRAPH: Dict[str, Dict[str, Any]] = {
"INICIO": {
"message": "",
"expected_input_type": "text",
"transitions": {
"default": "MENU_PRINCIPAL"
}
},
"MENU_PRINCIPAL": {
"message": "*Olá você inicou o Consultme!:*",
"action_to_perform": "send_main_menu", # Ação para enviar um menu interativo (botões)
"expected_input_type": "button_click",
"transitions": {
"OPTION_AGENDAR": "MENU_PRINCIPAL_STORE",
"OPTION_CADASTRO_FLOW": "MENU_PRINCIPAL_STORE",
"OPTION_STATUS": "MENU_PRINCIPAL_STORE",
"OPTION_FALAR_ATENDENTE": "MENU_PRINCIPAL_STORE",
"default": "MENU_PRINCIPAL_STORE" # Volta para o menu se a opção não for reconhecida
}
},
"MENU_PRINCIPAL_STORE": {
"action_to_perform": "send_main_store", # Ação para enviar um menu interativo (botões)
"expected_input_type": "button_click",
"transitions": {
"OPTION_AGENDAR": "AGENDAMENTO_INICIO",
"OPTION_CADASTRO_FLOW": "INICIAR_FLOW_CADASTRO",
"OPTION_STATUS": "PEDIR_NUMERO_PEDIDO",
"OPTION_FALAR_ATENDENTE": "ENCAMINHAR_ATENDENTE",
"default": "MENU_PRINCIPAL" # Volta para o menu se a opção não for reconhecida
}
},
"RESPOSTA_NAO_ENTENDIDA": {
"message": "Desculpe, não entendi sua última mensagem. Por favor, digite 'menu' para ver as opções ou 'ajuda'.",
"expected_input_type": "text",
"transitions": {
"menu": "MENU_PRINCIPAL",
"ajuda": "MENU_PRINCIPAL",
"default": "RESPOSTA_NAO_ENTENDIDA" # Continua não entendendo
}
},
# --- FLUXO DE CADASTRO (com Flow) ---
"INICIAR_FLOW_CADASTRO": {
"action_to_perform": "send_flow_cadastro", # Ação para enviar o Flow
"flow_id": "COLOQUE_AQUI_O_FLOW_ID_DO_SEU_CADASTRO_PUBLICADO", # ID do seu Flow publicado
"flow_cta": "Abrir Cadastro",
"expected_input_type": "flow_nfm_reply", # Espera a resposta do Flow
"transitions": {
"success": "CADASTRO_CONCLUIDO", # Se o Flow for concluído com sucesso
"failure": "CADASTRO_FALHA" # Se houver um problema no Flow (ou o usuário não preencher)
}
},
"CADASTRO_CONCLUIDO": {
"message": "Obrigado por se cadastrar, ${nome_completo}! Seu e-mail é: ${email}. Já pode explorar nossos serviços!",
"action_to_perform": "process_cadastro_data", # Ação para salvar no BD/CRM
"expected_input_type": "any", # Qualquer coisa leva ao menu
"transitions": {
"default": "MENU_PRINCIPAL"
}
},
"CADASTRO_FALHA": {
"message": "Não foi possível completar seu cadastro. Por favor, tente novamente ou digite 'ajuda'.",
"expected_input_type": "text",
"transitions": {
"default": "MENU_PRINCIPAL"
}
},
# --- FLUXO DE AGENDAMENTO (Exemplo Básico) ---
"AGENDAMENTO_INICIO": {
"message": "Certo! Para agendar, qual serviço você precisa?",
"expected_input_type": "text",
"transitions": {
"default": "AGENDAMENTO_CONFIRMAR_SERVICO"
}
},
"AGENDAMENTO_CONFIRMAR_SERVICO": {
"message": "Você precisa de *${servico_agendado}*. Qual data e horário você prefere?",
"expected_input_type": "text",
"transitions": {
"default": "AGENDAMENTO_FINALIZAR"
},
"action_to_perform": "save_temp_service" # Ação para guardar o serviço temporariamente
},
"AGENDAMENTO_FINALIZAR": {
"message": "Seu agendamento para *${servico_agendado}* em *${data_horario_agendado}* foi confirmado! Te vejo lá!",
"action_to_perform": "confirm_appointment", # Ação para agendar no sistema real
"expected_input_type": "any",
"transitions": {
"default": "MENU_PRINCIPAL"
}
},
# --- FLUXO DE STATUS DO PEDIDO ---
"PEDIR_NUMERO_PEDIDO": {
"message": "Por favor, digite o número do seu pedido para consultar o status:",
"expected_input_type": "text",
"transitions": {
"default": "CONSULTAR_STATUS_API"
}
},
"CONSULTAR_STATUS_API": {
"message": "Consultando o status do pedido *${numero_pedido}*...",
"action_to_perform": "call_external_status_api", # Ação para chamar um sistema externo (API futura)
"expected_input_type": "api_response", # A resposta vem de uma API, não do usuário
"transitions": {
"success": "STATUS_EXIBIR",
"failure": "STATUS_NAO_ENCONTRADO"
}
},
"STATUS_EXIBIR": {
"message": "O status do pedido *${numero_pedido}* é: *${status_retornado}*.",
"expected_input_type": "any",
"transitions": {
"default": "MENU_PRINCIPAL"
}
},
"STATUS_NAO_ENCONTRADO": {
"message": "Não consegui encontrar o pedido *${numero_pedido}*. Verifique e digite novamente, ou 'menu'.",
"expected_input_type": "text",
"transitions": {
"menu": "MENU_PRINCIPAL",
"default": "PEDIR_NUMERO_PEDIDO"
}
},
# --- FLUXO DE ATENDENTE ---
"ENCAMINHAR_ATENDENTE": {
"message": "Encaminhando você para um de nossos atendentes. Por favor, aguarde.",
"terminal": True # Indica que a conversa termina aqui (até o atendente assumir)
},
# ... Adicione mais estados conforme a complexidade da sua conversa ...
}
# --- ESTADO INICIAL ---
INITIAL_STATE_ID = "INICIO"

View File

@ -36,6 +36,10 @@ from cryptography.hazmat.backends import default_backend
from config import WHATSAPP_ACCESS_TOKEN, WHATSAPP_PHONE_NUMBER_ID, VERIFY_TOKEN, FLOW_PRIVATE_KEY_PASSWORD # <-- ADICIONADO
# Importe Message para type hinting. Certifique-se que webhook_model.py está correto primeiro!
# Certifique-se de importar o grafo e o estado inicial
from dialog_flow.graph_definition import DIALOG_GRAPH, INITIAL_STATE_ID
# Certifique-se de que FlowStatusChangeEvent também está importado se ainda não estiver
from models.webhook_model import Message, TextMessage, ButtonMessage, InteractiveMessage, ListReply # Adicione os modelos necessários
FLOW_PRIVATE_KEY_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../private.pem')
@ -254,88 +258,254 @@ def start_or_update_session(sender_id: str):
#Fluxo de mensagens:
# --- NOVA FUNÇÃO: O MOTOR DO CHATBOT BASEADO EM GRAFO ---
async def process_user_input_with_graph(sender_id: str, message_type: str, message_content: Any):
"""
Processa a entrada do usuário e avança o estado da conversa no grafo.
"""
session_data = get_session_state(sender_id)
if not session_data:
# Se a sessão não existe (timeout ou novo usuário), inicie do estado INICIO
current_state_id = INITIAL_STATE_ID
start_or_update_session(sender_id) # Garante que a sessão existe
session_data = get_session_state(sender_id) # Recarrega para ter certeza
else:
current_state_id = session_data.get("current_state", INITIAL_STATE_ID) # Pega o estado atual
current_state = DIALOG_GRAPH.get(current_state_id)
if not current_state:
print(f"❌ ERRO GRAVE: Estado '{current_state_id}' não encontrado no DIALOG_GRAPH. Retornando ao INICIO.")
current_state_id = INITIAL_STATE_ID
current_state = DIALOG_GRAPH[INITIAL_STATE_ID]
session_data["current_state"] = INITIAL_STATE_ID # Reseta o estado na sessão
print(f"DEBUG_GRAPH: Usuário {sender_id} no estado '{current_state_id}'. Tipo de entrada: {message_type}. Conteúdo: {message_content}")
next_state_id = None
user_input_processed = str(message_content).lower().strip() # Padroniza a entrada para transições
# --- Lógica para determinar o próximo estado (transição) ---
if message_type == "text":
# Verifica se alguma palavra-chave da transição corresponde ao texto
for keyword, target_state in current_state.get("transitions", {}).items():
if keyword != "default" and keyword in user_input_processed:
next_state_id = target_state
break
if not next_state_id: # Se nenhuma palavra-chave específica, usa o default
next_state_id = current_state.get("transitions", {}).get("default", INITIAL_STATE_ID)
session_data["last_text_input"] = message_content # Salva a última entrada de texto para uso futuro
elif message_type == "button_click":
# O message_content já é o payload do botão
next_state_id = current_state.get("transitions", {}).get(message_content, INITIAL_STATE_ID)
elif message_type == "flow_nfm_reply":
# Quando uma resposta de Flow chega, o next_state depende do sucesso/falha do Flow
# Assumimos que handle_flow_response já processou o Flow e determinou sucesso/falha
# A lógica para a transição do Flow precisa ser mais sofisticada.
# Por enquanto, vamos para o estado de conclusão do Flow.
next_state_id = current_state.get("transitions", {}).get("success", INITIAL_STATE_ID) # Por padrão, vai para o sucesso
session_data["flow_data"] = message_content # Salva os dados do Flow para processamento posterior
# ... (adicionar outros tipos de entrada, como "api_response" para transições internas, se necessário) ...
# Atualiza o estado da sessão
session_data["current_state"] = next_state_id
start_or_update_session(sender_id) # Atualiza o timestamp da sessão
print(f"DEBUG_GRAPH: Usuário {sender_id} transicionou para o estado: '{next_state_id}'")
await execute_state_action_and_respond(sender_id, next_state_id, session_data)
async def execute_state_action_and_respond(sender_id: str, state_id: str, session_data: Dict[str, Any]):
"""
Executa a ação de um estado e envia a resposta correspondente.
"""
state_definition = DIALOG_GRAPH.get(state_id)
if not state_definition:
print(f"❌ ERRO GRAVE: Definição para o estado '{state_id}' não encontrada. Não é possível responder.")
await send_text_message(sender_id, "Ops! Ocorreu um erro interno. Por favor, tente novamente.")
session_data["current_state"] = INITIAL_STATE_ID # Reseta o estado
start_or_update_session(sender_id)
return
# --- Ações a serem realizadas pelo bot no novo estado ---
if "action_to_perform" in state_definition:
action = state_definition["action_to_perform"]
print(f"DEBUG_GRAPH: Executando ação para estado '{state_id}': {action}")
if action == "send_main_menu":
await send_time_menu(sender_id)
elif action == "send_main_store":
await send_store_menu(sender_id)
elif action == "send_flow_cadastro":
flow_id = state_definition.get("flow_id")
flow_cta = state_definition.get("flow_cta")
if flow_id:
# O Flow ID real virá da DIALOG_GRAPH, não do SendFlowRequest
await send_whatsapp_flow(sender_id, flow_id, flow_cta)
else:
await send_text_message(sender_id, "Ops! O Flow de cadastro não está configurado. Tente novamente mais tarde.")
session_data["current_state"] = INITIAL_STATE_ID
start_or_update_session(sender_id)
elif action == "process_cadastro_data":
# Aqui você processaria os dados do Flow que estão em session_data["flow_data"]
flow_data = json.loads(session_data.get("flow_data", "{}")) # Carregar os dados salvos
nome_completo = flow_data.get("nome_completo")
email = flow_data.get("email")
if nome_completo and email:
print(f"DEBUG_GRAPH: Processando cadastro para {nome_completo}, {email}")
# AQUI você salvaria no BD/CRM
# salvar_usuario_no_banco_de_dados(sender_id, nome_completo, email)
# enviar_para_crm(nome_completo, email)
response_message = state_definition["message"].replace("${nome_completo}", nome_completo).replace("${email}", email)
await send_text_message(sender_id, response_message)
else:
await send_text_message(sender_id, "Falha ao processar cadastro. Dados incompletos. Por favor, tente novamente.")
session_data["current_state"] = DIALOG_GRAPH.get("CADASTRO_FALHA", {}).get("transitions",{}).get("default", INITIAL_STATE_ID) # Ir para estado de falha
start_or_update_session(sender_id)
elif action == "save_temp_service":
# Exemplo: guardar o serviço agendado
# service = session_data.get("last_text_input") # Pega o último texto que o usuário enviou
# session_data["servico_agendado"] = service
# start_or_update_session(sender_id) # Atualiza a sessão
pass # Ação interna, não envia mensagem aqui
elif action == "confirm_appointment":
# Exemplo: confirmar agendamento no sistema externo
# (Chamada de API para sistema de agendamento)
pass # Ação interna, a mensagem já vem do state_definition
elif action == "call_external_status_api":
# AQUI é onde você faria a chamada para o seu sistema externo
# Por enquanto, apenas um placeholder
# numero_pedido = session_data.get("last_text_input")
# print(f"DEBUG_GRAPH: Chamando API externa para pedido {numero_pedido}")
# try:
# status_api = await external_api_call(numero_pedido)
# session_data["status_retornado"] = status_api # Salva na sessão
# # Transicionar internamente para STATUS_EXIBIR ou STATUS_NAO_ENCONTRADO
# await execute_state_action_and_respond(sender_id, "STATUS_EXIBIR", session_data) # Transição interna
# return
# except Exception:
# await execute_state_action_and_respond(sender_id, "STATUS_NAO_ENCONTRADO", session_data) # Transição interna
# return
pass # Implementação futura, a mensagem vem do estado.
# --- Enviar a mensagem do novo estado (se houver) ---
if "message" in state_definition:
# Substitui placeholders na mensagem
final_message = state_definition["message"]
if "${nome_completo}" in final_message and session_data.get("flow_data"):
flow_data = json.loads(session_data["flow_data"])
final_message = final_message.replace("${nome_completo}", flow_data.get("nome_completo", "usuário"))
if "${email}" in final_message and session_data.get("flow_data"):
flow_data = json.loads(session_data["flow_data"])
final_message = final_message.replace("${email}", flow_data.get("email", "não informado"))
# Para outros placeholders como ${servico_agendado}, ${data_horario_agendado}, ${numero_pedido}, ${status_retornado}
# você faria substituições semelhantes baseadas em session_data
await send_text_message(sender_id, final_message)
# Lógica para limpar a sessão se o estado for terminal
if state_definition.get("terminal"):
await clean_session_and_notify(sender_id, send_timeout_message=False) # Não envia msg de timeout
print(f"DEBUG_GRAPH: Sessão para {sender_id} encerrada no estado terminal '{state_id}'.")
# --- Lógicas de Tratamento de Mensagens Recebidas (Funções Auxiliares) ---
# Estas funções contêm a lógica de como seu bot irá interagir.
# Esta função precisa ser definida no arquivo!
# --- handle_message_type: Agora é o ponto de entrada principal que chama o motor do grafo ---
#--- FUNÇÃO handle_message_type: A VERSÃO CORRETA PARA O TIMEOUT E GRAFO ---
async def handle_message_type(message: Message):
sender_id = message.from_
# 1. Iniciar ou atualizar a sessão para o remetente atual
# Isso vai automaticamente agendar/reagendar a limpeza.
start_or_update_session(sender_id) # Esta função agora agenda a limpeza!
# 1. Limpar sessões inativas
# Esta função limpa as sessões que expiraram e envia a mensagem de timeout via scheduler.
start_or_update_session(sender_id) # Esta é a que precisa ser assíncrona
# 2. Recuperar o estado da sessão (se houver, para uso futuro com grafos)
# 2. INICIAR OU ATUALIZAR A SESSÃO PARA O REMETENTE ATUAL.
# ISSO É ESSENCIAL PARA O TIMEOUT. Se o usuário mandar mensagem, a sessão dele é atualizada
# e o agendamento de timeout é resetado.
start_or_update_session(sender_id)
# 3. Recuperar o estado da sessão (já atualizado)
session_data = get_session_state(sender_id)
if session_data:
print(f"DEBUG_SESSION: Estado atual da sessão para {sender_id}: {session_data['current_state']}")
else:
# Isso não deveria acontecer se start_or_update_session funcionou
print(f"DEBUG_SESSION: Sessão para {sender_id} não encontrada após start_or_update_session (possível erro).")
print(f"DEBUG_SESSION: ERRO: Sessão para {sender_id} não encontrada após atualização. Isso é inesperado.")
# Se por algum motivo não tiver sessão, podemos resetar para o estado inicial
start_or_update_session(sender_id)
session_data = get_session_state(sender_id) # Tenta novamente
message_content = None
message_type = None
# --- IDENTIFICAÇÃO DO TIPO DE MENSAGEM ---
# Esta parte permanece como estava, identificando o tipo e conteúdo.
if message.type == 'text' and message.text:
await handle_text_message(sender_id, message.text.body) # Passe sender_id
elif message.type == 'button' and message.button:
await handle_button_response(sender_id, message.button.payload) # Passe sender_id
message_type = "text"
message_content = message.text.body
elif message.type == 'button' and message.button: # Resposta de botão de resposta rápida
message_type = "button_click"
message_content = message.button.payload
elif message.type == 'interactive' and message.interactive:
if message.interactive.type == 'list_reply' and message.interactive.list_reply:
await handle_list_response(sender_id, message.interactive.list_reply.id, message.interactive.list_reply.title) # Passe sender_id
elif message.interactive.type == 'button_reply' and message.interactive.button_reply:
await handle_button_response(sender_id, message.interactive.button_reply.payload) # Passe sender_id
if message.interactive.type == 'list_reply' and message.interactive.list_reply: # Resposta de lista
message_type = "list_reply"
message_content = message.interactive.list_reply.id
elif message.interactive.type == 'button_reply' and message.interactive.button_reply: # Resposta de botão interativo
message_type = "button_click" # Tratar como clique de botão
message_content = message.interactive.button_reply.id
elif message.interactive.type == 'nfm_reply' and message.interactive.nfm_reply: # Resposta de Flow
message_type = "flow_nfm_reply"
message_content = message.interactive.nfm_reply.response_json
else:
print(f" Tipo interativo desconhecido recebido: {message.interactive.type}")
await send_text_message(sender_id, "Desculpe, não entendi essa interação interativa.")
elif message.type == 'image':
print(' Recebi uma imagem!')
await send_text_message(sender_id, "Que legal! Recebi sua imagem. No momento, só consigo processar texto e interações.")
else:
print(f" Tipo de mensagem não suportado: {message.type}")
return # Sai, não há transição no grafo para isso
elif message.type == 'image': # Mensagens que não são processadas pelo grafo
message_type = "image"
message_content = "imagem_recebida" # Placeholder
await send_text_message(sender_id, "Recebi sua imagem. No momento, só consigo processar texto e interações.")
return # Sai, não há transição no grafo para isso
else: # Tipo de mensagem não suportado ou desconhecido
message_type = "unsupported"
message_content = "tipo_desconhecido"
await send_text_message(sender_id, "Desculpe, não entendi o tipo de mensagem que você enviou.")
return # Sai
# 4. O motor do grafo processa a entrada e gerencia o estado da sessão.
# A `session_data` já foi atualizada no passo 2.
await process_user_input_with_graph(sender_id, message_type, message_content)
async def handle_text_message(sender_id: str, text: str):
lower_text = text.lower()
if lower_text != "" :
await send_text_message(sender_id, "Olá! Você iniciou o Consultme. Escolha uma das opções de consulta no menu abaixo:")
await send_interactive_menu(sender_id) # Chama a função para enviar um menu interativo
elif 'menu' in lower_text:
await send_interactive_menu(sender_id)
elif 'cadastro' in lower_text:
await send_text_message(sender_id, "Para iniciar seu cadastro, por favor, me diga seu nome completo:")
# Implemente lógica para salvar o estado do usuário aqui (ex: em um DB)
elif 'ajuda' in lower_text:
await send_text_message(sender_id, "Posso te guiar com as funcionalidades principais. Escolha uma opção do menu ou digite uma pergunta.")
await send_interactive_menu(sender_id)
else:
await send_text_message(sender_id, f"Recebi sua mensagem: \"{text}\". Parece que não entendi bem. Você pode digitar 'menu' para ver as opções disponíveis ou 'ajuda'.")
async def handle_button_response(sender_id: str, payload: str):
response_text = ''
if payload == 'OPTION_AGENDAR':
response_text = "Certo! Para agendar um serviço, qual serviço você precisa e a data/hora preferida?"
elif payload == 'OPTION_STATUS':
response_text = "Para verificar o status de seu pedido, informe o número do seu pedido."
elif payload == 'OPTION_FALAR_ATENDENTE':
response_text = "Encaminhando você para um de nossos atendentes. Aguarde, por favor."
else:
response_text = "Não entendi a opção de botão selecionada. Tente novamente ou digite 'menu'."
await send_text_message(sender_id, response_text)
async def handle_list_response(sender_id: str, list_id: str, list_title: str):
response_text = ''
if list_id == 'item_reparo_geral':
response_text = f"Você selecionou \"{list_title}\". Qual o problema específico que você precisa de reparo?"
elif list_id == 'item_instalacao':
response_text = f"Você selecionou \"{list_title}\". Qual tipo de instalação você precisa?"
elif list_id == 'item_duvidas':
response_text = f"Você selecionou \"{list_title}\". Por favor, digite sua pergunta."
elif list_id == 'item_reclamacoes':
response_text = f"Você selecionou \"{list_title}\". Por favor, descreva o problema em detalhes."
else:
response_text = "Opção de lista não reconhecida. Tente novamente ou digite 'menu'."
await send_text_message(sender_id, response_text)
# --- Funções de Envio de Mensagens para o WhatsApp ---
# Estas funções fazem as chamadas à API da Meta para enviar mensagens.
@ -449,8 +619,85 @@ async def mark_message_as_read(message_id: str):
except httpx.RequestError as e:
print(f"❌ Erro de rede ao marcar mensagem {message_id} como lida: {e}")
# --- Função de Exemplo para Enviar um Menu Interativo ---
async def send_interactive_menu(to: str):
# --- NOVA FUNÇÃO: send_stores_list_menu (Para menu de LISTA de lojas) ---
async def send_time_menu(to: str): # <-- FUNÇÃO QUE VOCÊ PEDIU
"""
Envia uma mensagem interativa de LISTA para o usuário com opções de lojas.
"""
header_text = "Escolha o Período que você quer visualizar o indicador"
body_text = "Veja as opções a baixo"
button_title = "Clique aqui" # Texto do botão que o usuário clica para ABRIR a lista
sections = [
{
"title": "Acumulados", # Título da primeira seção
"rows": [
{"id": 'OPTION_ANO', "title": 'Acumulado do Ano', "description": "Do do ano até ontem."},
{"id": 'OPTION_MES', "title": 'Acumulado do Mês', "description": "Do primeira do mês até ontem."},
]
},
{
"title": "Resultados", # Título da segunda seção
"rows": [
{"id": 'OPTION_ONTEM', "title": 'Resultado de Ontem', "description": "Ver o realizado de ontem."},
{"id": 'OPTION_HOJE', "title": 'Resultado de Hoje', "description": "Resultado Parcial de Hoje"}
]
},
{
"title": "Sair", # Título da segunda seção
"rows": [
{"id": 'OPTION_SAIR', "title": 'Sair', "description": "Encerre a conversa"},
]
}
]
# Relembrando: até 10 seções e 10 itens por seção (total 100 itens).
# CHAMA send_interactive_list DIRETAMENTE AQUI
await send_interactive_list(to, header_text, body_text, button_title, sections)
async def send_store_menu(to: str): # <-- FUNÇÃO QUE VOCÊ PEDIU
"""
Envia uma mensagem interativa de LISTA para o usuário com opções de lojas.
"""
header_text = "Escolha o Período que você quer visualizar o indicador"
body_text = "Veja as opções a baixo"
button_title = "Clique aqui" # Texto do botão que o usuário clica para ABRIR a lista
sections = [
{
"title": "Acumulados", # Título da primeira seção
"rows": [
{"id": 'OPTION_ANO', "title": 'Total do CP', "description": "Visualize o resultado Total do CP"},
{"id": 'OPTION_MES', "title": 'Total do Estado', "description": "Visualize o resultado Total das suas Lojas"},
]
},
{
"title": "Por Loja", # Título da segunda seção
"rows": [
{"id": 'OPTION_ONTEM', "title": 'Total das suas Lojas', "description": "Visualize o total das suas Lojas"},
{"id": 'OPTION_HOJE', "title": 'Total de uma Loja', "description": "Visualize o total de uma loja."}
]
},
{
"title": "Sair", # Título da segunda seção
"rows": [
{"id": 'OPTION_SAIR', "title": 'Sair', "description": "Encerre a conversa"},
]
}
]
# Relembrando: até 10 seções e 10 itens por seção (total 100 itens).
# CHAMA send_interactive_list DIRETAMENTE AQUI
await send_interactive_list(to, header_text, body_text, button_title, sections)
# Exemplo de como usar o MENU, só aceita 3 opções mas já aparece direto pro usuário as opções.
'''
async def send_stores_menu(to: str):
buttons = [
{"id": 'OPTION_AGENDAR', "title": 'Total do CP'},
{"id": 'OPTION_STATUS', "title": 'Total das suas Lojas'},
@ -458,7 +705,17 @@ async def send_interactive_menu(to: str):
]
await send_interactive_buttons(to, "Menu Principal", "Escolha a Dimensão das Lojas que você quer visulizar o indicador:", buttons)
async def send_time_menu(to: str):
buttons = [
{"id": 'OPTION_AGENDAR', "title": 'Acumulado do Ano'},
{"id": 'OPTION_STATUS', "title": 'Acumulado do Mês'},
{"id": 'OPTION_FALAR_ATENDENTE', "title": 'Resultado de Ontem'}
#{"id": 'OPTION_FALAR_ATENDENTE', "title": 'Resultado Parcial de Hoje'},
#{"id": 'OPTION_FALAR_ATENDENTE', "title": 'Sair'}
]
await send_interactive_buttons(to, "Escolha o Período que você quer visualizar o indicador", "Veja as opções abaixo:", buttons)
'''
async def send_whatsapp_flow(to: str, flow_id: str, flow_cta: str, screen: str = "welcome_screen") -> Dict[str, Any]:
url = f"https://graph.facebook.com/v18.0/{WHATSAPP_PHONE_NUMBER_ID}/messages"
headers = {