mirror of
https://github.com/marcogll/talia_bot.git
synced 2026-01-13 21:35:19 +00:00
Merge pull request #33 from marcogll/feature/flow-engine-implementation-15654864159042246464
Feature/flow engine implementation 15654864159042246464
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -158,7 +158,6 @@ cython_debug/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
# Google Service Account Credentials
|
# Google Service Account Credentials
|
||||||
*.json
|
|
||||||
!credentials.example.json
|
!credentials.example.json
|
||||||
google_key.json
|
google_key.json
|
||||||
|
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -32,7 +32,9 @@ El sistema sigue un flujo modular:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 Flujos de Trabajo (Features)
|
## 📋 Flujos de Trabajo Modulares (Features)
|
||||||
|
|
||||||
|
El comportamiento del bot se define a través de **flujos de conversación modulares** gestionados por un motor central (`flow_engine.py`). Cada flujo es un archivo `.json` independiente ubicado en `talia_bot/data/flows/`, lo que permite modificar o crear nuevas conversaciones sin alterar el código principal.
|
||||||
|
|
||||||
### 1. 👑 Gestión Admin (Proyectos & Identidad)
|
### 1. 👑 Gestión Admin (Proyectos & Identidad)
|
||||||
|
|
||||||
@@ -118,10 +120,11 @@ IMAP_SERVER=imap.hostinger.com
|
|||||||
|
|
||||||
### 3. Estructura de Datos
|
### 3. Estructura de Datos
|
||||||
|
|
||||||
Asegúrate de tener los archivos base en `talia_bot/data/`:
|
Asegúrate de tener los archivos y directorios base en `talia_bot/data/`:
|
||||||
* `servicios.json`: Catálogo de servicios para el RAG de ventas.
|
* `servicios.json`: Catálogo de servicios para el RAG de ventas.
|
||||||
* `credentials.json`: Credenciales de Google Cloud.
|
* `credentials.json`: Credenciales de Google Cloud.
|
||||||
* `users.db`: Base de datos SQLite.
|
* `users.db`: Base de datos SQLite que almacena los roles de los usuarios.
|
||||||
|
* `flows/`: Directorio que contiene las definiciones de los flujos de conversación en formato JSON. Cada archivo representa una conversación completa para un rol específico.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -130,10 +133,11 @@ Asegúrate de tener los archivos base en `talia_bot/data/`:
|
|||||||
```text
|
```text
|
||||||
talia_bot_mg/
|
talia_bot_mg/
|
||||||
├── talia_bot/
|
├── talia_bot/
|
||||||
│ ├── main.py # Entry Point y Router de Identidad
|
│ ├── main.py # Entry Point y dispatcher principal
|
||||||
│ ├── db.py # Gestión de la base de datos
|
│ ├── db.py # Gestión de la base de datos SQLite
|
||||||
│ ├── config.py # Carga de variables de entorno
|
│ ├── config.py # Carga de variables de entorno
|
||||||
│ ├── modules/
|
│ ├── modules/
|
||||||
|
│ │ ├── flow_engine.py # Motor de flujos de conversación (lee los JSON)
|
||||||
│ │ ├── identity.py # Lógica de Roles y Permisos
|
│ │ ├── identity.py # Lógica de Roles y Permisos
|
||||||
│ │ ├── llm_engine.py # Cliente OpenAI/Gemini
|
│ │ ├── llm_engine.py # Cliente OpenAI/Gemini
|
||||||
│ │ ├── vikunja.py # API Manager para Tareas
|
│ │ ├── vikunja.py # API Manager para Tareas
|
||||||
@@ -141,7 +145,8 @@ talia_bot_mg/
|
|||||||
│ │ ├── printer.py # SMTP/IMAP Loop
|
│ │ ├── printer.py # SMTP/IMAP Loop
|
||||||
│ │ └── sales_rag.py # Lógica de Ventas y Servicios
|
│ │ └── sales_rag.py # Lógica de Ventas y Servicios
|
||||||
│ └── data/
|
│ └── data/
|
||||||
│ ├── servicios.json # Base de conocimiento
|
│ ├── flows/ # Directorio con los flujos de conversación en JSON
|
||||||
|
│ ├── servicios.json # Base de conocimiento para ventas
|
||||||
│ ├── credentials.json # Credenciales de Google
|
│ ├── credentials.json # Credenciales de Google
|
||||||
│ └── users.db # Base de datos de usuarios
|
│ └── users.db # Base de datos de usuarios
|
||||||
├── .env.example # Plantilla de variables de entorno
|
├── .env.example # Plantilla de variables de entorno
|
||||||
|
|||||||
25
talia_bot/data/flows/admin_block_agenda.json
Normal file
25
talia_bot/data/flows/admin_block_agenda.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"id": "admin_block_agenda",
|
||||||
|
"role": "admin",
|
||||||
|
"trigger_button": "🛑 Bloquear Agenda",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": 0,
|
||||||
|
"variable": "BLOCK_DATE",
|
||||||
|
"question": "Necesito bloquear la agenda. ¿Para cuándo?",
|
||||||
|
"options": ["Hoy", "Mañana"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 1,
|
||||||
|
"variable": "BLOCK_TIME",
|
||||||
|
"question": "Dame el horario exacto que necesitas bloquear (ej. 'de 2pm a 4pm').",
|
||||||
|
"input_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 2,
|
||||||
|
"variable": "BLOCK_TITLE",
|
||||||
|
"question": "Finalmente, dame una breve descripción o motivo del bloqueo.",
|
||||||
|
"input_type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
19
talia_bot/data/flows/admin_check_agenda.json
Normal file
19
talia_bot/data/flows/admin_check_agenda.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"id": "admin_check_agenda",
|
||||||
|
"role": "admin",
|
||||||
|
"trigger_button": "📅 Revisar Agenda",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": 0,
|
||||||
|
"variable": "AGENDA_TIMEFRAME",
|
||||||
|
"question": "Consultando el oráculo del tiempo... ⏳",
|
||||||
|
"options": ["📅 Hoy", "🔮 Mañana"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 1,
|
||||||
|
"variable": "AGENDA_ACTION",
|
||||||
|
"question": "Aquí tienes tu realidad: {CALENDAR_DATA}",
|
||||||
|
"options": ["✅ Todo bien", "🛑 Bloquear Espacio"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
talia_bot/data/flows/admin_create_nfc_tag.json
Normal file
25
talia_bot/data/flows/admin_create_nfc_tag.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"id": "admin_create_nfc_tag",
|
||||||
|
"role": "admin",
|
||||||
|
"trigger_button": "➕ Crear Tag NFC",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": 0,
|
||||||
|
"variable": "NFC_ACTION_TYPE",
|
||||||
|
"question": "Creemos un nuevo tag NFC. ¿Qué acción quieres que dispare?",
|
||||||
|
"options": ["Iniciar Flujo", "URL Estática"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 1,
|
||||||
|
"variable": "NFC_FLOW_CHOICE",
|
||||||
|
"question": "Okay, ¿qué flujo debería iniciar este tag?",
|
||||||
|
"input_type": "dynamic_keyboard_flows"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 2,
|
||||||
|
"variable": "NFC_CONFIRM",
|
||||||
|
"question": "Perfecto. Cuando acerques tu teléfono a este tag, se iniciará el flujo '{flow_name}'. Aquí tienes los datos para escribir en el tag: {NFC_DATA}",
|
||||||
|
"options": ["✅ Hecho"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
talia_bot/data/flows/admin_idea_capture.json
Normal file
25
talia_bot/data/flows/admin_idea_capture.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"id": "admin_idea_capture",
|
||||||
|
"role": "admin",
|
||||||
|
"trigger_button": "💡 Capturar Idea",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": 0,
|
||||||
|
"variable": "IDEA_CONTENT",
|
||||||
|
"question": "Te escucho. 💡 Las ideas vuelan...",
|
||||||
|
"input_type": "text_or_audio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 1,
|
||||||
|
"variable": "IDEA_CATEGORY",
|
||||||
|
"question": "¿En qué cajón mental guardamos esto?",
|
||||||
|
"options": ["💰 Negocio", "📹 Contenido", "👤 Personal"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 2,
|
||||||
|
"variable": "IDEA_ACTION",
|
||||||
|
"question": "¿Cuál es el plan de ataque?",
|
||||||
|
"options": ["✅ Crear Tarea", "📓 Guardar Nota"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
talia_bot/data/flows/admin_print_file.json
Normal file
13
talia_bot/data/flows/admin_print_file.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"id": "admin_print_file",
|
||||||
|
"role": "admin",
|
||||||
|
"trigger_button": "🖨️ Imprimir",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": 0,
|
||||||
|
"variable": "UPLOAD_FILE",
|
||||||
|
"question": "Por favor, envíame el archivo que quieres imprimir.",
|
||||||
|
"input_type": "document"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
31
talia_bot/data/flows/admin_project_management.json
Normal file
31
talia_bot/data/flows/admin_project_management.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"id": "admin_project_management",
|
||||||
|
"role": "admin",
|
||||||
|
"trigger_button": "🏗️ Ver Proyectos",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": 0,
|
||||||
|
"variable": "PROJECT_SELECT",
|
||||||
|
"question": "Aquí está el tablero de ajedrez...",
|
||||||
|
"input_type": "dynamic_keyboard_vikunja_projects"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 1,
|
||||||
|
"variable": "TASK_SELECT",
|
||||||
|
"question": "Has seleccionado el proyecto {project_name}. ¿Qué quieres hacer?",
|
||||||
|
"input_type": "dynamic_keyboard_vikunja_tasks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 2,
|
||||||
|
"variable": "ACTION_TYPE",
|
||||||
|
"question": "¿Cuál es la jugada?",
|
||||||
|
"options": ["🔄 Actualizar Estatus", "💬 Agregar Comentario"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 3,
|
||||||
|
"variable": "UPDATE_CONTENT",
|
||||||
|
"question": "Adelante. Soy todo oídos.",
|
||||||
|
"input_type": "text_or_audio"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
talia_bot/data/flows/client_sales_funnel.json
Normal file
25
talia_bot/data/flows/client_sales_funnel.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"id": "client_sales_funnel",
|
||||||
|
"role": "client",
|
||||||
|
"trigger_automatic": true,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": 0,
|
||||||
|
"variable": "CLIENT_NAME",
|
||||||
|
"question": "Hola. Soy Talia, la mano derecha de Armando. ✨Él está ocupado creando, pero yo soy la puerta de entrada. ¿Con quién tengo el gusto?",
|
||||||
|
"input_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 1,
|
||||||
|
"variable": "CLIENT_INDUSTRY",
|
||||||
|
"question": "Mucho gusto, {user_name}. Para entender mejor tus necesidades, ¿cuál es el giro de tu negocio o tu industria?",
|
||||||
|
"options": ["🍽️ Restaurantes", "🩺 Salud", "🛍️ Retail", "อื่น ๆ"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 2,
|
||||||
|
"variable": "IDEA_PITCH",
|
||||||
|
"question": "Excelente. Ahora, el escenario es tuyo. 🎤 Cuéntame sobre tu proyecto o la idea que tienes en mente. No te guardes nada. Puedes escribirlo o, si prefieres, enviarme una nota de voz.",
|
||||||
|
"input_type": "text_or_audio"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
talia_bot/data/flows/crew_print_file.json
Normal file
13
talia_bot/data/flows/crew_print_file.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"id": "crew_print_file",
|
||||||
|
"role": "crew",
|
||||||
|
"trigger_button": "🖨️ Imprimir",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": 0,
|
||||||
|
"variable": "UPLOAD_FILE",
|
||||||
|
"question": "Claro, envíame el archivo que necesitas imprimir y yo me encargo.",
|
||||||
|
"input_type": "document"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
31
talia_bot/data/flows/crew_request_time.json
Normal file
31
talia_bot/data/flows/crew_request_time.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"id": "crew_request_time",
|
||||||
|
"role": "crew",
|
||||||
|
"trigger_button": "📅 Solicitar Agenda",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": 0,
|
||||||
|
"variable": "REQUEST_TYPE",
|
||||||
|
"question": "Para usar la agenda del estudio, necesito que seas preciso.",
|
||||||
|
"options": ["🎥 Grabación", "🎙️ Locución", "🎬 Edición", "🛠️ Mantenimiento"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 1,
|
||||||
|
"variable": "REQUEST_DATE",
|
||||||
|
"question": "¿Para cuándo necesitas el espacio?",
|
||||||
|
"options": ["Hoy", "Mañana", "Esta Semana"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 2,
|
||||||
|
"variable": "REQUEST_TIME",
|
||||||
|
"question": "Dame el horario exacto que necesitas (ej. 'de 10am a 2pm').",
|
||||||
|
"input_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 3,
|
||||||
|
"variable": "REQUEST_JUSTIFICATION",
|
||||||
|
"question": "Entendido. Antes de confirmar, necesito que me expliques brevemente el plan o el motivo para justificar el bloqueo del espacio. Puedes escribirlo o enviarme un audio.",
|
||||||
|
"input_type": "text_or_audio"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
37
talia_bot/data/flows/crew_secret_onboarding.json
Normal file
37
talia_bot/data/flows/crew_secret_onboarding.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"id": "crew_secret_onboarding",
|
||||||
|
"role": "crew",
|
||||||
|
"trigger_command": "/abracadabra",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": 0,
|
||||||
|
"variable": "ONBOARD_START",
|
||||||
|
"question": "Vaya, vaya... Parece que conoces el comando secreto. 🎩. Antes de continuar, necesito saber tu nombre completo.",
|
||||||
|
"input_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 1,
|
||||||
|
"variable": "ONBOARD_ORIGIN",
|
||||||
|
"question": "Un placer, {user_name}. ¿Cuál es tu base de operaciones principal?",
|
||||||
|
"options": ["🏢 Office", "✨ Aura"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 2,
|
||||||
|
"variable": "ONBOARD_EMAIL",
|
||||||
|
"question": "Perfecto. Ahora necesito tu correo electrónico de la empresa.",
|
||||||
|
"input_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 3,
|
||||||
|
"variable": "ONBOARD_PHONE",
|
||||||
|
"question": "Y por último, tu número de teléfono.",
|
||||||
|
"input_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": 4,
|
||||||
|
"variable": "ONBOARD_CONFIRM",
|
||||||
|
"question": "Gracias. He enviado una notificación al Administrador para que apruebe tu acceso. En cuanto lo haga, tendrás acceso completo. ¡Bienvenido a bordo!",
|
||||||
|
"options": ["✅ Entendido"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
22
talia_bot/data/services.json
Normal file
22
talia_bot/data/services.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"service_name": "Web Development for Restaurants",
|
||||||
|
"description": "Custom websites and online ordering systems for restaurants, helping you reach more customers and streamline your operations.",
|
||||||
|
"keywords": ["restaurant", "food", "online ordering", "website", "restaurantes", "comida"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service_name": "Patient Management Systems for Healthcare",
|
||||||
|
"description": "A secure and efficient software solution for managing patient records, appointments, and billing in medical clinics.",
|
||||||
|
"keywords": ["healthcare", "medical", "patient", "clinic", "salud", "médico", "pacientes"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service_name": "Content Creation & Social Media Strategy",
|
||||||
|
"description": "Engaging content packages and social media management to build your brand's online presence and connect with your audience.",
|
||||||
|
"keywords": ["content creation", "social media", "marketing", "branding", "contenido", "redes sociales"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service_name": "General Business Consulting",
|
||||||
|
"description": "Strategic consulting to help you optimize business processes, identify growth opportunities, and improve overall performance.",
|
||||||
|
"keywords": ["business", "consulting", "strategy", "growth", "negocio", "consultoría"]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -32,18 +32,8 @@ def setup_database():
|
|||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Create the conversations table for the flow engine
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS conversations (
|
|
||||||
user_id INTEGER PRIMARY KEY,
|
|
||||||
flow_id TEXT NOT NULL,
|
|
||||||
current_step_id INTEGER NOT NULL,
|
|
||||||
collected_data TEXT
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
logger.info("Database setup complete. 'users' and 'conversations' tables are ready.")
|
logger.info("Database setup complete. 'users' table is ready.")
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
logger.error(f"Database error during setup: {e}")
|
logger.error(f"Database error during setup: {e}")
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from telegram.ext import (
|
|||||||
# Importamos las configuraciones y herramientas que creamos en otros archivos
|
# Importamos las configuraciones y herramientas que creamos en otros archivos
|
||||||
from talia_bot.config import TELEGRAM_BOT_TOKEN
|
from talia_bot.config import TELEGRAM_BOT_TOKEN
|
||||||
from talia_bot.modules.identity import get_user_role
|
from talia_bot.modules.identity import get_user_role
|
||||||
from talia_bot.modules.flow_engine import FlowEngine
|
|
||||||
from talia_bot.modules.onboarding import handle_start as onboarding_handle_start
|
from talia_bot.modules.onboarding import handle_start as onboarding_handle_start
|
||||||
from talia_bot.modules.onboarding import get_admin_secondary_menu
|
from talia_bot.modules.onboarding import get_admin_secondary_menu
|
||||||
from talia_bot.modules.agenda import get_agenda
|
from talia_bot.modules.agenda import get_agenda
|
||||||
@@ -63,56 +62,14 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
# Respondemos al usuario
|
# Respondemos al usuario
|
||||||
await update.message.reply_text(response_text, reply_markup=reply_markup)
|
await update.message.reply_text(response_text, reply_markup=reply_markup)
|
||||||
|
|
||||||
flow_engine = FlowEngine()
|
|
||||||
|
|
||||||
async def universal_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
"""
|
|
||||||
Handles all user interactions (text, callbacks, voice, documents).
|
|
||||||
Routes them to the flow engine or legacy handlers.
|
|
||||||
"""
|
|
||||||
user_id = update.effective_user.id
|
|
||||||
conversation_state = flow_engine.get_conversation_state(user_id)
|
|
||||||
|
|
||||||
if conversation_state:
|
|
||||||
# User is in an active flow, so we process the response.
|
|
||||||
response_text = update.message.text if update.message else None
|
|
||||||
result = flow_engine.handle_response(user_id, response_text)
|
|
||||||
|
|
||||||
if result['status'] == 'in_progress':
|
|
||||||
await update.message.reply_text(result['step']['message'])
|
|
||||||
elif result['status'] == 'complete':
|
|
||||||
summary = "\n".join([f"- {key}: {value}" for key, value in result['data'].items()])
|
|
||||||
await update.message.reply_text(f"Flow '{result['flow_id']}' completado.\n\nResumen:\n{summary}")
|
|
||||||
else:
|
|
||||||
await update.message.reply_text(result.get('message', 'Ocurrió un error.'))
|
|
||||||
else:
|
|
||||||
# No active flow, check for a callback query to start a new flow or use legacy dispatcher.
|
|
||||||
if update.callback_query:
|
|
||||||
query = update.callback_query
|
|
||||||
await query.answer()
|
|
||||||
flow_to_start = query.data
|
|
||||||
|
|
||||||
# Check if the callback is intended to start a known flow.
|
|
||||||
if flow_engine.get_flow(flow_to_start):
|
|
||||||
initial_step = flow_engine.start_flow(user_id, flow_to_start)
|
|
||||||
if initial_step:
|
|
||||||
await query.message.reply_text(initial_step['message'])
|
|
||||||
else:
|
|
||||||
# Fallback to the old button dispatcher for legacy actions.
|
|
||||||
await button_dispatcher(update, context)
|
|
||||||
elif update.message and update.message.text:
|
|
||||||
# Handle regular text messages that are not part of a flow (e.g., commands).
|
|
||||||
# For now, we just ignore them if they are not commands.
|
|
||||||
logger.info(f"Received non-flow text message from {user_id}: {update.message.text}")
|
|
||||||
|
|
||||||
|
|
||||||
async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
"""
|
"""
|
||||||
Legacy handler for menu button clicks that are not part of a flow.
|
Esta función maneja los clics en los botones del menú.
|
||||||
|
Dependiendo de qué botón se presione, ejecuta una acción diferente.
|
||||||
"""
|
"""
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
# No need to answer here as it's answered in the universal_handler
|
await query.answer()
|
||||||
logger.info(f"El despachador legacy recibió una consulta: {query.data}")
|
logger.info(f"El despachador recibió una consulta: {query.data}")
|
||||||
|
|
||||||
response_text = "Acción no reconocida."
|
response_text = "Acción no reconocida."
|
||||||
reply_markup = None
|
reply_markup = None
|
||||||
@@ -134,23 +91,27 @@ async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
|||||||
try:
|
try:
|
||||||
if query.data in simple_handlers:
|
if query.data in simple_handlers:
|
||||||
handler = simple_handlers[query.data]
|
handler = simple_handlers[query.data]
|
||||||
|
logger.info(f"Ejecutando simple_handler para: {query.data}")
|
||||||
if asyncio.iscoroutinefunction(handler):
|
if asyncio.iscoroutinefunction(handler):
|
||||||
response_text = await handler()
|
response_text = await handler()
|
||||||
else:
|
else:
|
||||||
response_text = handler()
|
response_text = handler()
|
||||||
elif query.data in complex_handlers:
|
elif query.data in complex_handlers:
|
||||||
handler = complex_handlers[query.data]
|
handler = complex_handlers[query.data]
|
||||||
|
logger.info(f"Ejecutando complex_handler para: {query.data}")
|
||||||
if asyncio.iscoroutinefunction(handler):
|
if asyncio.iscoroutinefunction(handler):
|
||||||
response_text, reply_markup = await handler()
|
response_text, reply_markup = await handler()
|
||||||
else:
|
else:
|
||||||
response_text, reply_markup = handler()
|
response_text, reply_markup = handler()
|
||||||
elif query.data.startswith(('approve:', 'reject:')):
|
elif query.data.startswith(('approve:', 'reject:')):
|
||||||
|
logger.info(f"Ejecutando acción de aprobación: {query.data}")
|
||||||
response_text = handle_approval_action(query.data)
|
response_text = handle_approval_action(query.data)
|
||||||
elif query.data == 'start_create_tag':
|
elif query.data == 'start_create_tag':
|
||||||
response_text = "Para crear un tag, por favor usa el comando /create_tag."
|
response_text = "Para crear un tag, por favor usa el comando /create_tag."
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Consulta no manejada por el despachador: {query.data}")
|
logger.warning(f"Consulta no manejada por el despachador: {query.data}")
|
||||||
|
await query.edit_message_text(text=response_text)
|
||||||
|
return
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(f"Error al procesar la acción {query.data}: {exc}")
|
logger.exception(f"Error al procesar la acción {query.data}: {exc}")
|
||||||
response_text = "❌ Ocurrió un error al procesar tu solicitud. Intenta de nuevo."
|
response_text = "❌ Ocurrió un error al procesar tu solicitud. Intenta de nuevo."
|
||||||
@@ -158,7 +119,6 @@ async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
|||||||
|
|
||||||
await query.edit_message_text(text=response_text, reply_markup=reply_markup, parse_mode='Markdown')
|
await query.edit_message_text(text=response_text, reply_markup=reply_markup, parse_mode='Markdown')
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Función principal que arranca el bot."""
|
"""Función principal que arranca el bot."""
|
||||||
if not TELEGRAM_BOT_TOKEN:
|
if not TELEGRAM_BOT_TOKEN:
|
||||||
@@ -170,9 +130,10 @@ def main() -> None:
|
|||||||
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
||||||
schedule_daily_summary(application)
|
schedule_daily_summary(application)
|
||||||
|
|
||||||
# Legacy ConversationHandlers
|
# El orden de los handlers es crucial para que las conversaciones funcionen.
|
||||||
application.add_handler(create_tag_conv_handler())
|
application.add_handler(create_tag_conv_handler())
|
||||||
application.add_handler(vikunja_conv_handler())
|
application.add_handler(vikunja_conv_handler())
|
||||||
|
|
||||||
conv_handler = ConversationHandler(
|
conv_handler = ConversationHandler(
|
||||||
entry_points=[CallbackQueryHandler(propose_activity_start, pattern='^propose_activity$')],
|
entry_points=[CallbackQueryHandler(propose_activity_start, pattern='^propose_activity$')],
|
||||||
states={
|
states={
|
||||||
@@ -184,13 +145,10 @@ def main() -> None:
|
|||||||
)
|
)
|
||||||
application.add_handler(conv_handler)
|
application.add_handler(conv_handler)
|
||||||
|
|
||||||
# Command Handlers
|
|
||||||
application.add_handler(CommandHandler("start", start))
|
application.add_handler(CommandHandler("start", start))
|
||||||
application.add_handler(CommandHandler("print", print_handler))
|
application.add_handler(CommandHandler("print", print_handler))
|
||||||
|
|
||||||
# Universal Handler for flows and callbacks
|
application.add_handler(CallbackQueryHandler(button_dispatcher))
|
||||||
application.add_handler(CallbackQueryHandler(universal_handler))
|
|
||||||
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, universal_handler))
|
|
||||||
|
|
||||||
logger.info("Iniciando Talía Bot...")
|
logger.info("Iniciando Talía Bot...")
|
||||||
application.run_polling()
|
application.run_polling()
|
||||||
|
|||||||
Reference in New Issue
Block a user