feat: Add database health checks and host configuration, enhance the start command with a quick reply keyboard, and improve general text cleaning.

This commit is contained in:
Marco Gallegos
2025-12-14 15:33:47 -06:00
parent cf128960cb
commit cbfcee557a
6 changed files with 40 additions and 16 deletions

View File

@@ -6,11 +6,12 @@ GOOGLE_API_KEY=AIzaSyBqH5...
# Webhooks de n8n (puedes agregar más aquí en el futuro) # Webhooks de n8n (puedes agregar más aquí en el futuro)
WEBHOOK_CONTRATO=url # Usa WEBHOOK_ONBOARDING (o el alias WEBHOOK_CONTRATO si ya lo tienes así)
WEBHOOK_ONBOARDING=url
# WEBHOOK_CONTRATO=url
WEBHOOK_PRINT=url WEBHOOK_PRINT=url
WEBHOOK_VACACIONES=url WEBHOOK_VACACIONES=url
WEBHOOK_PERMISOS=url WEBHOOK_PERMISOS=url
WEBHOOK_DATA_EMPLEADO=url
# --- DATABASE --- # --- DATABASE ---
# Usado por el servicio de la base de datos en docker-compose.yml # Usado por el servicio de la base de datos en docker-compose.yml

View File

@@ -51,7 +51,7 @@ Copia el archivo `.env.example` a `.env` y rellena los valores correspondientes.
TELEGRAM_TOKEN=TU_TOKEN_AQUI TELEGRAM_TOKEN=TU_TOKEN_AQUI
# --- WEBHOOKS N8N --- # --- WEBHOOKS N8N ---
WEBHOOK_CONTRATO=https://... # También acepta WEBHOOK_ONBOARDING WEBHOOK_ONBOARDING=https://... # Alias aceptado: WEBHOOK_CONTRATO
WEBHOOK_PRINT=https://... WEBHOOK_PRINT=https://...
WEBHOOK_VACACIONES=https://... WEBHOOK_VACACIONES=https://...

View File

@@ -8,11 +8,13 @@ services:
env_file: env_file:
- .env - .env
environment: environment:
- MYSQL_HOST=db
- MYSQL_USER=${MYSQL_USER} - MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD} - MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE} - MYSQL_DATABASE=${MYSQL_DATABASE}
depends_on: depends_on:
- db db:
condition: service_healthy
volumes: volumes:
- .:/app - .:/app
@@ -25,6 +27,12 @@ services:
MYSQL_USER: ${MYSQL_USER} MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD} MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER} -p${MYSQL_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 6
start_period: 30s
volumes: volumes:
- mysql_data:/var/lib/mysql - mysql_data:/var/lib/mysql
ports: ports:

14
main.py
View File

@@ -5,7 +5,7 @@ from dotenv import load_dotenv
# Cargar variables de entorno antes de importar módulos que las usan # Cargar variables de entorno antes de importar módulos que las usan
load_dotenv() load_dotenv()
from telegram import Update from telegram import Update, ReplyKeyboardMarkup
from telegram.constants import ParseMode from telegram.constants import ParseMode
from telegram.ext import Application, Defaults, CommandHandler, ContextTypes from telegram.ext import Application, Defaults, CommandHandler, ContextTypes
@@ -27,13 +27,13 @@ async def menu_principal(update: Update, context: ContextTypes.DEFAULT_TYPE):
log_request(user.id, user.username, "start", update.message.text) log_request(user.id, user.username, "start", update.message.text)
texto = ( texto = (
"👩‍💼 **Hola, soy Vanessa. ¿En qué puedo ayudarte hoy?**\n\n" "👩‍💼 **Hola, soy Vanessa. ¿En qué puedo ayudarte hoy?**\n\n"
"📝 `/welcome` - Iniciar onboarding/contrato\n" "Toca un comando en azul para lanzarlo rápido:"
"🖨️ `/print` - Imprimir o enviar archivo\n"
"🌴 `/vacaciones` - Solicitar días libres\n"
"⏱️ `/permiso` - Solicitar permiso por horas\n\n"
"Selecciona un comando para empezar."
) )
await update.message.reply_text(texto) teclado = ReplyKeyboardMarkup(
[["/welcome", "/print"], ["/vacaciones", "/permiso"]],
resize_keyboard=True
)
await update.message.reply_text(texto, reply_markup=teclado)
def main(): def main():
# Configuración Global # Configuración Global

View File

@@ -39,6 +39,13 @@ def _build_engine():
logging.error(f"No se pudo crear el engine de base de datos: {exc}") logging.error(f"No se pudo crear el engine de base de datos: {exc}")
return None return None
def _disable_db_logging(reason: str):
"""Deshabilita el logging a DB después de un error para evitar spam."""
global engine, SessionLocal
engine = None
SessionLocal = None
logging.warning(f"DB logging deshabilitado: {reason}")
# Crear el engine y sesión si es posible # Crear el engine y sesión si es posible
engine = _build_engine() engine = _build_engine()
metadata = MetaData() if engine else None metadata = MetaData() if engine else None
@@ -46,6 +53,7 @@ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) if e
# Función para inicializar la base de datos # Función para inicializar la base de datos
def init_db(): def init_db():
global engine, SessionLocal
if not engine: if not engine:
return return
try: try:
@@ -54,6 +62,7 @@ def init_db():
logging.info("Tablas verificadas/creadas correctamente.") logging.info("Tablas verificadas/creadas correctamente.")
except Exception as e: except Exception as e:
logging.error(f"Error al inicializar la base de datos: {e}") logging.error(f"Error al inicializar la base de datos: {e}")
_disable_db_logging("no se pudo inicializar la base de datos (se omitirán logs).")
# No propagamos para que el bot pueda seguir levantando aunque no haya DB # No propagamos para que el bot pueda seguir levantando aunque no haya DB
# Función para registrar una solicitud en la base de datos # Función para registrar una solicitud en la base de datos
@@ -62,7 +71,12 @@ def log_request(telegram_id, username, command, message):
logging.debug("Log de DB omitido (DB no configurada).") logging.debug("Log de DB omitido (DB no configurada).")
return return
try:
db_session = SessionLocal() db_session = SessionLocal()
except Exception as exc:
logging.error(f"No se pudo crear sesión DB, se deshabilita el log: {exc}")
_disable_db_logging("no se pudo abrir sesión")
return
try: try:
log_entry = RequestLog( log_entry = RequestLog(
telegram_id=str(telegram_id), telegram_id=str(telegram_id),

View File

@@ -32,12 +32,12 @@ logging.basicConfig(
) )
# Convertimos la string del webhook en una lista (por si en el futuro hay varios separados por coma) # Convertimos la string del webhook en una lista (por si en el futuro hay varios separados por coma)
# Se aceptan los nombres WEBHOOK_CONTRATO (nuevo) y WEBHOOK_ONBOARDING (legacy). # Se aceptan los nombres WEBHOOK_ONBOARDING (principal) y WEBHOOK_CONTRATO (alias).
_webhook_raw = os.getenv("WEBHOOK_CONTRATO") or os.getenv("WEBHOOK_ONBOARDING") or "" _webhook_raw = os.getenv("WEBHOOK_ONBOARDING") or os.getenv("WEBHOOK_CONTRATO") or ""
WEBHOOK_URLS = [w.strip() for w in _webhook_raw.split(",") if w.strip()] WEBHOOK_URLS = [w.strip() for w in _webhook_raw.split(",") if w.strip()]
if not WEBHOOK_URLS: if not WEBHOOK_URLS:
logging.warning("No se configuró WEBHOOK_CONTRATO/WEBHOOK_ONBOARDING; el onboarding no enviará datos.") logging.warning("No se configuró WEBHOOK_ONBOARDING (o alias WEBHOOK_CONTRATO); el onboarding no enviará datos.")
# --- 2. ESTADOS DEL FLUJO --- # --- 2. ESTADOS DEL FLUJO ---
( (
@@ -63,7 +63,8 @@ def normalizar_id(texto: str) -> str:
return "N/A" if limpio == "0" else limpio return "N/A" if limpio == "0" else limpio
def limpiar_texto_general(texto: str) -> str: def limpiar_texto_general(texto: str) -> str:
t = texto.strip() # Colapsa espacios múltiples que deja el autocorrector y recorta extremos
t = " ".join(texto.split())
return "N/A" if t == "0" else t return "N/A" if t == "0" else t
# --- 4. TECLADOS DINÁMICOS --- # --- 4. TECLADOS DINÁMICOS ---