From 220b78886d84080bdcb9db5cba2bd6f1e4159eb7 Mon Sep 17 00:00:00 2001 From: Marco Gallegos Date: Sun, 14 Dec 2025 11:53:45 -0600 Subject: [PATCH] feat: Enhance database connection robustness, update onboarding command to `/welcome`, and remove `/socia_finder` functionality. --- .gitignore | 2 ++ main.py | 5 ++-- modules/database.py | 62 +++++++++++++++++++++++++------------------ modules/onboarding.py | 33 +++++++++++------------ modules/printer.py | 6 ++--- 5 files changed, 58 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 9077704..0ff3fe2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Environments .env +.env.bak +.venv/ venv/ .idea/ diff --git a/main.py b/main.py index 51982ac..fcd71a5 100644 --- a/main.py +++ b/main.py @@ -30,8 +30,7 @@ async def menu_principal(update: Update, context: ContextTypes.DEFAULT_TYPE): "📝 `/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" + "⏱️ `/permiso` - Solicitar permiso por horas\n\n" "Selecciona un comando para empezar." ) await update.message.reply_text(texto) @@ -58,4 +57,4 @@ def main(): app.run_polling() if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/modules/database.py b/modules/database.py index 1ca2f76..6667379 100644 --- a/modules/database.py +++ b/modules/database.py @@ -1,28 +1,12 @@ -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 +import os +from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, MetaData, String, create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker # Configuración de logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') - -# Construir la URL de la base de datos desde las variables de entorno individuales -try: - user = os.getenv("MYSQL_USER") - password = os.getenv("MYSQL_PASSWORD") - host = "db" # El nombre del servicio de la base de datos en docker-compose - database = os.getenv("MYSQL_DATABASE") - DATABASE_URL = f"mysql+mysqlconnector://{user}:{password}@{host}:3306/{database}" - - # Crear el motor de la base de datos - engine = create_engine(DATABASE_URL) - metadata = MetaData() - -except AttributeError: - logging.error("Error: Faltan una o más variables de entorno para la base de datos (MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE).") - exit(1) +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") # Base para los modelos declarativos Base = declarative_base() @@ -37,21 +21,47 @@ class RequestLog(Base): message = Column(String(500)) created_at = Column(DateTime, default=datetime.utcnow) +def _build_engine(): + """Crea un engine de SQLAlchemy si hay variables de entorno suficientes.""" + user = os.getenv("MYSQL_USER") + password = os.getenv("MYSQL_PASSWORD") + database = os.getenv("MYSQL_DATABASE") + host = os.getenv("MYSQL_HOST") or "db" # Permitimos override para uso local + + if not all([user, password, database]): + logging.warning("DB logging deshabilitado: faltan MYSQL_USER/MYSQL_PASSWORD/MYSQL_DATABASE.") + return None + + try: + db_url = f"mysql+mysqlconnector://{user}:{password}@{host}:3306/{database}" + return create_engine(db_url, pool_pre_ping=True) + except Exception as exc: + logging.error(f"No se pudo crear el engine de base de datos: {exc}") + return None + +# Crear el engine y sesión si es posible +engine = _build_engine() +metadata = MetaData() if engine else None +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) if engine else None + # Función para inicializar la base de datos def init_db(): + if not engine: + return 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) + # No propagamos para que el bot pueda seguir levantando aunque no haya DB # Función para registrar una solicitud en la base de datos def log_request(telegram_id, username, command, message): + if not SessionLocal: + logging.debug("Log de DB omitido (DB no configurada).") + return + db_session = SessionLocal() try: log_entry = RequestLog( diff --git a/modules/onboarding.py b/modules/onboarding.py index 7a1921e..a14a2a8 100644 --- a/modules/onboarding.py +++ b/modules/onboarding.py @@ -321,11 +321,24 @@ async def cancelar(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: context.user_data.clear() return ConversationHandler.END +## Definición de estados para el ConversationHandler +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)] + +# Handler listo para importar en main.py +onboarding_handler = ConversationHandler( + entry_points=[CommandHandler("welcome", start)], # Cambiado a /welcome + states=states, # Tu diccionario de estados + fallbacks=[CommandHandler("cancelar", cancelar)] +) + def main(): defaults = Defaults(parse_mode=ParseMode.MARKDOWN) application = Application.builder().token(TOKEN).defaults(defaults).build() - - # states definition moved to global scope conv_handler = ConversationHandler( entry_points=[CommandHandler("contrato", start)], @@ -339,19 +352,3 @@ def main(): if __name__ == "__main__": main() -# ... todo el código del contrato ... - -# Definición de estados para el ConversationHandler -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)] - -# Al final: -onboarding_handler = ConversationHandler( - entry_points=[CommandHandler("welcome", start)], # Cambiado a /welcome - states=states, # Tu diccionario de estados - fallbacks=[CommandHandler("cancelar", cancelar)] -) \ No newline at end of file diff --git a/modules/printer.py b/modules/printer.py index 5ca13f3..caeed33 100644 --- a/modules/printer.py +++ b/modules/printer.py @@ -64,8 +64,8 @@ async def recibir_archivo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> msg.attach(attachment) # 3. Enviar el correo - context = ssl.create_default_context() - with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context) as server: + ssl_context = ssl.create_default_context() + with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=ssl_context) as server: server.login(SMTP_USER, SMTP_PASSWORD) server.sendmail(SMTP_USER, SMTP_RECIPIENT, msg.as_string()) @@ -86,4 +86,4 @@ print_handler = ConversationHandler( entry_points=[CommandHandler("print", start_print)], states={ESPERANDO_ARCHIVO: [MessageHandler(filters.Document.ALL | filters.PHOTO, recibir_archivo)]}, fallbacks=[CommandHandler("cancelar", cancelar)] -) \ No newline at end of file +)