fix: resolve unresponsive bot by migrating to FlowEngine

The Telegram bot was becoming unresponsive due to a conflict between a legacy `ConversationHandler` for the `propose_activity` flow and the main `FlowEngine`. This was likely caused by breaking changes in the `python-telegram-bot` library.

This commit resolves the issue by:
1.  Removing the problematic `ConversationHandler` from `main.py` and its related functions from `modules/equipo.py`.
2.  Migrating the "propose activity" feature to a data-driven JSON flow (`crew_propose_activity.json`), making it compatible with the existing `FlowEngine`.
3.  Pinning the `python-telegram-bot` dependency to `<22` in `requirements.txt` to prevent future breakage from upstream updates.

This change ensures all conversational flows are handled consistently by the `FlowEngine`, restoring bot responsiveness and improving maintainability.
This commit is contained in:
google-labs-jules[bot]
2025-12-21 18:07:45 +00:00
parent 0c2c9fc524
commit 384e23cf58
4 changed files with 30 additions and 98 deletions

View File

@@ -1,4 +1,4 @@
python-telegram-bot[job-queue] python-telegram-bot[job-queue]<22
requests requests
schedule schedule
google-api-python-client google-api-python-client

View File

@@ -0,0 +1,29 @@
{
"id": "crew_propose_activity",
"description": "Permite a un miembro del equipo proponer una actividad para aprobación.",
"trigger_button": "propose_activity",
"user_roles": ["crew"],
"steps": [
{
"step_id": "get_description",
"question": "Por favor, describe la actividad que quieres proponer.",
"input_type": "text",
"next_step": "get_duration"
},
{
"step_id": "get_duration",
"question": "Entendido. Ahora, por favor, indica la duración estimada en horas (ej. 2, 4.5).",
"input_type": "text",
"next_step": "confirm_proposal"
},
{
"step_id": "confirm_proposal",
"question": "Gracias. Se ha enviado la siguiente propuesta para aprobación:\n\n📝 *Actividad:* {get_description}\n⏳ *Duración:* {get_duration} horas\n\nRecibirás una notificación cuando sea revisada.",
"input_type": "static"
}
],
"completion_action": {
"action_type": "notify_admin",
"message_template": "Nueva propuesta de actividad:\n\nDescripción: {get_description}\nDuración: {get_duration} horas"
}
}

View File

@@ -23,13 +23,7 @@ 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
from talia_bot.modules.citas import request_appointment from talia_bot.modules.citas import request_appointment
from talia_bot.modules.equipo import ( from talia_bot.modules.equipo import (
propose_activity_start,
get_description,
get_duration,
cancel_proposal,
view_requests_status, view_requests_status,
DESCRIPTION,
DURATION,
) )
from talia_bot.modules.aprobaciones import view_pending, handle_approval_action from talia_bot.modules.aprobaciones import view_pending, handle_approval_action
from talia_bot.modules.servicios import get_service_info from talia_bot.modules.servicios import get_service_info
@@ -50,11 +44,6 @@ logging.basicConfig(
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def catch_all_handler(update: object, context: ContextTypes.DEFAULT_TYPE):
print("--- CATCH ALL HANDLER ---")
print(update)
async def send_step_message(update: Update, step: dict): async def send_step_message(update: Update, step: dict):
"""Helper to send a message for a flow step, including options if available.""" """Helper to send a message for a flow step, including options if available."""
text = step["question"] text = step["question"]
@@ -181,7 +170,6 @@ async def reset_conversation(update: Update, context: ContextTypes.DEFAULT_TYPE)
async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
print("--- BUTTON DISPATCHER CALLED ---")
""" """
Esta función maneja los clics en los botones del menú. Esta función maneja los clics en los botones del menú.
Dependiendo de qué botón se presione, ejecuta una acción diferente. Dependiendo de qué botón se presione, ejecuta una acción diferente.
@@ -289,21 +277,6 @@ def main() -> None:
schedule_daily_summary(application) schedule_daily_summary(application)
# Conversation handler for proposing activities
conv_handler = ConversationHandler(
entry_points=[CallbackQueryHandler(propose_activity_start, pattern='^propose_activity$')],
states={
DESCRIPTION: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_description)],
DURATION: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_duration)],
},
fallbacks=[CommandHandler('cancel', cancel_proposal)],
per_message=False
)
application.add_handler(conv_handler)
# El orden de los handlers es crucial para que las conversaciones funcionen.
# application.add_handler(vikunja_conv_handler())
application.add_handler(CommandHandler("start", start)) application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("reset", reset_conversation)) # Added reset command application.add_handler(CommandHandler("reset", reset_conversation)) # Added reset command
application.add_handler(CommandHandler("print", print_handler)) application.add_handler(CommandHandler("print", print_handler))
@@ -315,8 +288,6 @@ def main() -> None:
application.add_handler(CallbackQueryHandler(button_dispatcher)) application.add_handler(CallbackQueryHandler(button_dispatcher))
application.add_handler(TypeHandler(object, catch_all_handler))
logger.info("Iniciando Talía Bot...") logger.info("Iniciando Talía Bot...")
application.run_polling() application.run_polling()

View File

@@ -2,74 +2,6 @@
# Este módulo contiene funciones para los miembros autorizados del equipo. # Este módulo contiene funciones para los miembros autorizados del equipo.
# Incluye un flujo para proponer actividades que el dueño debe aprobar. # Incluye un flujo para proponer actividades que el dueño debe aprobar.
from telegram import Update
from telegram.ext import ContextTypes, ConversationHandler
# Definimos los estados para la conversación de propuesta de actividad.
DESCRIPTION, DURATION = range(2)
async def propose_activity_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
print("--- PROPOSE ACTIVITY START CALLED ---")
"""
Inicia el proceso para que un miembro del equipo proponga una actividad.
Se activa cuando se pulsa el botón correspondiente.
"""
await update.callback_query.answer()
await update.callback_query.edit_message_text(
"Por favor, describe la actividad que quieres proponer."
)
# Siguiente paso: DESCRIPTION
return DESCRIPTION
async def get_description(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""
Guarda la descripción de la actividad y pide la duración.
"""
context.user_data['activity_description'] = update.message.text
await update.message.reply_text(
"Entendido. Ahora, por favor, indica la duración estimada en horas (ej. 2, 4.5)."
)
# Siguiente paso: DURATION
return DURATION
async def get_duration(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""
Guarda la duración, confirma la propuesta y termina la conversación.
"""
try:
# Intentamos convertir el texto a un número decimal (float)
duration = float(update.message.text)
context.user_data['activity_duration'] = duration
description = context.user_data.get('activity_description', 'N/A')
confirmation_text = (
f"Gracias. Se ha enviado la siguiente propuesta para aprobación:\n\n"
f"📝 *Actividad:* {description}\n"
f"⏳ *Duración:* {duration} horas\n\n"
"Recibirás una notificación cuando sea revisada."
)
# TODO: Enviar esta propuesta al dueño (por webhook o base de datos).
await update.message.reply_text(confirmation_text, parse_mode='Markdown')
# Limpiamos los datos temporales
context.user_data.clear()
# Terminamos la conversación
return ConversationHandler.END
except ValueError:
# Si el usuario no escribe un número válido, se lo pedimos de nuevo
await update.message.reply_text("Por favor, introduce un número válido para la duración en horas.")
return DURATION
async def cancel_proposal(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""
Cancela el proceso de propuesta si el usuario escribe /cancel.
"""
await update.message.reply_text("La propuesta de actividad ha sido cancelada.")
context.user_data.clear()
return ConversationHandler.END
def view_requests_status(): def view_requests_status():
""" """
Permite a un miembro del equipo ver el estado de sus solicitudes recientes. Permite a un miembro del equipo ver el estado de sus solicitudes recientes.