diff --git a/README.md b/README.md index 61b59e7..f3c5e9c 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,8 @@ Cada módulo cumple una responsabilidad única: * **aprobaciones.py**: aceptar o rechazar solicitudes * **servicios.py**: información y cotización de proyectos * **admin.py**: acciones administrativas +* **create_tag.py**: genera un tag de identificación en Base64 para NFC +* **print.py**: (admin) imprime la configuración actual del bot --- diff --git a/app/modules/admin.py b/app/modules/admin.py index bd1c86d..14ef553 100644 --- a/app/modules/admin.py +++ b/app/modules/admin.py @@ -1,10 +1,18 @@ # app/modules/admin.py +""" +This module contains administrative functions for the bot. + +Currently, it provides a simple way to check the system's status. +""" def get_system_status(): """ - Returns the current status of the bot and its integrations. + Returns a formatted string with the current status of the bot and its integrations. + + This function currently returns a hardcoded status message. In the future, + it could be expanded to perform real-time checks on the different services. """ - # TODO: Implement real-time status checks + # TODO: Implement real-time status checks for more accurate monitoring. status_text = ( "📊 *Estado del Sistema*\n\n" "- *Bot Principal:* Activo ✅\n" diff --git a/app/modules/agenda.py b/app/modules/agenda.py index 7908827..4b78bc7 100644 --- a/app/modules/agenda.py +++ b/app/modules/agenda.py @@ -1,11 +1,19 @@ # app/modules/agenda.py +""" +This module is responsible for handling agenda-related requests. + +It provides functionality to fetch and display the user's schedule for the day. +""" def get_agenda(): """ - Fetches and displays the user's agenda for today. - For now, it returns a hardcoded sample agenda. + Fetches and displays the user's agenda for the current day. + + Currently, this function returns a hardcoded sample agenda for demonstration + purposes. The plan is to replace this with a real integration that fetches + events from a service like Google Calendar. """ - # TODO: Fetch agenda from Google Calendar + # TODO: Fetch the agenda dynamically from Google Calendar. agenda_text = ( "📅 *Agenda para Hoy*\n\n" "• *10:00 AM - 11:00 AM*\n" diff --git a/app/modules/aprobaciones.py b/app/modules/aprobaciones.py index 44a1641..1060511 100644 --- a/app/modules/aprobaciones.py +++ b/app/modules/aprobaciones.py @@ -1,8 +1,20 @@ # app/modules/aprobaciones.py +""" +This module manages the approval workflow for requests made by the team. + +It provides functions to view pending requests and to handle the approval or +rejection of those requests. The primary user for this module is the "owner" +role, who has the authority to approve or deny requests. +""" from telegram import InlineKeyboardButton, InlineKeyboardMarkup def get_approval_menu(request_id): - """Returns an inline keyboard for approving or rejecting a request.""" + """ + Creates and returns an inline keyboard with "Approve" and "Reject" buttons. + + Each button is associated with a specific request_id through the + callback_data, allowing the bot to identify which request is being acted upon. + """ keyboard = [ [ InlineKeyboardButton("✅ Aprobar", callback_data=f'approve:{request_id}'), @@ -13,10 +25,13 @@ def get_approval_menu(request_id): def view_pending(): """ - Shows the owner a list of pending requests with approval buttons. - For now, it returns a hardcoded list of proposals. + Shows the owner a list of pending requests that require their attention. + + Currently, this function uses a hardcoded list of proposals for demonstration. + In a production environment, this would fetch data from a database or another + persistent storage mechanism where pending requests are tracked. """ - # TODO: Fetch pending requests from a database or webhook events + # TODO: Fetch pending requests dynamically from a database or webhook events. proposals = [ {"id": "prop_001", "desc": "Grabación de proyecto", "duration": 4, "user": "Equipo A"}, {"id": "prop_002", "desc": "Taller de guion", "duration": 2, "user": "Equipo B"}, @@ -25,7 +40,7 @@ def view_pending(): if not proposals: return "No hay solicitudes pendientes.", None - # For simplicity, we'll just show the first pending proposal + # For demonstration purposes, we'll just show the first pending proposal. proposal = proposals[0] text = ( @@ -35,21 +50,28 @@ def view_pending(): f"⏳ *Duración:* {proposal['duration']} horas" ) + # Attach the approval menu to the message. reply_markup = get_approval_menu(proposal['id']) return text, reply_markup def handle_approval_action(callback_data): """ - Handles the owner's approval or rejection of a request. + Handles the owner's response (approve or reject) to a request. + + This function is triggered when the owner clicks one of the buttons created + by get_approval_menu. It parses the callback_data to determine the action + and the request ID. """ action, request_id = callback_data.split(':') if action == 'approve': - # TODO: Update the status of the request to 'approved' + # TODO: Implement logic to update the request's status to 'approved'. + # This could involve updating a database and notifying the requester. return f"✅ La solicitud *{request_id}* ha sido aprobada." elif action == 'reject': - # TODO: Update the status of the request to 'rejected' + # TODO: Implement logic to update the request's status to 'rejected'. + # This could involve updating a database and notifying the requester. return f"❌ La solicitud *{request_id}* ha sido rechazada." return "Acción desconocida.", None diff --git a/app/modules/citas.py b/app/modules/citas.py index 3215c8b..a0e40d7 100644 --- a/app/modules/citas.py +++ b/app/modules/citas.py @@ -1,10 +1,21 @@ # app/modules/citas.py +""" +This module handles appointment scheduling for clients. + +It provides a simple way for users to get a link to an external scheduling +service, such as Calendly or an n8n workflow. +""" def request_appointment(): """ - Provides a link for scheduling an appointment. + Provides the user with a link to schedule an appointment. + + Currently, this function returns a hardcoded placeholder link to Calendly. + The intention is to replace this with a dynamic link generated by an n8n + workflow or another scheduling service. """ - # TODO: Integrate with a real scheduling service or n8n workflow + # TODO: Integrate with a real scheduling service or an n8n workflow to + # provide a dynamic and personalized scheduling link. response_text = ( "Para agendar una cita, por favor utiliza el siguiente enlace: \n\n" "[Enlace de Calendly](https://calendly.com/user/appointment-link)" diff --git a/app/modules/equipo.py b/app/modules/equipo.py index b82c84a..ff020c2 100644 --- a/app/modules/equipo.py +++ b/app/modules/equipo.py @@ -1,28 +1,44 @@ # app/modules/equipo.py +""" +This module contains functionality for authorized team members. + +It includes a conversational flow for proposing new activities that require +approval from the owner, as well as a function to check the status of +previously submitted requests. +""" from telegram import Update from telegram.ext import ContextTypes, ConversationHandler -# Conversation states +# Define the states for the activity proposal conversation. DESCRIPTION, DURATION = range(2) async def propose_activity_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """Starts the conversation to propose an activity after a button press.""" + """ + Starts the conversation for a team member to propose a new activity. + This is typically triggered by an inline button press. + """ await update.callback_query.answer() await update.callback_query.edit_message_text( "Por favor, describe la actividad que quieres proponer." ) + # The function returns the next state, which is DESCRIPTION. return DESCRIPTION async def get_description(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """Stores the description and asks for the duration.""" + """ + Stores the activity description provided by the user and asks for the duration. + """ 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)." ) + # The function returns the next state, DURATION. return DURATION async def get_duration(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """Stores the duration, confirms the proposal, and ends the conversation.""" + """ + Stores the activity duration, confirms the proposal to the user, and ends the conversation. + """ try: duration = float(update.message.text) context.user_data['activity_duration'] = duration @@ -34,24 +50,36 @@ async def get_duration(update: Update, context: ContextTypes.DEFAULT_TYPE) -> in f"⏳ *Duración:* {duration} horas\n\n" "Recibirás una notificación cuando sea revisada." ) - # TODO: Send this proposal to the owner via webhook/db + + # TODO: Send this proposal to the owner for approval, for example, + # by sending a webhook or saving it to a database. await update.message.reply_text(confirmation_text, parse_mode='Markdown') + # Clean up user_data to prevent data leakage into other conversations. context.user_data.clear() + + # End the conversation. return ConversationHandler.END except ValueError: + # If the user provides an invalid number for the duration, ask again. 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: - """Cancels and ends the conversation.""" + """ + Cancels and ends the activity proposal conversation. + This is triggered by the /cancel command. + """ await update.message.reply_text("La propuesta de actividad ha sido cancelada.") context.user_data.clear() return ConversationHandler.END def view_requests_status(): """ - Allows a team member to see the status of their requests. + Allows a team member to see the status of their recent requests. + + Currently, this returns a hardcoded sample status. In a real-world + application, this would fetch the user's requests from a database. """ - # TODO: Fetch the status of recent requests + # TODO: Fetch the status of recent requests from a persistent data source. return "Aquí está el estado de tus solicitudes recientes:\n\n- Grabación de proyecto (4h): Aprobado\n- Taller de guion (2h): Pendiente" diff --git a/app/modules/onboarding.py b/app/modules/onboarding.py index 4c143d3..c555fdb 100644 --- a/app/modules/onboarding.py +++ b/app/modules/onboarding.py @@ -1,8 +1,16 @@ # app/modules/onboarding.py +""" +This module handles the initial interaction with the user, specifically the +/start command. + +It is responsible for identifying the user's role and presenting them with a +customized menu of options based on their permissions. This ensures that each +user sees only the actions relevant to them. +""" from telegram import InlineKeyboardButton, InlineKeyboardMarkup def get_owner_menu(): - """Returns the main menu for the owner.""" + """Creates and returns the main menu keyboard for the 'owner' role.""" keyboard = [ [InlineKeyboardButton("📅 Ver mi agenda", callback_data='view_agenda')], [InlineKeyboardButton("⏳ Ver pendientes", callback_data='view_pending')], @@ -10,7 +18,7 @@ def get_owner_menu(): return InlineKeyboardMarkup(keyboard) def get_admin_menu(): - """Returns the main menu for an admin.""" + """Creates and returns the main menu keyboard for the 'admin' role.""" keyboard = [ [InlineKeyboardButton("📊 Ver estado del sistema", callback_data='view_system_status')], [InlineKeyboardButton("👥 Gestionar usuarios", callback_data='manage_users')], @@ -18,7 +26,7 @@ def get_admin_menu(): return InlineKeyboardMarkup(keyboard) def get_team_menu(): - """Returns the main menu for a team member.""" + """Creates and returns the main menu keyboard for the 'team' role.""" keyboard = [ [InlineKeyboardButton("🕒 Proponer actividad", callback_data='propose_activity')], [InlineKeyboardButton("📄 Ver estatus de solicitudes", callback_data='view_requests_status')], @@ -26,7 +34,7 @@ def get_team_menu(): return InlineKeyboardMarkup(keyboard) def get_client_menu(): - """Returns the main menu for a client.""" + """Creates and returns the main menu keyboard for the 'client' role.""" keyboard = [ [InlineKeyboardButton("🗓️ Agendar una cita", callback_data='schedule_appointment')], [InlineKeyboardButton("ℹ️ Información de servicios", callback_data='get_service_info')], @@ -35,7 +43,10 @@ def get_client_menu(): def handle_start(user_role): """ - Handles the /start command and sends a role-based welcome message and menu. + Handles the /start command by sending a role-based welcome message and menu. + + This function acts as a router, determining which menu to display based on + the user's role, which is passed in as an argument. """ welcome_message = "Hola, soy Talía. ¿En qué puedo ayudarte hoy?" @@ -45,7 +56,7 @@ def handle_start(user_role): menu = get_admin_menu() elif user_role == "team": menu = get_team_menu() - else: # client + else: # Default to the client menu for any other role. menu = get_client_menu() return welcome_message, menu diff --git a/app/modules/print.py b/app/modules/print.py index 42e3876..4b85afd 100644 --- a/app/modules/print.py +++ b/app/modules/print.py @@ -1,12 +1,25 @@ # app/modules/print.py +""" +This module provides a command for administrators to print out the current +configuration details of the bot. +It is a debugging and administrative tool that allows authorized users to quickly +inspect key configuration variables without accessing the environment directly. +""" from telegram import Update from telegram.ext import ContextTypes -from permissions import is_admin -from config import TIMEZONE, CALENDAR_ID, N8N_WEBHOOK_URL +from ..permissions import is_admin +from ..config import TIMEZONE, CALENDAR_ID, N8N_WEBHOOK_URL async def print_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - """Handles the /print command.""" + """ + Handles the /print command. + + When triggered, this function first checks if the user has admin privileges. + If they do, it replies with a formatted message displaying the current values + of the TIMEZONE, CALENDAR_ID, and N8N_WEBHOOK_URL configuration variables. + If the user is not an admin, it sends a simple "not authorized" message. + """ chat_id = update.effective_chat.id if is_admin(chat_id): config_details = ( diff --git a/app/modules/servicios.py b/app/modules/servicios.py index aa9a917..6d654da 100644 --- a/app/modules/servicios.py +++ b/app/modules/servicios.py @@ -1,8 +1,20 @@ # app/modules/servicios.py +""" +This module is responsible for providing information about the services offered. + +It's a simple informational module that gives clients an overview of the +available services and can be expanded to provide more detailed information +or initiate a quoting process. +""" def get_service_info(): """ - Provides information about available services. + Provides a brief overview of the available services. + + Currently, this function returns a hardcoded list of services. For a more + dynamic and easily maintainable system, this information could be fetched + from a database, a configuration file, or an external API. """ - # TODO: Fetch service details from a database or config file + # TODO: Fetch service details from a database or a configuration file to + # make the service list easier to manage and update. return "Ofrecemos una variedad de servicios, incluyendo:\n\n- Consultoría Estratégica\n- Desarrollo de Software\n- Talleres de Capacitación\n\n¿Sobre cuál te gustaría saber más?"