feat: Enhance database connection robustness, update onboarding command to /welcome, and remove /socia_finder functionality.

This commit is contained in:
Marco Gallegos
2025-12-14 11:53:45 -06:00
parent b0d30928f1
commit 220b78886d
5 changed files with 58 additions and 50 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
# Environments # Environments
.env .env
.env.bak
.venv/
venv/ venv/
.idea/ .idea/

View File

@@ -30,8 +30,7 @@ async def menu_principal(update: Update, context: ContextTypes.DEFAULT_TYPE):
"📝 `/welcome` - Iniciar onboarding/contrato\n" "📝 `/welcome` - Iniciar onboarding/contrato\n"
"🖨️ `/print` - Imprimir o enviar archivo\n" "🖨️ `/print` - Imprimir o enviar archivo\n"
"🌴 `/vacaciones` - Solicitar días libres\n" "🌴 `/vacaciones` - Solicitar días libres\n"
"⏱️ `/permiso` - Solicitar permiso por horas\n" "⏱️ `/permiso` - Solicitar permiso por horas\n\n"
"🔍 `/socia_finder` - Buscar datos de una compañera\n\n"
"Selecciona un comando para empezar." "Selecciona un comando para empezar."
) )
await update.message.reply_text(texto) await update.message.reply_text(texto)

View File

@@ -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 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 # Configuración de logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') 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)
# Base para los modelos declarativos # Base para los modelos declarativos
Base = declarative_base() Base = declarative_base()
@@ -37,21 +21,47 @@ class RequestLog(Base):
message = Column(String(500)) message = Column(String(500))
created_at = Column(DateTime, default=datetime.utcnow) 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 # Función para inicializar la base de datos
def init_db(): def init_db():
if not engine:
return
try: try:
logging.info("Inicializando la base de datos y creando tablas si no existen...") logging.info("Inicializando la base de datos y creando tablas si no existen...")
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
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}")
raise # No propagamos para que el bot pueda seguir levantando aunque no haya DB
# 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 # Función para registrar una solicitud en la base de datos
def log_request(telegram_id, username, command, message): def log_request(telegram_id, username, command, message):
if not SessionLocal:
logging.debug("Log de DB omitido (DB no configurada).")
return
db_session = SessionLocal() db_session = SessionLocal()
try: try:
log_entry = RequestLog( log_entry = RequestLog(

View File

@@ -321,12 +321,25 @@ async def cancelar(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
context.user_data.clear() context.user_data.clear()
return ConversationHandler.END 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(): def main():
defaults = Defaults(parse_mode=ParseMode.MARKDOWN) defaults = Defaults(parse_mode=ParseMode.MARKDOWN)
application = Application.builder().token(TOKEN).defaults(defaults).build() application = Application.builder().token(TOKEN).defaults(defaults).build()
# states definition moved to global scope
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
entry_points=[CommandHandler("contrato", start)], entry_points=[CommandHandler("contrato", start)],
states=states, states=states,
@@ -339,19 +352,3 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
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)]
)

View File

@@ -64,8 +64,8 @@ async def recibir_archivo(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
msg.attach(attachment) msg.attach(attachment)
# 3. Enviar el correo # 3. Enviar el correo
context = ssl.create_default_context() ssl_context = ssl.create_default_context()
with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context) as server: with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=ssl_context) as server:
server.login(SMTP_USER, SMTP_PASSWORD) server.login(SMTP_USER, SMTP_PASSWORD)
server.sendmail(SMTP_USER, SMTP_RECIPIENT, msg.as_string()) server.sendmail(SMTP_USER, SMTP_RECIPIENT, msg.as_string())