mirror of
https://github.com/marcogll/talia_bot.git
synced 2026-01-13 21:35: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**.
|
* 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.
|
* 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
|
## ⚙️ Instalación y Configuración
|
||||||
@@ -165,9 +171,9 @@ talia_bot/
|
|||||||
- **✅ Integración con Google Calendar**: Consulta de agenda.
|
- **✅ Integración con Google Calendar**: Consulta de agenda.
|
||||||
- **✅ Servicio de Impresión Remota (SMTP/IMAP)**: Envío de documentos y monitoreo de estado.
|
- **✅ 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.
|
- **✅ 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
|
### 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.
|
- [ ] **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.
|
- [ ] **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",
|
"id": "admin_create_nfc_tag",
|
||||||
"role": "admin",
|
"role": "admin",
|
||||||
"trigger_button": "➕ Crear Tag NFC",
|
"trigger_button": "start_create_tag",
|
||||||
"steps": [
|
"steps": [
|
||||||
{
|
{
|
||||||
"step_id": 0,
|
"step_id": 0,
|
||||||
"variable": "NFC_ACTION_TYPE",
|
"variable": "EMPLOYEE_NAME",
|
||||||
"question": "Creemos un nuevo tag NFC. ¿Qué acción quieres que dispare?",
|
"question": "Vamos a crear un nuevo tag de empleado. Por favor, dime el nombre completo:",
|
||||||
"options": ["Iniciar Flujo", "URL Estática"]
|
"input_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_id": 1,
|
"step_id": 1,
|
||||||
"variable": "NFC_FLOW_CHOICE",
|
"variable": "EMPLOYEE_ID",
|
||||||
"question": "Okay, ¿qué flujo debería iniciar este tag?",
|
"question": "Gracias. Ahora, por favor, dime el número de empleado:",
|
||||||
"input_type": "dynamic_keyboard_flows"
|
"input_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_id": 2,
|
"step_id": 2,
|
||||||
"variable": "NFC_CONFIRM",
|
"variable": "BRANCH",
|
||||||
"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}",
|
"question": "Entendido. Ahora, por favor, selecciona la sucursal:",
|
||||||
"options": ["✅ Hecho"]
|
"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
|
from talia_bot.modules.admin import get_system_status
|
||||||
import os
|
import os
|
||||||
from talia_bot.modules.debug import print_handler
|
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.vikunja import vikunja_conv_handler
|
||||||
from talia_bot.modules.printer import send_file_to_printer, check_print_status
|
from talia_bot.modules.printer import send_file_to_printer, check_print_status
|
||||||
from talia_bot.db import setup_database
|
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":
|
elif result["status"] == "complete":
|
||||||
if "sales_pitch" in result:
|
if "sales_pitch" in result:
|
||||||
await update.message.reply_text(result["sales_pitch"])
|
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:
|
else:
|
||||||
await update.message.reply_text("Gracias por completar el flujo.")
|
await update.message.reply_text("Gracias por completar el flujo.")
|
||||||
elif result["status"] == "error":
|
elif result["status"] == "error":
|
||||||
@@ -167,8 +168,6 @@ async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
|||||||
elif query.data.startswith(('approve:', 'reject:')):
|
elif query.data.startswith(('approve:', 'reject:')):
|
||||||
logger.info(f"Ejecutando acción de aprobación: {query.data}")
|
logger.info(f"Ejecutando acción de aprobación: {query.data}")
|
||||||
response_text = handle_approval_action(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:
|
else:
|
||||||
logger.warning(f"Consulta no manejada por el despachador: {query.data}")
|
logger.warning(f"Consulta no manejada por el despachador: {query.data}")
|
||||||
await query.edit_message_text(text=response_text)
|
await query.edit_message_text(text=response_text)
|
||||||
@@ -207,7 +206,6 @@ def main() -> None:
|
|||||||
schedule_daily_summary(application)
|
schedule_daily_summary(application)
|
||||||
|
|
||||||
# El orden de los handlers es crucial para que las conversaciones funcionen.
|
# 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())
|
application.add_handler(vikunja_conv_handler())
|
||||||
|
|
||||||
conv_handler = ConversationHandler(
|
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
|
import os
|
||||||
from talia_bot.db import get_db_connection
|
from talia_bot.db import get_db_connection
|
||||||
from talia_bot.modules.sales_rag import generate_sales_pitch
|
from talia_bot.modules.sales_rag import generate_sales_pitch
|
||||||
|
from talia_bot.modules.nfc_tag import generate_nfc_tag
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -127,6 +128,9 @@ class FlowEngine:
|
|||||||
user_query = final_data.get('IDEA_PITCH', '')
|
user_query = final_data.get('IDEA_PITCH', '')
|
||||||
sales_pitch = generate_sales_pitch(user_query, final_data)
|
sales_pitch = generate_sales_pitch(user_query, final_data)
|
||||||
response['sales_pitch'] = sales_pitch
|
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
|
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