From 7b633ef2e8d1d9fe8cb0d2f1b21ae2b83fe54721 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 21 Dec 2025 10:13:28 +0000 Subject: [PATCH] feat: Implement NFC tag creation wizard via FlowEngine This commit implements the NFC tag creation wizard as a data-driven JSON flow, completing a key item from the roadmap. - **Refactor to FlowEngine:** The previous implementation, which used a `ConversationHandler`, has been completely replaced. The wizard is now defined in `talia_bot/data/flows/admin_create_nfc_tag.json` and is managed by the central `FlowEngine`. - **New Module:** The logic for generating the Base64 tag is encapsulated in a new, dedicated module: `talia_bot/modules/nfc_tag.py`. - **Improved UX:** The "Sucursal" (branch) selection step now uses buttons, as originally specified in the documentation, improving the user experience. - **Code Cleanup:** The obsolete `talia_bot/modules/create_tag.py` module and its corresponding `ConversationHandler` have been removed from `main.py`, making the codebase more consistent and maintainable. - **Documentation:** The `README.md` has been updated to mark the feature as complete and to include a description of the new wizard. --- README.md | 8 +- .../data/flows/admin_create_nfc_tag.json | 26 ++-- talia_bot/main.py | 6 +- talia_bot/modules/create_tag.py | 121 ------------------ talia_bot/modules/flow_engine.py | 4 + talia_bot/modules/nfc_tag.py | 25 ++++ 6 files changed, 54 insertions(+), 136 deletions(-) delete mode 100644 talia_bot/modules/create_tag.py create mode 100644 talia_bot/modules/nfc_tag.py diff --git a/README.md b/README.md index 2f9bdd7..9bc4831 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,12 @@ Permite a los usuarios autorizados (`admin`) enviar documentos a una impresora f * El acceso a las funcionalidades está restringido por roles (`admin`, `crew`, `client`), los cuales se gestionan en una base de datos **SQLite**. * Los menús y opciones se muestran dinámicamente según el rol del usuario, asegurando que cada quien solo vea las herramientas que le corresponden. +### 5. NFC Tag Wizard + +* Un flujo de conversación exclusivo para administradores que permite registrar a un nuevo colaborador. +* El wizard recopila el nombre, ID de empleado, sucursal y Telegram ID a través de una serie de preguntas. +* Al finalizar, genera un string en formato **Base64** que contiene los datos en un objeto JSON, listo para ser escrito en una etiqueta NFC. + --- ## ⚙️ Instalación y Configuración @@ -165,9 +171,9 @@ talia_bot/ - **✅ Integración con Google Calendar**: Consulta de agenda. - **✅ Servicio de Impresión Remota (SMTP/IMAP)**: Envío de documentos y monitoreo de estado. - **✅ Flujo de Ventas RAG**: Captura de leads y generación de propuestas personalizadas con IA. +- **✅ Wizard de Creación de Tags NFC**: Flujo para registrar nuevos colaboradores y generar un tag Base64. ### Próximos Pasos -- [ ] **Wizard de Creación de Tags NFC (Base64)**: Implementar el flujo completo para registrar nuevos colaboradores. - [ ] **Soporte para Fotos en Impresión**: Añadir la capacidad de enviar imágenes al servicio de impresión. - [ ] **Migración a Google Gemini 1.5 Pro**: Evaluar y migrar el motor de IA para optimizar costos y capacidades. diff --git a/talia_bot/data/flows/admin_create_nfc_tag.json b/talia_bot/data/flows/admin_create_nfc_tag.json index 56e3d92..999c13f 100644 --- a/talia_bot/data/flows/admin_create_nfc_tag.json +++ b/talia_bot/data/flows/admin_create_nfc_tag.json @@ -1,25 +1,31 @@ { "id": "admin_create_nfc_tag", "role": "admin", - "trigger_button": "➕ Crear Tag NFC", + "trigger_button": "start_create_tag", "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"] + "variable": "EMPLOYEE_NAME", + "question": "Vamos a crear un nuevo tag de empleado. Por favor, dime el nombre completo:", + "input_type": "text" }, { "step_id": 1, - "variable": "NFC_FLOW_CHOICE", - "question": "Okay, ¿qué flujo debería iniciar este tag?", - "input_type": "dynamic_keyboard_flows" + "variable": "EMPLOYEE_ID", + "question": "Gracias. Ahora, por favor, dime el número de empleado:", + "input_type": "text" }, { "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"] + "variable": "BRANCH", + "question": "Entendido. Ahora, por favor, selecciona la sucursal:", + "options": ["🏢 Oficina Principal", "📦 Almacén", "🚚 Logística"] + }, + { + "step_id": 3, + "variable": "TELEGRAM_ID", + "question": "Perfecto. Finalmente, por favor, dime el ID numérico de Telegram del empleado:", + "input_type": "text" } ] } diff --git a/talia_bot/main.py b/talia_bot/main.py index e44ca22..3831c41 100644 --- a/talia_bot/main.py +++ b/talia_bot/main.py @@ -35,7 +35,6 @@ from talia_bot.modules.servicios import get_service_info from talia_bot.modules.admin import get_system_status import os from talia_bot.modules.debug import print_handler -from talia_bot.modules.create_tag import create_tag_conv_handler from talia_bot.modules.vikunja import vikunja_conv_handler from talia_bot.modules.printer import send_file_to_printer, check_print_status from talia_bot.db import setup_database @@ -90,6 +89,8 @@ async def text_and_voice_handler(update: Update, context: ContextTypes.DEFAULT_T elif result["status"] == "complete": if "sales_pitch" in result: await update.message.reply_text(result["sales_pitch"]) + elif "nfc_tag" in result: + await update.message.reply_text(result["nfc_tag"], parse_mode='Markdown') else: await update.message.reply_text("Gracias por completar el flujo.") elif result["status"] == "error": @@ -167,8 +168,6 @@ async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE) elif query.data.startswith(('approve:', 'reject:')): logger.info(f"Ejecutando acción de aprobación: {query.data}") response_text = handle_approval_action(query.data) - elif query.data == 'start_create_tag': - response_text = "Para crear un tag, por favor usa el comando /create_tag." else: logger.warning(f"Consulta no manejada por el despachador: {query.data}") await query.edit_message_text(text=response_text) @@ -207,7 +206,6 @@ def main() -> None: schedule_daily_summary(application) # El orden de los handlers es crucial para que las conversaciones funcionen. - application.add_handler(create_tag_conv_handler()) application.add_handler(vikunja_conv_handler()) conv_handler = ConversationHandler( diff --git a/talia_bot/modules/create_tag.py b/talia_bot/modules/create_tag.py deleted file mode 100644 index e145868..0000000 --- a/talia_bot/modules/create_tag.py +++ /dev/null @@ -1,121 +0,0 @@ -# app/modules/create_tag.py -# Este módulo permite crear un "tag" (etiqueta) con información del empleado. -# Usa un ConversationHandler para hacer una serie de preguntas al usuario. -# Al final, genera un código en Base64 que contiene toda la información en formato JSON. - -import base64 -import json -import logging -from telegram import Update -from telegram.ext import ( - ContextTypes, - ConversationHandler, - CommandHandler, - MessageHandler, - filters, -) - -# Configuramos los logs para este archivo -logger = logging.getLogger(__name__) - -# Definimos los estados de la conversación. -# Cada número representa un paso en el proceso de preguntas. -NAME, NUM_EMP, SUCURSAL, TELEGRAM_ID = range(4) - -async def create_tag_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """ - Inicia el proceso cuando el usuario escribe /create_tag. - Pide el primer dato: el nombre. - """ - await update.message.reply_text("Vamos a crear un nuevo tag. Por favor, dime el nombre:") - # Devolvemos el siguiente estado: NAME - return NAME - -async def get_name(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """ - Guarda el nombre y pide el número de empleado. - """ - context.user_data['name'] = update.message.text - await update.message.reply_text("Gracias. Ahora, por favor, dime el número de empleado:") - # Devolvemos el siguiente estado: NUM_EMP - return NUM_EMP - -async def get_num_emp(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """ - Guarda el número de empleado y pide la sucursal. - """ - context.user_data['num_emp'] = update.message.text - await update.message.reply_text("Entendido. Ahora, por favor, dime la sucursal:") - # Devolvemos el siguiente estado: SUCURSAL - return SUCURSAL - -async def get_sucursal(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """ - Guarda la sucursal y pide el ID de Telegram. - """ - context.user_data['sucursal'] = update.message.text - await update.message.reply_text("Perfecto. Finalmente, por favor, dime el ID de Telegram:") - # Devolvemos el siguiente estado: TELEGRAM_ID - return TELEGRAM_ID - -async def get_telegram_id(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """ - Guarda el ID de Telegram, junta todos los datos y genera el código Base64. - """ - context.user_data['telegram_id'] = update.message.text - - # Creamos un diccionario (como una caja con etiquetas) con todos los datos - tag_data = { - "name": context.user_data.get('name'), - "num_emp": context.user_data.get('num_emp'), - "sucursal": context.user_data.get('sucursal'), - "telegram_id": context.user_data.get('telegram_id'), - } - - # Convertimos el diccionario a una cadena de texto en formato JSON - json_string = json.dumps(tag_data) - - # Convertimos esa cadena a Base64 (un formato que se puede guardar en tags NFC) - # 1. Codificamos a bytes (utf-8) - # 2. Codificamos esos bytes a base64 - # 3. Convertimos de vuelta a texto para mostrarlo - base64_bytes = base64.b64encode(json_string.encode('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') - - # Limpiamos los datos temporales del usuario - context.user_data.clear() - - # Terminamos la conversación - return ConversationHandler.END - -async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - """ - Cancela el proceso si el usuario escribe /cancel. - """ - await update.message.reply_text("Creación de tag cancelada.") - context.user_data.clear() - return ConversationHandler.END - -def create_tag_conv_handler(): - """ - Configura el manejador de la conversación (el flujo de preguntas). - """ - return ConversationHandler( - # Punto de entrada: el comando /create_tag - entry_points=[CommandHandler('create_tag', create_tag_start)], - - # Mapa de estados: qué función responde a cada paso - states={ - NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_name)], - NUM_EMP: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_num_emp)], - SUCURSAL: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_sucursal)], - TELEGRAM_ID: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_telegram_id)], - }, - - # Si algo falla o el usuario cancela - fallbacks=[CommandHandler('cancel', cancel)], - - per_message=False - ) diff --git a/talia_bot/modules/flow_engine.py b/talia_bot/modules/flow_engine.py index 9d06006..5727d3b 100644 --- a/talia_bot/modules/flow_engine.py +++ b/talia_bot/modules/flow_engine.py @@ -4,6 +4,7 @@ import logging import os from talia_bot.db import get_db_connection from talia_bot.modules.sales_rag import generate_sales_pitch +from talia_bot.modules.nfc_tag import generate_nfc_tag logger = logging.getLogger(__name__) @@ -127,6 +128,9 @@ class FlowEngine: user_query = final_data.get('IDEA_PITCH', '') sales_pitch = generate_sales_pitch(user_query, final_data) response['sales_pitch'] = sales_pitch + elif flow['id'] == 'admin_create_nfc_tag': + nfc_tag = generate_nfc_tag(final_data) + response['nfc_tag'] = nfc_tag return response diff --git a/talia_bot/modules/nfc_tag.py b/talia_bot/modules/nfc_tag.py new file mode 100644 index 0000000..1408475 --- /dev/null +++ b/talia_bot/modules/nfc_tag.py @@ -0,0 +1,25 @@ +# talia_bot/modules/nfc_tag.py +# This module contains the logic for generating NFC tags. + +import base64 +import json +import logging + +logger = logging.getLogger(__name__) + +def generate_nfc_tag(collected_data): + """ + Generates a Base64 encoded string from the collected data. + """ + tag_data = { + "name": collected_data.get("EMPLOYEE_NAME"), + "num_emp": collected_data.get("EMPLOYEE_ID"), + "sucursal": collected_data.get("BRANCH"), + "telegram_id": collected_data.get("TELEGRAM_ID"), + } + + json_string = json.dumps(tag_data) + base64_bytes = base64.b64encode(json_string.encode("utf-8")) + base64_string = base64_bytes.decode("utf-8") + + return f"¡Gracias! Aquí está tu tag en formato Base64:\n\n`{base64_string}`"