mirror of
https://github.com/marcogll/telegram_new_socias.git
synced 2026-01-13 13:15:16 +00:00
feat: Dockerize application and add MySQL logging
This commit introduces Docker and Docker Compose to containerize the application and orchestrate it with a MySQL database. Key changes include: - Added a `Dockerfile` to create a container for the Python bot. - Created a `docker-compose.yml` file to manage the bot and MySQL services. - Added a `modules/database.py` module to handle database connections and logging with SQLAlchemy. - Integrated request logging into all command handlers. - Updated `requirements.txt` with necessary dependencies for MySQL. - Updated `.env` and `.gitignore` to manage database credentials securely. - Updated `Readme.md` with instructions on how to run the application using Docker Compose.
This commit is contained in:
6
.env
6
.env
@@ -3,4 +3,8 @@ 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)
|
||||
WEBHOOK_PRINT=
|
||||
WEBHOOK_VACACIONES=
|
||||
|
||||
# --- DATABASE ---
|
||||
DATABASE_URL=mysql+mysqlconnector://user:password@db:3306/vanessa_logs
|
||||
|
||||
93
.gitignore
vendored
93
.gitignore
vendored
@@ -1,61 +1,46 @@
|
||||
# =========================
|
||||
# Secrets & Environment
|
||||
# =========================
|
||||
# Environments
|
||||
.env
|
||||
.env.*
|
||||
*.env
|
||||
|
||||
# =========================
|
||||
# Python
|
||||
# =========================
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.pyo
|
||||
*.pyd
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
.venv/
|
||||
env/
|
||||
ENV/
|
||||
|
||||
# =========================
|
||||
# Logs & Runtime
|
||||
# =========================
|
||||
*.log
|
||||
logs/
|
||||
*.sqlite
|
||||
*.db
|
||||
|
||||
# =========================
|
||||
# OS & Editors
|
||||
# =========================
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
# JetBrains
|
||||
.idea/
|
||||
|
||||
# =========================
|
||||
# Build / Distribution
|
||||
# =========================
|
||||
# Byte-compiled / optimized / C extensions
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
*.egg-info/
|
||||
|
||||
# =========================
|
||||
# Systemd / Deploy Artifacts
|
||||
# =========================
|
||||
*.service
|
||||
*.pid
|
||||
|
||||
# =========================
|
||||
# Temp / Downloads
|
||||
# =========================
|
||||
tmp/
|
||||
temp/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
*.log
|
||||
.pytest_cache/
|
||||
.hypothesis/
|
||||
|
||||
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
# Usar una imagen base de Python
|
||||
FROM python:3.9-slim
|
||||
|
||||
# Establecer el directorio de trabajo
|
||||
WORKDIR /app
|
||||
|
||||
# Copiar los archivos de requisitos e instalar dependencias
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copiar el resto del código de la aplicación
|
||||
COPY . .
|
||||
|
||||
# Comando para ejecutar la aplicación
|
||||
CMD ["python", "main.py"]
|
||||
91
Readme.md
91
Readme.md
@@ -27,10 +27,13 @@ vanity_bot/
|
||||
├── .env # Variables sensibles (tokens, URLs)
|
||||
├── main.py # Cerebro principal del bot
|
||||
├── requirements.txt # Dependencias
|
||||
├── Dockerfile # Definición del contenedor del bot
|
||||
├── docker-compose.yml # Orquestación de servicios (bot + db)
|
||||
├── README.md # Este documento
|
||||
│
|
||||
└── modules/ # Habilidades del bot
|
||||
├── __init__.py
|
||||
├── database.py # Módulo de conexión a la base de datos
|
||||
├── onboarding.py # Flujo /welcome (onboarding RH)
|
||||
├── printer.py # Flujo /print (impresión)
|
||||
└── rh_requests.py # /vacaciones y /permiso
|
||||
@@ -50,13 +53,40 @@ TELEGRAM_TOKEN=TU_TOKEN_AQUI
|
||||
WEBHOOK_ONBOARDING=https://flows.soul23.cloud/webhook/contrato
|
||||
WEBHOOK_PRINT=https://flows.soul23.cloud/webhook/impresion
|
||||
WEBHOOK_VACACIONES=https://flows.soul23.cloud/webhook/vacaciones
|
||||
|
||||
# --- DATABASE ---
|
||||
# Esta URL es para la conexión interna de Docker, no la modifiques si usas Docker Compose.
|
||||
DATABASE_URL=mysql+mysqlconnector://user:password@db:3306/vanessa_logs
|
||||
```
|
||||
|
||||
Nunca subas este archivo al repositorio.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Instalación
|
||||
## 🐳 Ejecución con Docker (Recomendado)
|
||||
|
||||
El proyecto está dockerizado para facilitar su despliegue.
|
||||
|
||||
### 1. Pre-requisitos
|
||||
- Docker
|
||||
- Docker Compose
|
||||
|
||||
### 2. Levantar los servicios
|
||||
Con el archivo `.env` ya configurado, simplemente ejecuta:
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
Este comando construirá la imagen del bot, descargará la imagen de MySQL, creará los volúmenes y redes, y lanzará ambos servicios. El bot se conectará automáticamente a la base de datos para registrar los logs.
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Instalación Manual
|
||||
|
||||
Se recomienda usar un entorno virtual.
|
||||
|
||||
@@ -79,82 +109,37 @@ Si el token es válido, verás:
|
||||
```
|
||||
🧠 Vanessa Brain iniciada y escuchando...
|
||||
```
|
||||
**Nota**: Para que la ejecución manual funcione, necesitarás tener una base de datos MySQL corriendo localmente y accesible en la URL especificada en `DATABASE_URL` dentro de tu archivo `.env`.
|
||||
|
||||
---
|
||||
|
||||
## 🧩 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/database.py
|
||||
- Gestiona la conexión a la base de datos MySQL con SQLAlchemy.
|
||||
- Define el modelo `RequestLog` para la tabla de logs.
|
||||
- Provee la función `log_request` para registrar interacciones.
|
||||
|
||||
### 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
|
||||
```
|
||||
- Maneja solicitudes simples de RH: Vacaciones y Permisos por horas.
|
||||
|
||||
---
|
||||
|
||||
@@ -163,6 +148,8 @@ sudo systemctl start vanessa
|
||||
- Telegram como UI
|
||||
- Python como cerebro
|
||||
- n8n como sistema nervioso
|
||||
- Docker para despliegue
|
||||
- MySQL para persistencia de logs
|
||||
- Datos estructurados, no mensajes sueltos
|
||||
- Modularidad total: cada habilidad se enchufa o se quita
|
||||
|
||||
|
||||
32
docker-compose.yml
Normal file
32
docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
bot:
|
||||
build: .
|
||||
container_name: vanessa_bot
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- DATABASE_URL=mysql+mysqlconnector://user:password@db:3306/vanessa_logs
|
||||
depends_on:
|
||||
- db
|
||||
volumes:
|
||||
- .:/app
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
container_name: vanessa_db
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_DATABASE: vanessa_logs
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
3
main.py
3
main.py
@@ -9,6 +9,7 @@ from telegram.ext import Application, Defaults, CommandHandler, ContextTypes
|
||||
from modules.onboarding import onboarding_handler
|
||||
from modules.printer import print_handler
|
||||
from modules.rh_requests import vacaciones_handler, permiso_handler
|
||||
from modules.database import log_request
|
||||
# from modules.finder import finder_handler (Si lo creas después)
|
||||
|
||||
load_dotenv()
|
||||
@@ -18,6 +19,8 @@ logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s
|
||||
|
||||
async def menu_principal(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Muestra el menú de opciones de Vanessa"""
|
||||
user = update.effective_user
|
||||
log_request(user.id, user.username, "start", update.message.text)
|
||||
texto = (
|
||||
"👩💼 **Hola, soy Vanessa. ¿En qué puedo ayudarte hoy?**\n\n"
|
||||
"📝 `/welcome` - Iniciar onboarding/contrato\n"
|
||||
|
||||
64
modules/database.py
Normal file
64
modules/database.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import os
|
||||
from sqlalchemy import create_engine, Column, Integer, String, DateTime, MetaData, Table
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
# Configuración de logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
# Obtener la URL de la base de datos desde las variables de entorno
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "mysql+mysqlconnector://user:password@db:3306/vanessa_logs")
|
||||
|
||||
# Crear el motor de la base de datos
|
||||
engine = create_engine(DATABASE_URL)
|
||||
metadata = MetaData()
|
||||
|
||||
# Base para los modelos declarativos
|
||||
Base = declarative_base()
|
||||
|
||||
# Clase que mapea a la tabla de logs
|
||||
class RequestLog(Base):
|
||||
__tablename__ = 'request_logs'
|
||||
id = Column(Integer, primary_key=True)
|
||||
telegram_id = Column(String(50))
|
||||
username = Column(String(100))
|
||||
command = Column(String(100))
|
||||
message = Column(String(500))
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Función para inicializar la base de datos
|
||||
def init_db():
|
||||
try:
|
||||
logging.info("Inicializando la base de datos y creando tablas si no existen...")
|
||||
Base.metadata.create_all(bind=engine)
|
||||
logging.info("Tablas verificadas/creadas correctamente.")
|
||||
except Exception as e:
|
||||
logging.error(f"Error al inicializar la base de datos: {e}")
|
||||
raise
|
||||
|
||||
# Crear una sesión para interactuar con la base de datos
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# Función para registrar una solicitud en la base de datos
|
||||
def log_request(telegram_id, username, command, message):
|
||||
db_session = SessionLocal()
|
||||
try:
|
||||
log_entry = RequestLog(
|
||||
telegram_id=str(telegram_id),
|
||||
username=username,
|
||||
command=command,
|
||||
message=message
|
||||
)
|
||||
db_session.add(log_entry)
|
||||
db_session.commit()
|
||||
logging.info(f"Log guardado: {command} de {username}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error al guardar el log: {e}")
|
||||
db_session.rollback()
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
# Inicializar la base de datos al arrancar el módulo
|
||||
init_db()
|
||||
@@ -17,6 +17,8 @@ from telegram.ext import (
|
||||
Defaults,
|
||||
)
|
||||
|
||||
from modules.database import log_request
|
||||
|
||||
# --- 1. CARGA DE ENTORNO ---
|
||||
load_dotenv() # Carga las variables del archivo .env
|
||||
TOKEN = os.getenv("TELEGRAM_TOKEN")
|
||||
@@ -115,6 +117,7 @@ TECLADO_RELACION_EMERGENCIA = ReplyKeyboardMarkup(
|
||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
user = update.effective_user
|
||||
context.user_data.clear()
|
||||
log_request(user.id, user.username, "welcome", update.message.text)
|
||||
|
||||
context.user_data["metadata"] = {
|
||||
"telegram_id": user.id,
|
||||
|
||||
@@ -2,11 +2,14 @@ import os
|
||||
import requests
|
||||
from telegram import Update
|
||||
from telegram.ext import ContextTypes, ConversationHandler, CommandHandler, MessageHandler, filters
|
||||
from modules.database import log_request
|
||||
|
||||
# Estado
|
||||
ESPERANDO_ARCHIVO = 1
|
||||
|
||||
async def start_print(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
user = update.effective_user
|
||||
log_request(user.id, user.username, "print", update.message.text)
|
||||
await update.message.reply_text("🖨️ **Servicio de Impresión**\n\nPor favor, envíame el archivo (PDF, DOCX o Imagen) que deseas imprimir/enviar.")
|
||||
return ESPERANDO_ARCHIVO
|
||||
|
||||
|
||||
@@ -2,15 +2,20 @@ import os
|
||||
import requests
|
||||
from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||
from telegram.ext import ContextTypes, ConversationHandler, CommandHandler, MessageHandler, filters
|
||||
from modules.database import log_request
|
||||
|
||||
TIPO_SOLICITUD, FECHAS, MOTIVO = range(3)
|
||||
|
||||
async def start_vacaciones(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
user = update.effective_user
|
||||
log_request(user.id, user.username, "vacaciones", update.message.text)
|
||||
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:
|
||||
user = update.effective_user
|
||||
log_request(user.id, user.username, "permiso", update.message.text)
|
||||
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
|
||||
|
||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
python-telegram-bot
|
||||
python-dotenv
|
||||
requests
|
||||
SQLAlchemy
|
||||
mysql-connector-python
|
||||
Reference in New Issue
Block a user