docs: Add comments and document /create_tag command

This commit adds detailed inline comments to the `app/modules/create_tag.py` module to improve code clarity and maintainability.

It also updates the `README.md` file to include a new section documenting the functionality and usage of the `/create_tag` command.
This commit is contained in:
google-labs-jules[bot]
2025-12-18 05:24:40 +00:00
parent 9e7e093409
commit 02dba09599
2 changed files with 83 additions and 13 deletions

View File

@@ -132,6 +132,21 @@ Cada módulo cumple una responsabilidad única:
--- ---
## ⚡ Comandos Adicionales
### `/create_tag`
Este comando inicia un flujo conversacional para generar un tag de identificación en formato Base64, compatible con aplicaciones de escritura NFC. El bot solicitará los siguientes datos:
* **Nombre**
* **Número de empleado**
* **Sucursal**
* **ID de Telegram**
Al finalizar, el bot devolverá una cadena de texto en Base64 que contiene un objeto JSON con la información proporcionada.
---
## 🔁 Flujo General de Ejecución ## 🔁 Flujo General de Ejecución
1. Usuario envía mensaje o interactúa con botones 1. Usuario envía mensaje o interactúa con botones

View File

@@ -1,4 +1,12 @@
# app/modules/create_tag.py # app/modules/create_tag.py
"""
This module contains the functionality for the /create_tag command.
It uses a ConversationHandler to guide the user through a series of questions
to collect data (name, employee number, branch, and Telegram ID), and then
generates a Base64-encoded JSON string from that data. This string is intended
to be used for creating an NFC tag.
"""
import base64 import base64
import json import json
import logging import logging
@@ -11,40 +19,62 @@ from telegram.ext import (
filters, filters,
) )
# Enable logging # Enable logging to monitor the bot's operation and for debugging.
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Define states for the conversation # Define the states for the conversation. These states are used to track the
# user's progress through the conversation and determine which handler function
# should be executed next.
NAME, NUM_EMP, SUCURSAL, TELEGRAM_ID = range(4) NAME, NUM_EMP, SUCURSAL, TELEGRAM_ID = range(4)
async def create_tag_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def create_tag_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Starts the conversation to create a new tag.""" """
Starts the conversation to create a new tag when the /create_tag command
is issued. It prompts the user for the first piece of information (name).
"""
await update.message.reply_text("Vamos a crear un nuevo tag. Por favor, dime el nombre:") await update.message.reply_text("Vamos a crear un nuevo tag. Por favor, dime el nombre:")
# The function returns the next state, which is NAME, so the conversation
# knows which handler to call next.
return NAME return NAME
async def get_name(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def get_name(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Stores the name and asks for the employee number.""" """
Stores the user's provided name in the context and then asks for the
next piece of information, the employee number.
"""
context.user_data['name'] = update.message.text context.user_data['name'] = update.message.text
await update.message.reply_text("Gracias. Ahora, por favor, dime el número de empleado:") await update.message.reply_text("Gracias. Ahora, por favor, dime el número de empleado:")
# The function returns the next state, NUM_EMP.
return NUM_EMP return NUM_EMP
async def get_num_emp(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def get_num_emp(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Stores the employee number and asks for the branch.""" """
Stores the employee number and proceeds to ask for the branch name.
"""
context.user_data['num_emp'] = update.message.text context.user_data['num_emp'] = update.message.text
await update.message.reply_text("Entendido. Ahora, por favor, dime la sucursal:") await update.message.reply_text("Entendido. Ahora, por favor, dime la sucursal:")
# The function returns the next state, SUCURSAL.
return SUCURSAL return SUCURSAL
async def get_sucursal(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def get_sucursal(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Stores the branch and asks for the Telegram ID.""" """
Stores the branch name and asks for the final piece of information,
the user's Telegram ID.
"""
context.user_data['sucursal'] = update.message.text context.user_data['sucursal'] = update.message.text
await update.message.reply_text("Perfecto. Finalmente, por favor, dime el ID de Telegram:") await update.message.reply_text("Perfecto. Finalmente, por favor, dime el ID de Telegram:")
# The function returns the next state, TELEGRAM_ID.
return TELEGRAM_ID return TELEGRAM_ID
async def get_telegram_id(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def get_telegram_id(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Stores the Telegram ID, generates the Base64 string, and ends the conversation.""" """
Stores the Telegram ID, assembles all the collected data into a JSON
object, encodes it into a Base64 string, and sends the result back to
the user. This function concludes the conversation.
"""
context.user_data['telegram_id'] = update.message.text context.user_data['telegram_id'] = update.message.text
# Create the JSON object from the collected data # Create a dictionary from the data collected and stored in user_data.
tag_data = { tag_data = {
"name": context.user_data.get('name'), "name": context.user_data.get('name'),
"num_emp": context.user_data.get('num_emp'), "num_emp": context.user_data.get('num_emp'),
@@ -52,36 +82,61 @@ async def get_telegram_id(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
"telegram_id": context.user_data.get('telegram_id'), "telegram_id": context.user_data.get('telegram_id'),
} }
# Convert the dictionary to a JSON string # Convert the Python dictionary into a JSON formatted string.
json_string = json.dumps(tag_data) json_string = json.dumps(tag_data)
# Encode the JSON string to Base64 # Encode the JSON string into Base64. The string is first encoded to
# UTF-8 bytes, which is then encoded to Base64 bytes, and finally
# decoded back to a UTF-8 string for display.
base64_bytes = base64.b64encode(json_string.encode('utf-8')) base64_bytes = base64.b64encode(json_string.encode('utf-8'))
base64_string = base64_bytes.decode('utf-8') base64_string = base64_bytes.decode('utf-8')
await update.message.reply_text(f"¡Gracias! Aquí está tu tag en formato Base64:\n\n`{base64_string}`", parse_mode='Markdown') await update.message.reply_text(f"¡Gracias! Aquí está tu tag en formato Base64:\n\n`{base64_string}`", parse_mode='Markdown')
# Clean up user_data # Clean up the user_data dictionary to ensure no data from this
# conversation is accidentally used in another one.
context.user_data.clear() context.user_data.clear()
# End the conversation.
return ConversationHandler.END return ConversationHandler.END
async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Cancels and ends the conversation.""" """
Cancels and ends the conversation if the user issues the /cancel command.
It also clears any data that has been collected so far.
"""
await update.message.reply_text("Creación de tag cancelada.") await update.message.reply_text("Creación de tag cancelada.")
context.user_data.clear() context.user_data.clear()
return ConversationHandler.END return ConversationHandler.END
def create_tag_conv_handler(): def create_tag_conv_handler():
"""Creates a conversation handler for the /create_tag command.""" """
Creates and returns a ConversationHandler for the /create_tag command.
This handler manages the entire conversational flow, from starting the
conversation to handling user inputs and ending the conversation.
"""
return ConversationHandler( return ConversationHandler(
# The entry_points list defines how the conversation can be started.
# In this case, it's started by the /create_tag command.
entry_points=[CommandHandler('create_tag', create_tag_start)], entry_points=[CommandHandler('create_tag', create_tag_start)],
# The states dictionary maps the conversation states to their
# respective handler functions. When the conversation is in a
# particular state, the corresponding handler is called to process
# the user's message.
states={ states={
NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_name)], NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_name)],
NUM_EMP: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_num_emp)], NUM_EMP: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_num_emp)],
SUCURSAL: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_sucursal)], SUCURSAL: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_sucursal)],
TELEGRAM_ID: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_telegram_id)], TELEGRAM_ID: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_telegram_id)],
}, },
# The fallbacks list defines handlers that are called if the user
# sends a message that doesn't match the current state's handler.
# Here, it's used to handle the /cancel command.
fallbacks=[CommandHandler('cancel', cancel)], fallbacks=[CommandHandler('cancel', cancel)],
# per_message=False means the conversation is tied to the user, not
# to a specific message, which is standard for this type of flow.
per_message=False per_message=False
) )