From cbfcee557a7be89cedc0ae19c8ff3e677b4c61f8 Mon Sep 17 00:00:00 2001 From: Marco Gallegos Date: Sun, 14 Dec 2025 15:33:47 -0600 Subject: [PATCH] feat: Add database health checks and host configuration, enhance the start command with a quick reply keyboard, and improve general text cleaning. --- .env.example | 5 +++-- Readme.md | 2 +- docker-compose.yml | 10 +++++++++- main.py | 14 +++++++------- modules/database.py | 16 +++++++++++++++- modules/onboarding.py | 9 +++++---- 6 files changed, 40 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index 502c3c6..f888f14 100644 --- a/.env.example +++ b/.env.example @@ -6,11 +6,12 @@ GOOGLE_API_KEY=AIzaSyBqH5... # 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_VACACIONES=url WEBHOOK_PERMISOS=url -WEBHOOK_DATA_EMPLEADO=url # --- DATABASE --- # Usado por el servicio de la base de datos en docker-compose.yml diff --git a/Readme.md b/Readme.md index 4881dbf..abfdde6 100644 --- a/Readme.md +++ b/Readme.md @@ -51,7 +51,7 @@ Copia el archivo `.env.example` a `.env` y rellena los valores correspondientes. TELEGRAM_TOKEN=TU_TOKEN_AQUI # --- WEBHOOKS N8N --- -WEBHOOK_CONTRATO=https://... # También acepta WEBHOOK_ONBOARDING +WEBHOOK_ONBOARDING=https://... # Alias aceptado: WEBHOOK_CONTRATO WEBHOOK_PRINT=https://... WEBHOOK_VACACIONES=https://... diff --git a/docker-compose.yml b/docker-compose.yml index 53ea0d9..6243936 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,11 +8,13 @@ services: env_file: - .env environment: + - MYSQL_HOST=db - MYSQL_USER=${MYSQL_USER} - MYSQL_PASSWORD=${MYSQL_PASSWORD} - MYSQL_DATABASE=${MYSQL_DATABASE} depends_on: - - db + db: + condition: service_healthy volumes: - .:/app @@ -25,6 +27,12 @@ services: MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_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: - mysql_data:/var/lib/mysql ports: diff --git a/main.py b/main.py index fcd71a5..9139996 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,7 @@ from dotenv import load_dotenv # Cargar variables de entorno antes de importar módulos que las usan load_dotenv() -from telegram import Update +from telegram import Update, ReplyKeyboardMarkup from telegram.constants import ParseMode 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) 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\n" - "Selecciona un comando para empezar." + "Toca un comando en azul para lanzarlo rápido:" ) - 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(): # Configuración Global diff --git a/modules/database.py b/modules/database.py index 6667379..a0365f1 100644 --- a/modules/database.py +++ b/modules/database.py @@ -39,6 +39,13 @@ def _build_engine(): logging.error(f"No se pudo crear el engine de base de datos: {exc}") 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 engine = _build_engine() 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 def init_db(): + global engine, SessionLocal if not engine: return try: @@ -54,6 +62,7 @@ def init_db(): logging.info("Tablas verificadas/creadas correctamente.") except Exception as 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 # 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).") return - db_session = SessionLocal() + try: + 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: log_entry = RequestLog( telegram_id=str(telegram_id), diff --git a/modules/onboarding.py b/modules/onboarding.py index 75aab13..f7dadd6 100644 --- a/modules/onboarding.py +++ b/modules/onboarding.py @@ -32,12 +32,12 @@ logging.basicConfig( ) # 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). -_webhook_raw = os.getenv("WEBHOOK_CONTRATO") or os.getenv("WEBHOOK_ONBOARDING") or "" +# Se aceptan los nombres WEBHOOK_ONBOARDING (principal) y WEBHOOK_CONTRATO (alias). +_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()] 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 --- ( @@ -63,7 +63,8 @@ def normalizar_id(texto: str) -> str: return "N/A" if limpio == "0" else limpio 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 # --- 4. TECLADOS DINÁMICOS ---