Files
talia_bot/app/main.py
google-labs-jules[bot] c9ef9ab5b5 feat: Restructure admin menu and enhance Vikunja integration
Restructures the admin menu into a primary and secondary menu for better user experience.
The primary menu now shows the most common actions.
The secondary menu contains less frequent admin commands.

Refactors the Vikunja module to be triggered by a menu button instead of a command.
Adds "edit task" functionality to the Vikunja module.
Fixes a bug where the button dispatcher was calling a non-existent function.
2025-12-18 15:05:54 +00:00

140 lines
5.3 KiB
Python

# app/main.py
# Este es el archivo principal del bot. Aquí se inicia todo y se configuran los comandos.
import logging
from telegram import Update
from telegram.ext import (
Application,
CommandHandler,
CallbackQueryHandler,
ConversationHandler,
MessageHandler,
ContextTypes,
filters,
)
# Importamos las configuraciones y herramientas que creamos en otros archivos
from config import TELEGRAM_BOT_TOKEN
from permissions import get_user_role
from modules.onboarding import handle_start as onboarding_handle_start
from modules.onboarding import get_admin_secondary_menu
from modules.agenda import get_agenda
from modules.citas import request_appointment
from modules.equipo import (
propose_activity_start,
get_description,
get_duration,
cancel_proposal,
view_requests_status,
DESCRIPTION,
DURATION,
)
from modules.aprobaciones import view_pending, handle_approval_action
from modules.servicios import get_service_info
from modules.admin import get_system_status
from modules.print import print_handler
from modules.create_tag import create_tag_conv_handler, create_tag_start
from modules.vikunja import vikunja_conv_handler, get_tasks as get_vikunja_tasks
from scheduler import schedule_daily_summary
# Configuramos el sistema de logs para ver mensajes de estado en la consola
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
logger = logging.getLogger(__name__)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
Se ejecuta cuando el usuario escribe /start.
Muestra un mensaje de bienvenida y un menú según el rol del usuario.
"""
chat_id = update.effective_chat.id
user_role = get_user_role(chat_id)
logger.info(f"Usuario {chat_id} inició conversación con el rol: {user_role}")
# Obtenemos el texto y los botones de bienvenida desde el módulo de onboarding
response_text, reply_markup = onboarding_handle_start(user_role)
# Respondemos al usuario
await update.message.reply_text(response_text, reply_markup=reply_markup)
async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
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
await query.answer()
logger.info(f"El despachador recibió una consulta: {query.data}")
response_text = "Acción no reconocida."
reply_markup = None
simple_handlers = {
'view_agenda': get_agenda,
'view_requests_status': view_requests_status,
'schedule_appointment': request_appointment,
'get_service_info': get_service_info,
'view_system_status': get_system_status,
'manage_users': lambda: "Función de gestión de usuarios no implementada.",
}
complex_handlers = {
'view_pending': view_pending,
'admin_menu': get_admin_secondary_menu,
}
if query.data in simple_handlers:
response_text = simple_handlers[query.data]()
await query.edit_message_text(text=response_text, parse_mode='Markdown')
elif query.data in complex_handlers:
response_text, reply_markup = complex_handlers[query.data]()
await query.edit_message_text(text=response_text, reply_markup=reply_markup, parse_mode='Markdown')
elif query.data.startswith(('approve:', 'reject:')):
response_text = handle_approval_action(query.data)
await query.edit_message_text(text=response_text, parse_mode='Markdown')
elif query.data == 'start_create_tag':
await query.message.reply_text("Iniciando creación de tag...")
return await create_tag_start(update, context)
else:
# Si no es ninguna de las acciones conocidas, asumimos que es para un ConversationHandler
# y no hacemos nada aquí para no interferir.
logger.warning(f"Consulta no manejada por el despachador principal: {query.data}")
def main() -> None:
"""Función principal que arranca el bot."""
if not TELEGRAM_BOT_TOKEN:
logger.error("TELEGRAM_BOT_TOKEN no está configurado en las variables de entorno.")
return
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
schedule_daily_summary(application)
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
)
# El order de los handlers importa. El dispatcher debe ir después de los ConversationHandlers
# para no interceptar sus callbacks.
application.add_handler(conv_handler)
application.add_handler(create_tag_conv_handler())
application.add_handler(vikunja_conv_handler())
application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("print", print_handler))
application.add_handler(CallbackQueryHandler(button_dispatcher))
logger.info("Iniciando Talía Bot...")
application.run_polling()
if __name__ == "__main__":
main()