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.
This commit is contained in:
google-labs-jules[bot]
2025-12-21 10:13:28 +00:00
parent 5274ac3b0c
commit 7b633ef2e8
6 changed files with 54 additions and 136 deletions

View File

@@ -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.

View File

@@ -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"
}
]
}

View File

@@ -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(

View File

@@ -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
)

View File

@@ -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

View File

@@ -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}`"