mirror of
https://github.com/marcogll/telegram_new_socias.git
synced 2026-01-13 13:15:16 +00:00
feat: Implement direct MySQL database integration for onboarding and duplicate checks, add Gemini AI support, and update webhook and email configurations.
This commit is contained in:
59
.env.example
59
.env.example
@@ -1,49 +1,40 @@
|
|||||||
# Configuración de Telegram
|
# Configuración de Telegram
|
||||||
TELEGRAM_TOKEN=TU_TOKEN_NUEVO_AQUI
|
TELEGRAM_TOKEN=TU_TOKEN_NUEVO_AQUI
|
||||||
TELEGRAM_ADMIN_CHAT_ID=TELEGRAM_ADMIN_CHAT_ID
|
TELEGRAM_ADMIN_CHAT_ID=TELEGRAM_ADMIN_CHAT_ID
|
||||||
OPENAI_API_KEY=SK......
|
OPENAI_API_KEY=sk-proj-xxxx
|
||||||
GOOGLE_API_KEY=AIzaSyBqH5...
|
GOOGLE_API_KEY=AIzaSyBqH5... # Usado para Gemini AI en modules/ai.py
|
||||||
|
|
||||||
# URL de la hoja de cálculo de Google para verificar duplicados
|
# ===============================
|
||||||
GOOGLE_SHEET_URL=https://docs.google.com/spreadsheets/d/1iVHnNoAF4sVVhb2kcclthznYFUKetmhsM6b2ZUCXd-0/edit?gid=370216950#gid=370216950
|
# WEBHOOKS
|
||||||
|
# ===============================
|
||||||
# Opcional: Credenciales de Google como variables de entorno
|
|
||||||
# Si estas variables están definidas, se usarán en lugar del archivo JSON.
|
|
||||||
# Asegúrate de escapar correctamente el valor de GSA_PRIVATE_KEY (ej. reemplazando saltos de línea con \n)
|
|
||||||
GSA_TYPE=service_account
|
|
||||||
GSA_PROJECT_ID=
|
|
||||||
GSA_PRIVATE_KEY_ID=
|
|
||||||
GSA_PRIVATE_KEY=
|
|
||||||
GSA_CLIENT_EMAIL=
|
|
||||||
GSA_CLIENT_ID=
|
|
||||||
GSA_AUTH_URI=
|
|
||||||
GSA_TOKEN_URI=
|
|
||||||
GSA_AUTH_PROVIDER_X509_CERT_URL=
|
|
||||||
GSA_CLIENT_X509_CERT_URL=
|
|
||||||
|
|
||||||
|
|
||||||
# Webhooks de n8n (puedes agregar más aquí en el futuro)
|
|
||||||
# Usa WEBHOOK_ONBOARDING (o el alias WEBHOOK_CONTRATO si ya lo tienes así)
|
|
||||||
WEBHOOK_ONBOARDING=url
|
WEBHOOK_ONBOARDING=url
|
||||||
# WEBHOOK_CONTRATO=url
|
|
||||||
WEBHOOK_PRINT=url
|
|
||||||
WEBHOOK_VACACIONES=url
|
WEBHOOK_VACACIONES=url
|
||||||
WEBHOOK_PERMISOS=url
|
WEBHOOK_PERMISOS=url
|
||||||
|
WEBHOOK_PRINTS=url
|
||||||
|
WEBHOOK_SCHEDULE=url
|
||||||
|
|
||||||
# --- DATABASE ---
|
# ===============================
|
||||||
# Usado por el servicio de la base de datos en docker-compose.yml
|
# DATABASE SETUP
|
||||||
MYSQL_DATABASE_USERS_ALMA=USERS_ALMA
|
# ===============================
|
||||||
MYSQL_DATABASE_VANITY_HR=vanity_hr
|
MYSQL_HOST=db
|
||||||
MYSQL_DATABASE_VANITY_ATTENDANCE=vanity_attendance
|
|
||||||
MYSQL_USER=user
|
MYSQL_USER=user
|
||||||
MYSQL_PASSWORD=password
|
MYSQL_PASSWORD=password
|
||||||
MYSQL_ROOT_PASSWORD=rootpassword
|
MYSQL_ROOT_PASSWORD=rootpassword
|
||||||
|
|
||||||
# --- SMTP ---
|
# Database Names
|
||||||
# Usado por el módulo de impresión para enviar correos
|
MYSQL_DATABASE_USERS_ALMA=USERS_ALMA
|
||||||
|
MYSQL_DATABASE_VANITY_HR=vanity_hr
|
||||||
|
MYSQL_DATABASE_VANITY_ATTENDANCE=vanity_attendance
|
||||||
|
|
||||||
|
# ===============================
|
||||||
|
# EMAIL SETUP
|
||||||
|
# ===============================
|
||||||
SMTP_SERVER=smtp.hostinger.com
|
SMTP_SERVER=smtp.hostinger.com
|
||||||
SMTP_PORT=465
|
SMTP_PORT=587
|
||||||
SMTP_USER=your_email@example.com
|
SMTP_USER=your_email@example.com
|
||||||
SMTP_PASSWORD=your_password
|
SMTP_PASSWORD=your_password
|
||||||
SMTP_RECIPIENT=your_email@example.com # También se acepta PRINTER_EMAIL como alias
|
IMAP_SERVER=imap.hostinger.com
|
||||||
GOOGLE_CREDENTIALS_FILE=google_credentials.json
|
IMAP_PORT=993
|
||||||
|
IMAP_USER=your_email@example.com
|
||||||
|
IMAP_PASSWORD=your_password
|
||||||
|
PRINTER_EMAIL=your_printer_email@example.com
|
||||||
|
|||||||
125
Readme.md
125
Readme.md
@@ -1,6 +1,6 @@
|
|||||||
# 🤖 Vanessa Bot – Asistente de RH para Vanity
|
# 🤖 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 y solicitudes de RH, todo orquestado desde Telegram y conectado a flujos de n8n o servicios de correo.
|
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 y solicitudes de RH, todo orquestado desde Telegram y conectado a flujos de n8n, servicios de correo y bases de datos MySQL.
|
||||||
|
|
||||||
Este repositorio está pensado como **proyecto Python profesional**, modular y listo para correr 24/7 en producción.
|
Este repositorio está pensado como **proyecto Python profesional**, modular y listo para correr 24/7 en producción.
|
||||||
|
|
||||||
@@ -10,11 +10,11 @@ Este repositorio está pensado como **proyecto Python profesional**, modular y l
|
|||||||
|
|
||||||
Vanessa no es un chatbot genérico: es una interfaz conversacional para procesos reales de negocio.
|
Vanessa no es un chatbot genérico: es una interfaz conversacional para procesos reales de negocio.
|
||||||
|
|
||||||
- Onboarding completo de nuevas socias (`/welcome`)
|
- **Onboarding completo de nuevas socias (`/welcome`)**: Recolecta datos, valida que no existan duplicados en la DB, registra a la usuaria en `USERS_ALMA` y envía los datos a n8n.
|
||||||
- Solicitud de vacaciones (`/vacaciones`)
|
- **Solicitud de vacaciones (`/vacaciones`)**: Flujo dinámico para gestionar días de descanso.
|
||||||
- Solicitud de permisos por horas (`/permiso`)
|
- **Solicitud de permisos por horas (`/permiso`)**: Incluye clasificación de motivos mediante IA (Gemini).
|
||||||
|
|
||||||
Cada flujo es un módulo independiente, y los datos se envían a **webhooks de n8n**.
|
Cada flujo es un módulo independiente que interactúa con la base de datos y flujos de **n8n**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -31,124 +31,91 @@ vanity_bot/
|
|||||||
├── docker-compose.yml # Orquestación de servicios (bot + db)
|
├── docker-compose.yml # Orquestación de servicios (bot + db)
|
||||||
├── README.md # Este documento
|
├── README.md # Este documento
|
||||||
│
|
│
|
||||||
|
├── models/ # Modelos de base de datos (SQLAlchemy)
|
||||||
|
│ ├── users_alma_models.py
|
||||||
|
│ ├── vanity_hr_models.py
|
||||||
|
│ └── vanity_attendance_models.py
|
||||||
|
│
|
||||||
└── modules/ # Habilidades del bot
|
└── modules/ # Habilidades del bot
|
||||||
├── __init__.py
|
├── ai.py # Clasificación de motivos con Gemini
|
||||||
├── database.py # Módulo de conexión a la base de datos
|
├── database.py # Conexión a DB y lógica de negocio (registro/verificación)
|
||||||
├── onboarding.py # Flujo /welcome (onboarding RH)
|
├── logger.py # Registro de auditoría
|
||||||
└── rh_requests.py # /vacaciones y /permiso
|
├── onboarding.py # Flujo /welcome
|
||||||
|
├── rh_requests.py # /vacaciones y /permiso
|
||||||
|
└── ui.py # Teclados y componentes de interfaz
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔐 Configuración (.env)
|
## 🔐 Configuración (.env)
|
||||||
|
|
||||||
Copia el archivo `.env.example` a `.env` y rellena los valores correspondientes. Este archivo es ignorado por Git para proteger tus credenciales.
|
Vanessa utiliza múltiples bases de datos y webhooks. Asegúrate de configurar correctamente los nombres de las bases de datos.
|
||||||
|
|
||||||
```
|
```ini
|
||||||
# --- TELEGRAM ---
|
# --- TELEGRAM ---
|
||||||
TELEGRAM_TOKEN=TU_TOKEN_AQUI
|
TELEGRAM_TOKEN=TU_TOKEN_AQUI
|
||||||
|
|
||||||
|
# --- AI (Gemini) ---
|
||||||
|
GOOGLE_API_KEY=AIzaSy...
|
||||||
|
|
||||||
# --- WEBHOOKS N8N ---
|
# --- WEBHOOKS N8N ---
|
||||||
WEBHOOK_ONBOARDING=https://... # Alias aceptado: WEBHOOK_CONTRATO
|
WEBHOOK_ONBOARDING=https://...
|
||||||
WEBHOOK_VACACIONES=https://...
|
WEBHOOK_VACACIONES=https://...
|
||||||
WEBHOOK_PERMISOS=https://...
|
WEBHOOK_PERMISOS=https://...
|
||||||
|
|
||||||
# --- DATABASE ---
|
# --- DATABASE SETUP ---
|
||||||
# Usado por el servicio de la base de datos en docker-compose.yml
|
MYSQL_HOST=db
|
||||||
MYSQL_DATABASE=vanessa_logs
|
|
||||||
MYSQL_USER=user
|
MYSQL_USER=user
|
||||||
MYSQL_PASSWORD=password
|
MYSQL_PASSWORD=password
|
||||||
MYSQL_ROOT_PASSWORD=rootpassword
|
MYSQL_ROOT_PASSWORD=rootpassword
|
||||||
|
|
||||||
|
# Nombres de las Bases de Datos
|
||||||
|
MYSQL_DATABASE_USERS_ALMA=USERS_ALMA
|
||||||
|
MYSQL_DATABASE_VANITY_HR=vanity_hr
|
||||||
|
MYSQL_DATABASE_VANITY_ATTENDANCE=vanity_attendance
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🐳 Ejecución con Docker (Recomendado)
|
## 🐳 Ejecución con Docker (Recomendado)
|
||||||
|
|
||||||
El proyecto está dockerizado para facilitar su despliegue.
|
El proyecto está dockerizado para facilitar su despliegue y aislamiento.
|
||||||
|
|
||||||
### 1. Pre-requisitos
|
### 1. Pre-requisitos
|
||||||
- Docker
|
- Docker y Docker Compose instalaros.
|
||||||
- Docker Compose
|
|
||||||
|
|
||||||
### 2. Levantar los servicios
|
### 2. Levantar los servicios
|
||||||
Con el archivo `.env` ya configurado, simplemente ejecuta:
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up --build
|
docker-compose up --build -d
|
||||||
```
|
```
|
||||||
Este comando construirá la imagen del bot, descargará la imagen de MySQL, y lanzará ambos servicios. `docker-compose` leerá las variables del archivo `.env` para configurar los contenedores.
|
Este comando levantará el bot y un contenedor de MySQL (si se usa el compose por defecto). El bot se reconectará automáticamente a la DB si esta tarda en iniciar.
|
||||||
|
|
||||||
### 3. Detener los servicios
|
|
||||||
Para detener los contenedores, presiona `Ctrl+C` en la terminal donde se están ejecutando, o ejecuta desde otro terminal:
|
|
||||||
```bash
|
|
||||||
docker-compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Despliegue con imagen pre-construida (Collify)
|
|
||||||
Si Collify solo consume imágenes ya publicadas, usa el archivo `docker-compose.collify.yml` que apunta a una imagen en registro (`DOCKER_IMAGE`).
|
|
||||||
|
|
||||||
1) Construir y publicar la imagen (ejemplo con Buildx y tag con timestamp):
|
|
||||||
```bash
|
|
||||||
export DOCKER_IMAGE=marcogll/vanessa-bot:prod-$(date +%Y%m%d%H%M)
|
|
||||||
docker buildx build --platform linux/amd64 -t $DOCKER_IMAGE . --push
|
|
||||||
```
|
|
||||||
|
|
||||||
2) Desplegar en el servidor (Collify) usando la imagen publicada:
|
|
||||||
```bash
|
|
||||||
export DOCKER_IMAGE=marcogll/vanessa-bot:prod-20240101
|
|
||||||
docker compose -f docker-compose.collify.yml pull
|
|
||||||
docker compose -f docker-compose.collify.yml up -d
|
|
||||||
```
|
|
||||||
`docker-compose.collify.yml` usa `env_file: .env`, así que carga las credenciales igual que en local o configúralas como variables de entorno en la plataforma.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧩 Arquitectura Interna
|
## 🧩 Arquitectura Interna
|
||||||
|
|
||||||
### main.py (El Cerebro)
|
### main.py (El Cerebro)
|
||||||
- Inicializa el bot de Telegram
|
- Inicializa el bot de Telegram y carga variables de entorno.
|
||||||
- Carga variables de entorno
|
- Registra los handlers de cada módulo y define el menú principal y comandos persistentes.
|
||||||
- Registra los handlers de cada módulo
|
|
||||||
- Define el menú principal (`/start`, `/help`)
|
|
||||||
|
|
||||||
### modules/database.py
|
### modules/database.py
|
||||||
- Gestiona la conexión a la base de datos MySQL con SQLAlchemy.
|
- Centraliza la conexión a las 3 bases de datos (`USERS_ALMA`, `vanity_hr`, `vanity_attendance`).
|
||||||
- Define el modelo `RequestLog` para la tabla de logs.
|
- **Verificación de duplicados**: Ya no usa Google Sheets; ahora verifica el `telegram_id` directamente en la tabla `users`.
|
||||||
- Provee la función `log_request` para registrar interacciones.
|
- **Registro de usuarias**: Función `register_user` para insertar candidatas tras el onboarding.
|
||||||
|
|
||||||
### modules/onboarding.py
|
### modules/onboarding.py
|
||||||
Flujo conversacional complejo que recolecta datos de nuevas empleadas y los envía a un webhook de n8n.
|
Recolección exhaustiva de datos. Al finalizar:
|
||||||
Incluye derivadas útiles: `num_ext_texto` (número en letras, con interior) y `numero_empleado` (primeras 4 del CURP + fecha de ingreso).
|
1. Valida y formatea datos (RFC, CURP, fechas).
|
||||||
|
2. Registra a la empleada en la base de datos MySQL.
|
||||||
|
3. Envía el payload completo al webhook de n8n para generación de contratos.
|
||||||
|
|
||||||
### modules/rh_requests.py
|
### modules/ai.py & modules/rh_requests.py
|
||||||
- Maneja solicitudes simples de RH (Vacaciones y Permisos) y las envía a un webhook de n8n.
|
Integración con **Google Gemini** para clasificar automáticamente los motivos de los permisos (Médico, Trámite, etc.) y envío sincronizado a webhooks de gestión humana.
|
||||||
- Vacaciones: pregunta año (actual o siguiente), día/mes de inicio y fin, calcula métricas y aplica semáforo automático.
|
|
||||||
- Permisos: ofrece accesos rápidos (hoy/mañana/pasado) o fecha específica (año actual/siguiente, día/mes), pide horario, clasifica motivo con IA y envía al webhook.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧠 Filosofía del Proyecto
|
|
||||||
|
|
||||||
- **Telegram como UI**: Interfaz conversacional accesible para todos.
|
|
||||||
- **Python como cerebro**: Lógica de negocio y orquestación.
|
|
||||||
- **Docker para despliegue**: Entornos consistentes y portátiles.
|
|
||||||
- **MySQL para persistencia**: Registro auditable de todas las interacciones.
|
|
||||||
- **Modularidad total**: Cada habilidad es un componente independiente.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Estado del Proyecto
|
|
||||||
|
|
||||||
✔ Funcional en producción
|
|
||||||
✔ Modular
|
|
||||||
✔ Escalable
|
|
||||||
✔ Auditable
|
|
||||||
|
|
||||||
Vanessa está viva. Y aprende con cada flujo nuevo.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🗒️ Registro de versiones
|
## 🗒️ Registro de versiones
|
||||||
|
|
||||||
- **1.2 (2025-01-25)** — Onboarding: selector de año 2020–2026; `numero_empleado` incluye prefijo CURP (4 chars) + fecha de ingreso; vacaciones/permiso ajustan fin automático al siguiente año cuando aplica.
|
- **1.3 (2025-12-18)** — **Adiós Google Sheets**: Migración total a base de datos MySQL para verificación de existencia y registro de nuevas socias. Limpieza de `.env` y optimización de arquitectura de modelos.
|
||||||
|
- **1.2 (2025-01-25)** — Onboarding: selector de año 2020–2026; `numero_empleado` dinámico; mejoras en flujos de vacaciones/permiso.
|
||||||
|
- **1.1** — Implementación inicial de webhooks y Docker.
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from models.users_alma_models import Base as BaseUsersAlma, User
|
from models.users_alma_models import Base as BaseUsersAlma, User
|
||||||
from models.vanity_hr_models import Base as BaseVanityHr, DataEmpleadas, Vacaciones, Permisos
|
from models.vanity_hr_models import Base as BaseVanityHr, DataEmpleadas, Vacaciones, Permisos
|
||||||
from models.vanity_attendance_models import Base as BaseVanityAttendance, AsistenciaRegistros, HorarioEmpleadas
|
from models.vanity_attendance_models import Base as BaseVanityAttendance, AsistenciaRegistros, HorarioEmpleadas
|
||||||
import gspread
|
|
||||||
from google.oauth2.service_account import Credentials
|
|
||||||
|
|
||||||
# --- DATABASE (MySQL) SETUP ---
|
# --- DATABASE (MySQL) SETUP ---
|
||||||
def _build_engine(db_name_env_var):
|
def _build_engine(db_name_env_var):
|
||||||
@@ -36,69 +35,50 @@ SessionUsersAlma = sessionmaker(autocommit=False, autoflush=False, bind=engine_u
|
|||||||
SessionVanityHr = sessionmaker(autocommit=False, autoflush=False, bind=engine_vanity_hr) if engine_vanity_hr else None
|
SessionVanityHr = sessionmaker(autocommit=False, autoflush=False, bind=engine_vanity_hr) if engine_vanity_hr else None
|
||||||
SessionVanityAttendance = sessionmaker(autocommit=False, autoflush=False, bind=engine_vanity_attendance) if engine_vanity_attendance else None
|
SessionVanityAttendance = sessionmaker(autocommit=False, autoflush=False, bind=engine_vanity_attendance) if engine_vanity_attendance else None
|
||||||
|
|
||||||
# --- GOOGLE SHEETS SETUP ---
|
# --- GOOGLE SHEETS SETUP (REMOVED) ---
|
||||||
GSHEET_URL = os.getenv("GOOGLE_SHEET_URL")
|
# Duplicate checking is now done via database.
|
||||||
GOOGLE_CREDENTIALS_FILE = os.getenv("GOOGLE_CREDENTIALS_FILE", "google_credentials.json")
|
|
||||||
SHEET_COLUMN_INDEX = 40 # AN is the 40th column
|
|
||||||
|
|
||||||
def get_gsheet_client():
|
|
||||||
"""Returns an authenticated gspread client or None if it fails."""
|
|
||||||
if not GSHEET_URL:
|
|
||||||
logging.warning("GOOGLE_SHEET_URL is not configured. Duplicate checking is disabled.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
creds = None
|
|
||||||
scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"]
|
|
||||||
|
|
||||||
gsa_creds_dict = {
|
|
||||||
"type": os.getenv("GSA_TYPE"),
|
|
||||||
"project_id": os.getenv("GSA_PROJECT_ID"),
|
|
||||||
"private_key_id": os.getenv("GSA_PRIVATE_KEY_ID"),
|
|
||||||
"private_key": (os.getenv("GSA_PRIVATE_KEY") or "").replace("\\n", "\n"),
|
|
||||||
"client_email": os.getenv("GSA_CLIENT_EMAIL"),
|
|
||||||
"client_id": os.getenv("GSA_CLIENT_ID"),
|
|
||||||
"auth_uri": os.getenv("GSA_AUTH_URI"),
|
|
||||||
"token_uri": os.getenv("GSA_TOKEN_URI"),
|
|
||||||
"auth_provider_x509_cert_url": os.getenv("GSA_AUTH_PROVIDER_X509_CERT_URL"),
|
|
||||||
"client_x509_cert_url": os.getenv("GSA_CLIENT_X509_CERT_URL"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if all(gsa_creds_dict.values()):
|
|
||||||
try:
|
|
||||||
creds = Credentials.from_service_account_info(gsa_creds_dict, scopes=scopes)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Error processing Google credentials from environment: {e}")
|
|
||||||
return None
|
|
||||||
elif os.path.exists(GOOGLE_CREDENTIALS_FILE):
|
|
||||||
try:
|
|
||||||
creds = Credentials.from_service_account_file(GOOGLE_CREDENTIALS_FILE, scopes=scopes)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Error processing credentials file '{GOOGLE_CREDENTIALS_FILE}': {e}")
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
logging.warning("Google credentials not found (neither environment variables nor file). Duplicate checking is disabled.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
return gspread.authorize(creds)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Error authorizing gspread client: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def chat_id_exists(chat_id: int) -> bool:
|
def chat_id_exists(chat_id: int) -> bool:
|
||||||
"""Checks if a Telegram chat_id already exists in the Google Sheet."""
|
"""Checks if a Telegram chat_id already exists in the USERS_ALMA.users table."""
|
||||||
client = get_gsheet_client()
|
if not SessionUsersAlma:
|
||||||
if not client:
|
logging.warning("SessionUsersAlma not initialized. Cannot check if chat_id exists.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
session = SessionUsersAlma()
|
||||||
try:
|
try:
|
||||||
spreadsheet = client.open_by_url(GSHEET_URL)
|
exists = session.query(User).filter(User.telegram_id == str(chat_id)).first() is not None
|
||||||
worksheet = spreadsheet.get_worksheet(0)
|
return exists
|
||||||
chat_ids_in_sheet = worksheet.col_values(SHEET_COLUMN_INDEX)
|
|
||||||
return str(chat_id) in chat_ids_in_sheet
|
|
||||||
except gspread.exceptions.SpreadsheetNotFound:
|
|
||||||
logging.error("Could not find the spreadsheet at the provided URL.")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error reading the spreadsheet: {e}")
|
logging.error(f"Error checking if chat_id exists in DB: {e}")
|
||||||
return False
|
return False
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
def register_user(user_data: dict) -> bool:
|
||||||
|
"""Registers a new user in the USERS_ALMA.users table."""
|
||||||
|
if not SessionUsersAlma:
|
||||||
|
logging.warning("SessionUsersAlma not initialized. Cannot register user.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
session = SessionUsersAlma()
|
||||||
|
try:
|
||||||
|
new_user = User(
|
||||||
|
telegram_id=str(user_data.get("chat_id")),
|
||||||
|
username=user_data.get("telegram_user"),
|
||||||
|
first_name=user_data.get("first_name"),
|
||||||
|
last_name=f"{user_data.get('apellido_paterno', '')} {user_data.get('apellido_materno', '')}".strip(),
|
||||||
|
email=user_data.get("email"),
|
||||||
|
cell_phone=user_data.get("celular"),
|
||||||
|
role='user' # Default role
|
||||||
|
)
|
||||||
|
session.add(new_user)
|
||||||
|
session.commit()
|
||||||
|
logging.info(f"User {user_data.get('chat_id')} registered successfully in DB.")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
session.rollback()
|
||||||
|
logging.error(f"Error registering user in DB: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from telegram.ext import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from modules.logger import log_request
|
from modules.logger import log_request
|
||||||
from modules.database import chat_id_exists
|
from modules.database import chat_id_exists, register_user
|
||||||
from modules.ui import main_actions_keyboard
|
from modules.ui import main_actions_keyboard
|
||||||
|
|
||||||
# --- 1. CARGA DE ENTORNO ---
|
# --- 1. CARGA DE ENTORNO ---
|
||||||
@@ -404,6 +404,19 @@ async def finalizar(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error enviando webhook a {url}: {e}")
|
logging.error(f"Error enviando webhook a {url}: {e}")
|
||||||
|
|
||||||
|
# --- REGISTRO EN BASE DE DATOS ---
|
||||||
|
db_ok = register_user({
|
||||||
|
**meta,
|
||||||
|
**payload["metadata"],
|
||||||
|
**payload["candidato"],
|
||||||
|
**payload["contacto"]
|
||||||
|
})
|
||||||
|
|
||||||
|
if db_ok:
|
||||||
|
logging.info(f"Usuario {meta['chat_id']} registrado en la base de datos.")
|
||||||
|
else:
|
||||||
|
logging.error(f"Fallo al registrar usuario {meta['chat_id']} en la base de datos.")
|
||||||
|
|
||||||
if enviado:
|
if enviado:
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"✅ *¡Registro Exitoso!*\n\n"
|
"✅ *¡Registro Exitoso!*\n\n"
|
||||||
|
|||||||
@@ -5,5 +5,3 @@ SQLAlchemy
|
|||||||
mysql-connector-python
|
mysql-connector-python
|
||||||
google-generativeai
|
google-generativeai
|
||||||
openai
|
openai
|
||||||
gspread
|
|
||||||
google-auth-oauthlib
|
|
||||||
Reference in New Issue
Block a user