mirror of
https://github.com/marcogll/talia_bot.git
synced 2026-01-13 13:25:19 +00:00
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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
|
||||
25
talia_bot/modules/nfc_tag.py
Normal file
25
talia_bot/modules/nfc_tag.py
Normal 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}`"
|
||||
Reference in New Issue
Block a user