mirror of
https://github.com/marcogll/telegram_new_socias.git
synced 2026-01-13 13:15:16 +00:00
first commit
This commit is contained in:
6
.env
Normal file
6
.env
Normal file
@@ -0,0 +1,6 @@
|
||||
# Configuración de Telegram
|
||||
TELEGRAM_TOKEN=TU_TOKEN_NUEVO_AQUI
|
||||
|
||||
# Webhooks de n8n (puedes agregar más aquí en el futuro)
|
||||
WEBHOOK_CONTRATO=https://flows.soul23.cloud/webhook/DuXh9Oi7SCAMf9
|
||||
# WEBHOOK_VACACIONES=https://... (futuro)
|
||||
190
Readme.md
Normal file
190
Readme.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# 🤖 Vanessa Bot – Asistente de RH para Vanity
|
||||
|
||||
Vanessa es un bot de Telegram escrito en Python que automatiza procesos internos de Recursos Humanos en Vanity. Su objetivo es eliminar fricción operativa: onboarding, solicitudes de RH e impresión de documentos, todo orquestado desde Telegram y conectado a flujos de n8n.
|
||||
|
||||
Este repositorio está pensado como **proyecto Python profesional**, modular y listo para correr 24/7 en producción.
|
||||
|
||||
---
|
||||
|
||||
## 🧠 ¿Qué hace Vanessa?
|
||||
|
||||
Vanessa no es un chatbot genérico: es una interfaz conversacional para procesos reales de negocio.
|
||||
|
||||
- Onboarding completo de nuevas socias (/welcome)
|
||||
- Envío de archivos a impresión (/print)
|
||||
- Solicitud de vacaciones (/vacaciones)
|
||||
- Solicitud de permisos por horas (/permiso)
|
||||
|
||||
Cada flujo es un módulo independiente y todos los datos se envían a **webhooks de n8n** para su procesamiento posterior.
|
||||
|
||||
---
|
||||
|
||||
## 📂 Estructura del Proyecto
|
||||
|
||||
```
|
||||
vanity_bot/
|
||||
│
|
||||
├── .env # Variables sensibles (tokens, URLs)
|
||||
├── main.py # Cerebro principal del bot
|
||||
├── requirements.txt # Dependencias
|
||||
├── README.md # Este documento
|
||||
│
|
||||
└── modules/ # Habilidades del bot
|
||||
├── __init__.py
|
||||
├── onboarding.py # Flujo /welcome (onboarding RH)
|
||||
├── printer.py # Flujo /print (impresión)
|
||||
└── rh_requests.py # /vacaciones y /permiso
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Configuración (.env)
|
||||
|
||||
Crea un archivo `.env` en la raíz del proyecto con el siguiente contenido:
|
||||
|
||||
```
|
||||
# --- TELEGRAM ---
|
||||
TELEGRAM_TOKEN=TU_TOKEN_AQUI
|
||||
|
||||
# --- WEBHOOKS N8N ---
|
||||
WEBHOOK_ONBOARDING=https://flows.soul23.cloud/webhook/contrato
|
||||
WEBHOOK_PRINT=https://flows.soul23.cloud/webhook/impresion
|
||||
WEBHOOK_VACACIONES=https://flows.soul23.cloud/webhook/vacaciones
|
||||
```
|
||||
|
||||
Nunca subas este archivo al repositorio.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Instalación
|
||||
|
||||
Se recomienda usar un entorno virtual.
|
||||
|
||||
```
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ▶️ Ejecución Manual
|
||||
|
||||
```
|
||||
python main.py
|
||||
```
|
||||
|
||||
Si el token es válido, verás:
|
||||
|
||||
```
|
||||
🧠 Vanessa Brain iniciada y escuchando...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Arquitectura Interna
|
||||
|
||||
### main.py (El Cerebro)
|
||||
|
||||
- Inicializa el bot de Telegram
|
||||
- Carga variables de entorno
|
||||
- Registra los handlers de cada módulo
|
||||
- Define el menú principal (/start, /help)
|
||||
|
||||
Nada de lógica de negocio vive aquí. Solo coordinación.
|
||||
|
||||
---
|
||||
|
||||
### modules/onboarding.py
|
||||
|
||||
Flujo conversacional complejo basado en `ConversationHandler`.
|
||||
|
||||
- Recolecta información personal, laboral y de emergencia
|
||||
- Normaliza datos (RFC, CURP, fechas)
|
||||
- Usa teclados guiados para reducir errores
|
||||
- Envía un payload estructurado a n8n
|
||||
|
||||
El diseño es **estado → pregunta → respuesta → siguiente estado**.
|
||||
|
||||
---
|
||||
|
||||
### modules/printer.py
|
||||
|
||||
- Recibe documentos o imágenes desde Telegram
|
||||
- Obtiene el enlace temporal de Telegram
|
||||
- Envía el archivo a una cola de impresión vía webhook
|
||||
|
||||
Telegram se usa como interfaz, n8n como backend operativo.
|
||||
|
||||
---
|
||||
|
||||
### modules/rh_requests.py
|
||||
|
||||
- Maneja solicitudes simples de RH
|
||||
- Vacaciones
|
||||
- Permisos por horas
|
||||
|
||||
El bot solo valida y recopila; la lógica de aprobación vive fuera.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Ejecución Automática con systemd (Linux)
|
||||
|
||||
Ejemplo de servicio:
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=Vanessa Bot
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=vanity
|
||||
WorkingDirectory=/opt/vanity_bot
|
||||
EnvironmentFile=/opt/vanity_bot/.env
|
||||
ExecStart=/opt/vanity_bot/venv/bin/python main.py
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Luego:
|
||||
|
||||
```
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable vanessa
|
||||
sudo systemctl start vanessa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Filosofía del Proyecto
|
||||
|
||||
- Telegram como UI
|
||||
- Python como cerebro
|
||||
- n8n como sistema nervioso
|
||||
- Datos estructurados, no mensajes sueltos
|
||||
- Modularidad total: cada habilidad se enchufa o se quita
|
||||
|
||||
Vanessa no reemplaza RH: elimina fricción humana innecesaria.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Extensiones Futuras
|
||||
|
||||
- Firma digital de contratos
|
||||
- Finder de documentos
|
||||
- Reportes automáticos
|
||||
- Roles y permisos
|
||||
- Modo administrador
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Estado del Proyecto
|
||||
|
||||
✔ Funcional en producción
|
||||
✔ Modular
|
||||
✔ Escalable
|
||||
✔ Auditable
|
||||
|
||||
Vanessa está viva. Y aprende con cada flujo nuevo.
|
||||
54
main.py
Normal file
54
main.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import os
|
||||
import logging
|
||||
from dotenv import load_dotenv
|
||||
from telegram import Update
|
||||
from telegram.constants import ParseMode
|
||||
from telegram.ext import Application, Defaults, CommandHandler, ContextTypes
|
||||
|
||||
# --- IMPORTAR HABILIDADES ---
|
||||
from modules.onboarding import onboarding_handler
|
||||
from modules.printer import print_handler
|
||||
from modules.rh_requests import vacaciones_handler, permiso_handler
|
||||
# from modules.finder import finder_handler (Si lo creas después)
|
||||
|
||||
load_dotenv()
|
||||
TOKEN = os.getenv("TELEGRAM_TOKEN")
|
||||
|
||||
logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)
|
||||
|
||||
async def menu_principal(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Muestra el menú de opciones de Vanessa"""
|
||||
texto = (
|
||||
"👩💼 **Hola, soy Vanessa. ¿En qué puedo ayudarte hoy?**\n\n"
|
||||
"📝 `/welcome` - Iniciar onboarding/contrato\n"
|
||||
"🖨️ `/print` - Imprimir o enviar archivo\n"
|
||||
"🌴 `/vacaciones` - Solicitar días libres\n"
|
||||
"⏱️ `/permiso` - Solicitar permiso por horas\n"
|
||||
"🔍 `/socia_finder` - Buscar datos de una compañera\n\n"
|
||||
"Selecciona un comando para empezar."
|
||||
)
|
||||
await update.message.reply_text(texto)
|
||||
|
||||
def main():
|
||||
# Configuración Global
|
||||
defaults = Defaults(parse_mode=ParseMode.MARKDOWN)
|
||||
app = Application.builder().token(TOKEN).defaults(defaults).build()
|
||||
|
||||
# --- REGISTRO DE HABILIDADES ---
|
||||
|
||||
# 1. Comando de Ayuda / Menú
|
||||
app.add_handler(CommandHandler("start", menu_principal))
|
||||
app.add_handler(CommandHandler("help", menu_principal))
|
||||
|
||||
# 2. Habilidades Complejas (Conversaciones)
|
||||
app.add_handler(onboarding_handler)
|
||||
app.add_handler(print_handler)
|
||||
app.add_handler(vacaciones_handler)
|
||||
app.add_handler(permiso_handler)
|
||||
# app.add_handler(finder_handler)
|
||||
|
||||
print("🧠 Vanessa Bot Brain iniciada y lista para trabajar en todos los módulos.")
|
||||
app.run_polling()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
343
modules/onboarding.py
Normal file
343
modules/onboarding.py
Normal file
@@ -0,0 +1,343 @@
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from dotenv import load_dotenv # pip install python-dotenv
|
||||
|
||||
from telegram import Update, ReplyKeyboardRemove, ReplyKeyboardMarkup
|
||||
from telegram.constants import ParseMode
|
||||
from telegram.ext import (
|
||||
Application,
|
||||
CommandHandler,
|
||||
ContextTypes,
|
||||
ConversationHandler,
|
||||
MessageHandler,
|
||||
filters,
|
||||
Defaults,
|
||||
)
|
||||
|
||||
# --- 1. CARGA DE ENTORNO ---
|
||||
load_dotenv() # Carga las variables del archivo .env
|
||||
TOKEN = os.getenv("TELEGRAM_TOKEN")
|
||||
# Convertimos la string del webhook en una lista (por si en el futuro hay varios separados por coma)
|
||||
WEBHOOK_URLS = os.getenv("WEBHOOK_CONTRATO", "").split(",")
|
||||
|
||||
# Validación de seguridad
|
||||
if not TOKEN:
|
||||
raise ValueError("⚠️ Error: No se encontró TELEGRAM_TOKEN en el archivo .env")
|
||||
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
||||
)
|
||||
|
||||
# --- 2. ESTADOS DEL FLUJO ---
|
||||
(
|
||||
NOMBRE_SALUDO, NOMBRE_COMPLETO, APELLIDO_PATERNO, APELLIDO_MATERNO,
|
||||
CUMPLE_DIA, CUMPLE_MES, CUMPLE_ANIO, ESTADO_NACIMIENTO,
|
||||
RFC, CURP,
|
||||
CORREO, CELULAR,
|
||||
CALLE, NUM_EXTERIOR, NUM_INTERIOR, COLONIA, CODIGO_POSTAL, CIUDAD_RESIDENCIA,
|
||||
ROL, SUCURSAL, INICIO_DIA, INICIO_MES, INICIO_ANIO,
|
||||
REF1_NOMBRE, REF1_TELEFONO, REF1_TIPO,
|
||||
REF2_NOMBRE, REF2_TELEFONO, REF2_TIPO,
|
||||
REF3_NOMBRE, REF3_TELEFONO, REF3_TIPO,
|
||||
EMERGENCIA_NOMBRE, EMERGENCIA_TEL, EMERGENCIA_RELACION
|
||||
) = range(35)
|
||||
|
||||
# --- 3. HELPER: NORMALIZACIÓN Y MAPEOS ---
|
||||
|
||||
def normalizar_id(texto: str) -> str:
|
||||
"""Elimina espacios y convierte a mayúsculas (para RFC y CURP)."""
|
||||
if not texto: return "N/A"
|
||||
# Elimina todos los espacios en blanco y pone mayúsculas
|
||||
limpio = "".join(texto.split()).upper()
|
||||
return "N/A" if limpio == "0" else limpio
|
||||
|
||||
def limpiar_texto_general(texto: str) -> str:
|
||||
t = texto.strip()
|
||||
return "N/A" if t == "0" else t
|
||||
|
||||
# --- 4. TECLADOS DINÁMICOS ---
|
||||
|
||||
# Meses: Texto vs Valor
|
||||
MAPA_MESES = {
|
||||
"Enero": "01", "Febrero": "02", "Marzo": "03", "Abril": "04",
|
||||
"Mayo": "05", "Junio": "06", "Julio": "07", "Agosto": "08",
|
||||
"Septiembre": "09", "Octubre": "10", "Noviembre": "11", "Diciembre": "12"
|
||||
}
|
||||
# Generamos el teclado de 3 en 3
|
||||
TECLADO_MESES = ReplyKeyboardMarkup(
|
||||
[list(MAPA_MESES.keys())[i:i+3] for i in range(0, 12, 3)],
|
||||
one_time_keyboard=True, resize_keyboard=True
|
||||
)
|
||||
|
||||
# Años: Actual y Siguiente
|
||||
anio_actual = datetime.now().year
|
||||
TECLADO_ANIOS_INICIO = ReplyKeyboardMarkup(
|
||||
[[str(anio_actual), str(anio_actual + 1)]],
|
||||
one_time_keyboard=True, resize_keyboard=True
|
||||
)
|
||||
|
||||
# Roles
|
||||
TECLADO_ROLES = ReplyKeyboardMarkup(
|
||||
[["Partner", "Manager"], ["Staff", "Tech"], ["Marketing"]],
|
||||
one_time_keyboard=True, resize_keyboard=True
|
||||
)
|
||||
|
||||
# Sucursales (Mapeo Visual -> ID Técnico)
|
||||
MAPA_SUCURSALES = {
|
||||
"Plaza Cima (Sur) ⛰️": "plaza_cima",
|
||||
"Plaza O (Carranza) 🏙️": "plaza_o"
|
||||
}
|
||||
TECLADO_SUCURSALES = ReplyKeyboardMarkup(
|
||||
[["Plaza Cima (Sur) ⛰️", "Plaza O (Carranza) 🏙️"]],
|
||||
one_time_keyboard=True, resize_keyboard=True
|
||||
)
|
||||
|
||||
TECLADO_CIUDAD = ReplyKeyboardMarkup(
|
||||
[["Saltillo", "Ramos Arizpe", "Arteaga"]],
|
||||
one_time_keyboard=True, resize_keyboard=True
|
||||
)
|
||||
|
||||
TECLADO_REF_TIPO = ReplyKeyboardMarkup(
|
||||
[["Familiar", "Amistad"], ["Trabajo", "Académica", "Otra"]],
|
||||
one_time_keyboard=True, resize_keyboard=True
|
||||
)
|
||||
|
||||
TECLADO_RELACION_EMERGENCIA = ReplyKeyboardMarkup(
|
||||
[["Padre/Madre", "Esposo/a", "Hijo/a"], ["Hermano/a", "Amigo/a", "Otro"]],
|
||||
one_time_keyboard=True, resize_keyboard=True
|
||||
)
|
||||
|
||||
# --- 5. LOGICA DEL BOT (VANESSA) ---
|
||||
|
||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
user = update.effective_user
|
||||
context.user_data.clear()
|
||||
|
||||
context.user_data["metadata"] = {
|
||||
"telegram_id": user.id,
|
||||
"username": user.username or "N/A",
|
||||
"first_name": user.first_name,
|
||||
"start_ts": datetime.now().timestamp()
|
||||
}
|
||||
context.user_data["respuestas"] = {}
|
||||
|
||||
await update.message.reply_text(
|
||||
f"¡Hola {user.first_name}! 👋\n\n"
|
||||
"Soy *Vanessa de Recursos Humanos* de Vanity. 👩💼\n"
|
||||
"Bienvenida al equipo Soul. Vamos a dejar listo tu registro en unos minutos.\n\n"
|
||||
"💡 _Tip: Si te equivocas, escribe /cancelar y empezamos de nuevo._"
|
||||
)
|
||||
await update.message.reply_text("Para empezar con el pie derecho, ¿cómo te gusta que te llamemos?")
|
||||
return NOMBRE_SALUDO
|
||||
|
||||
async def manejar_flujo(update: Update, context: ContextTypes.DEFAULT_TYPE, estado_actual: int) -> int:
|
||||
texto_recibido = update.message.text
|
||||
respuesta_procesada = limpiar_texto_general(texto_recibido)
|
||||
|
||||
# --- LÓGICA DE PROCESAMIENTO ESPECÍFICA POR ESTADO ---
|
||||
|
||||
# 1. Normalización de RFC y CURP (Quitar espacios, Mayúsculas)
|
||||
if estado_actual in [RFC, CURP]:
|
||||
respuesta_procesada = normalizar_id(texto_recibido)
|
||||
|
||||
# 2. Mapeo de Meses (Texto -> Número)
|
||||
if estado_actual in [CUMPLE_MES, INICIO_MES]:
|
||||
# Si el usuario seleccionó un botón, buscamos su valor numérico
|
||||
respuesta_procesada = MAPA_MESES.get(texto_recibido, texto_recibido) # Fallback al texto si no está en mapa
|
||||
|
||||
# 3. Mapeo de Sucursales (Texto Bonito -> ID Técnico)
|
||||
if estado_actual == SUCURSAL:
|
||||
respuesta_procesada = MAPA_SUCURSALES.get(texto_recibido, "otra_sucursal")
|
||||
|
||||
# Guardar en memoria
|
||||
context.user_data["respuestas"][estado_actual] = respuesta_procesada
|
||||
|
||||
# --- GUIÓN DE ENTREVISTA ---
|
||||
siguiente_estado = estado_actual + 1
|
||||
|
||||
preguntas = {
|
||||
NOMBRE_SALUDO: "¡Lindo nombre! ✨\n\nNecesito tus datos oficiales para el contrato.\n¿Cuál es tu *nombre completo* (nombres) tal cual aparece en tu INE?",
|
||||
NOMBRE_COMPLETO: "¿Cuál es tu *apellido paterno*?",
|
||||
APELLIDO_PATERNO: "¿Y tu *apellido materno*?",
|
||||
|
||||
# Cumpleaños
|
||||
APELLIDO_MATERNO: "🎂 Hablemos de ti. ¿Qué *día* es tu cumpleaños? (Escribe el número, ej: 13)",
|
||||
CUMPLE_DIA: {"texto": "¿De qué *mes*? 🎉", "teclado": TECLADO_MESES},
|
||||
CUMPLE_MES: "Entendido. ¿Y de qué *año*? 🗓️",
|
||||
CUMPLE_ANIO: "🇲🇽 ¿En qué *estado de la república* naciste?",
|
||||
|
||||
# Identificación
|
||||
ESTADO_NACIMIENTO: "Pasemos a lo administrativo 📄.\n\nPor favor escribe tu *RFC* (Sin espacios):",
|
||||
RFC: "Gracias. Ahora tu *CURP*:",
|
||||
|
||||
# Contacto
|
||||
CURP: "¡Súper! 📧 ¿A qué *correo electrónico* te enviamos la info?",
|
||||
CORREO: "📱 ¿Cuál es tu número de *celular* personal? (10 dígitos)",
|
||||
|
||||
# Domicilio
|
||||
CELULAR: "🏠 Registremos tu domicilio.\n\n¿En qué *calle* vives?",
|
||||
CALLE: "#️⃣ ¿Cuál es el *número exterior*?",
|
||||
NUM_EXTERIOR: "🚪 ¿Tienes *número interior*? (Escribe 0 si no aplica)",
|
||||
NUM_INTERIOR: "🏘️ ¿Cómo se llama la *colonia*?",
|
||||
COLONIA: "📮 ¿Cuál es el *Código Postal*?",
|
||||
CODIGO_POSTAL: {"texto": "¿En qué *ciudad* resides actualmente?", "teclado": TECLADO_CIUDAD},
|
||||
|
||||
# Laboral
|
||||
CIUDAD_RESIDENCIA: {"texto": "¡Excelente! Coahuila es territorio Vanity 🌵.\n\n¿Qué *rol* tendrás en el equipo? 💼", "teclado": TECLADO_ROLES},
|
||||
ROL: {"texto": "¿A qué *sucursal* te vas a integrar? 📍", "teclado": TECLADO_SUCURSALES},
|
||||
SUCURSAL: "¡Qué emoción! 🎉\n\n¿Qué *día* está programado tu ingreso? (Solo el número, ej: 01)",
|
||||
INICIO_DIA: {"texto": "¿De qué *mes* será tu ingreso?", "teclado": TECLADO_MESES},
|
||||
INICIO_MES: {"texto": "¿Y de qué *año*?", "teclado": TECLADO_ANIOS_INICIO},
|
||||
|
||||
# Referencias
|
||||
INICIO_ANIO: "Ya casi acabamos. Necesito 3 referencias.\n\n👤 *Referencia 1*: Nombre completo",
|
||||
REF1_NOMBRE: "📞 Teléfono de la Referencia 1:",
|
||||
REF1_TELEFONO: {"texto": "🧑🤝🧑 ¿Qué relación tienes con ella/él?", "teclado": TECLADO_REF_TIPO},
|
||||
|
||||
REF1_TIPO: "Ok. Vamos con la *Referencia 2*.\n\n👤 Nombre completo:",
|
||||
REF2_NOMBRE: "📞 Teléfono de la Referencia 2:",
|
||||
REF2_TELEFONO: {"texto": "🧑🤝🧑 ¿Qué relación tienen?", "teclado": TECLADO_REF_TIPO},
|
||||
|
||||
REF2_TIPO: "Última. *Referencia 3*.\n\n👤 Nombre completo:",
|
||||
REF3_NOMBRE: "📞 Teléfono de la Referencia 3:",
|
||||
REF3_TELEFONO: {"texto": "🧑🤝🧑 ¿Qué relación tienen?", "teclado": TECLADO_REF_TIPO},
|
||||
|
||||
# Emergencia
|
||||
REF3_TIPO: "Finalmente, por seguridad 🚑:\n\n¿A quién llamamos en caso de *emergencia*?",
|
||||
EMERGENCIA_NOMBRE: "☎️ ¿Cuál es el teléfono de esa persona?",
|
||||
EMERGENCIA_TEL: {"texto": "¿Qué parentesco tiene contigo?", "teclado": TECLADO_RELACION_EMERGENCIA},
|
||||
}
|
||||
|
||||
siguiente = preguntas.get(estado_actual)
|
||||
|
||||
if isinstance(siguiente, dict):
|
||||
await update.message.reply_text(siguiente["texto"], reply_markup=siguiente["teclado"])
|
||||
else:
|
||||
await update.message.reply_text(siguiente, reply_markup=ReplyKeyboardRemove())
|
||||
|
||||
return siguiente_estado
|
||||
|
||||
async def finalizar(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
# Guardar última respuesta (Relación Emergencia)
|
||||
context.user_data["respuestas"][EMERGENCIA_RELACION] = limpiar_texto_general(update.message.text)
|
||||
|
||||
await update.message.reply_text("¡Perfecto! 📝 Guardando tu expediente en el sistema... dame un momento.")
|
||||
|
||||
r = context.user_data["respuestas"]
|
||||
meta = context.user_data["metadata"]
|
||||
|
||||
# Construcción segura de fechas
|
||||
try:
|
||||
fecha_nac = f"{r[CUMPLE_ANIO]}-{r[CUMPLE_MES]}-{str(r[CUMPLE_DIA]).zfill(2)}"
|
||||
fecha_ini = f"{r[INICIO_ANIO]}-{r[INICIO_MES]}-{str(r[INICIO_DIA]).zfill(2)}"
|
||||
except Exception:
|
||||
fecha_nac = "ERROR_FECHA"
|
||||
fecha_ini = "ERROR_FECHA"
|
||||
|
||||
# PAYLOAD ESTRUCTURADO PARA N8N
|
||||
payload = {
|
||||
"candidato": {
|
||||
"nombre_preferido": r.get(NOMBRE_SALUDO),
|
||||
"nombre_oficial": r.get(NOMBRE_COMPLETO),
|
||||
"apellido_paterno": r.get(APELLIDO_PATERNO),
|
||||
"apellido_materno": r.get(APELLIDO_MATERNO),
|
||||
"fecha_nacimiento": fecha_nac,
|
||||
"rfc": r.get(RFC),
|
||||
"curp": r.get(CURP),
|
||||
"lugar_nacimiento": r.get(ESTADO_NACIMIENTO)
|
||||
},
|
||||
"contacto": {
|
||||
"email": r.get(CORREO),
|
||||
"celular": r.get(CELULAR)
|
||||
},
|
||||
"domicilio": {
|
||||
"calle": r.get(CALLE),
|
||||
"num_ext": r.get(NUM_EXTERIOR),
|
||||
"num_int": r.get(NUM_INTERIOR),
|
||||
"colonia": r.get(COLONIA),
|
||||
"cp": r.get(CODIGO_POSTAL),
|
||||
"ciudad": r.get(CIUDAD_RESIDENCIA),
|
||||
"estado": "Coahuila de Zaragoza"
|
||||
},
|
||||
"laboral": {
|
||||
"rol_id": r.get(ROL).lower(), # partner, manager...
|
||||
"sucursal_id": r.get(SUCURSAL), # plaza_cima, plaza_o
|
||||
"fecha_inicio": fecha_ini
|
||||
},
|
||||
"referencias": [
|
||||
{"nombre": r.get(REF1_NOMBRE), "telefono": r.get(REF1_TELEFONO), "relacion": r.get(REF1_TIPO)},
|
||||
{"nombre": r.get(REF2_NOMBRE), "telefono": r.get(REF2_TELEFONO), "relacion": r.get(REF2_TIPO)},
|
||||
{"nombre": r.get(REF3_NOMBRE), "telefono": r.get(REF3_TELEFONO), "relacion": r.get(REF3_TIPO)}
|
||||
],
|
||||
"emergencia": {
|
||||
"nombre": r.get(EMERGENCIA_NOMBRE),
|
||||
"telefono": r.get(EMERGENCIA_TEL),
|
||||
"relacion": r.get(EMERGENCIA_RELACION)
|
||||
},
|
||||
"metadata": {
|
||||
"telegram_user": meta["username"],
|
||||
"chat_id": meta["telegram_id"],
|
||||
"bot_version": "welcome2soul_v2",
|
||||
"fecha_registro": datetime.now().isoformat()
|
||||
}
|
||||
}
|
||||
|
||||
headers = {"Content-Type": "application/json", "User-Agent": "Welcome2Soul-Bot"}
|
||||
|
||||
enviado = False
|
||||
for url in WEBHOOK_URLS:
|
||||
if not url: continue
|
||||
try:
|
||||
res = requests.post(url.strip(), json=payload, headers=headers, timeout=20)
|
||||
res.raise_for_status()
|
||||
enviado = True
|
||||
logging.info(f"Webhook enviado exitosamente a: {url}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error enviando webhook: {e}")
|
||||
|
||||
if enviado:
|
||||
await update.message.reply_text(
|
||||
"✅ *¡Registro Exitoso!*\n\n"
|
||||
"Bienvenida a la familia Soul/Vanity. Tu contrato se está generando y te avisaremos pronto.\n"
|
||||
"¡Nos vemos el primer día! ✨"
|
||||
)
|
||||
else:
|
||||
await update.message.reply_text("⚠️ Se guardaron tus datos pero hubo un error de conexión. RH lo revisará manualmente.")
|
||||
|
||||
context.user_data.clear()
|
||||
return ConversationHandler.END
|
||||
|
||||
async def cancelar(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
await update.message.reply_text(
|
||||
"Proceso cancelado. ⏸️\nCuando quieras retomar, escribe /contrato.",
|
||||
reply_markup=ReplyKeyboardRemove()
|
||||
)
|
||||
context.user_data.clear()
|
||||
return ConversationHandler.END
|
||||
|
||||
def main():
|
||||
defaults = Defaults(parse_mode=ParseMode.MARKDOWN)
|
||||
application = Application.builder().token(TOKEN).defaults(defaults).build()
|
||||
|
||||
states = {}
|
||||
for i in range(34):
|
||||
callback = partial(manejar_flujo, estado_actual=i)
|
||||
states[i] = [MessageHandler(filters.TEXT & ~filters.COMMAND, callback)]
|
||||
|
||||
states[34] = [MessageHandler(filters.TEXT & ~filters.COMMAND, finalizar)]
|
||||
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler("contrato", start)],
|
||||
states=states,
|
||||
fallbacks=[CommandHandler("cancelar", cancelar)],
|
||||
)
|
||||
|
||||
application.add_handler(conv_handler)
|
||||
print("🧠 Welcome2Soul Bot (Vanessa) iniciado...")
|
||||
application.run_polling()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
0
modules/printer.py
Normal file
0
modules/printer.py
Normal file
61
modules/rh_requests.py
Normal file
61
modules/rh_requests.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import os
|
||||
import requests
|
||||
from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||
from telegram.ext import ContextTypes, ConversationHandler, CommandHandler, MessageHandler, filters
|
||||
|
||||
TIPO_SOLICITUD, FECHAS, MOTIVO = range(3)
|
||||
|
||||
async def start_vacaciones(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
context.user_data['tipo'] = 'Vacaciones'
|
||||
await update.message.reply_text("🌴 **Solicitud de Vacaciones**\n\n¿Para qué fechas las necesitas? (Ej: 10 al 15 de Octubre)")
|
||||
return FECHAS
|
||||
|
||||
async def start_permiso(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
context.user_data['tipo'] = 'Permiso Especial'
|
||||
await update.message.reply_text("⏱️ **Solicitud de Permiso**\n\n¿Para qué día y horario lo necesitas?")
|
||||
return FECHAS
|
||||
|
||||
async def recibir_fechas(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
context.user_data['fechas'] = update.message.text
|
||||
await update.message.reply_text("Entendido. ¿Cuál es el motivo o comentario adicional?")
|
||||
return MOTIVO
|
||||
|
||||
async def recibir_motivo_fin(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
motivo = update.message.text
|
||||
datos = context.user_data
|
||||
user = update.effective_user
|
||||
|
||||
# Payload para n8n
|
||||
payload = {
|
||||
"solicitante": user.full_name,
|
||||
"id_telegram": user.id,
|
||||
"tipo_solicitud": datos['tipo'],
|
||||
"fechas": datos['fechas'],
|
||||
"motivo": motivo
|
||||
}
|
||||
|
||||
webhook = os.getenv("WEBHOOK_VACACIONES")
|
||||
try:
|
||||
requests.post(webhook, json=payload)
|
||||
await update.message.reply_text(f"✅ Solicitud de *{datos['tipo']}* enviada a tu Manager.")
|
||||
except:
|
||||
await update.message.reply_text("⚠️ Error enviando la solicitud.")
|
||||
|
||||
return ConversationHandler.END
|
||||
|
||||
async def cancelar(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
await update.message.reply_text("Solicitud cancelada.")
|
||||
return ConversationHandler.END
|
||||
|
||||
# Handlers separados pero comparten lógica
|
||||
vacaciones_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler("vacaciones", start_vacaciones)],
|
||||
states={FECHAS: [MessageHandler(filters.TEXT, recibir_fechas)], MOTIVO: [MessageHandler(filters.TEXT, recibir_motivo_fin)]},
|
||||
fallbacks=[CommandHandler("cancelar", cancelar)]
|
||||
)
|
||||
|
||||
permiso_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler("permiso", start_permiso)],
|
||||
states={FECHAS: [MessageHandler(filters.TEXT, recibir_fechas)], MOTIVO: [MessageHandler(filters.TEXT, recibir_motivo_fin)]},
|
||||
fallbacks=[CommandHandler("cancelar", cancelar)]
|
||||
)
|
||||
Reference in New Issue
Block a user