diff --git a/app/__pycache__/main.cpython-312.pyc b/app/__pycache__/main.cpython-312.pyc index 7e36a39..ccd07a1 100644 Binary files a/app/__pycache__/main.cpython-312.pyc and b/app/__pycache__/main.cpython-312.pyc differ diff --git a/app/dialog_flow/graph_definition.py b/app/dialog_flow/graph_definition.py new file mode 100644 index 0000000..d1ba053 --- /dev/null +++ b/app/dialog_flow/graph_definition.py @@ -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" \ No newline at end of file diff --git a/app/main.py b/app/main.py index 57923c8..2089847 100644 --- a/app/main.py +++ b/app/main.py @@ -35,4 +35,4 @@ async def shutdown_event(): # Exemplo de uso de Uvicorn para rodar o FastAPI if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=5000) \ No newline at end of file + uvicorn.run(app, host="0.0.0.0", port=5000) diff --git a/app/services/__pycache__/webhook_service.cpython-312.pyc b/app/services/__pycache__/webhook_service.cpython-312.pyc index 5364451..19bc549 100644 Binary files a/app/services/__pycache__/webhook_service.cpython-312.pyc and b/app/services/__pycache__/webhook_service.cpython-312.pyc differ diff --git a/app/services/webhook_service.py b/app/services/webhook_service.py index 0c5ad2c..3de0858 100644 --- a/app/services/webhook_service.py +++ b/app/services/webhook_service.py @@ -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 = {