""" main.py — Entrypoint de Pedrito Hechakuaa. Arranca el bot de Telegram y el poller en paralelo. Al arrancar: inicializa la DB y migra el tenant principal del .env si no existe. """ from __future__ import annotations import asyncio import signal import sys import structlog from telegram.ext import Application import vault as vault_mod from config import get_settings from database import get_tenant, init_db, save_tenant from onboarding import OnboardingFlow from poller import Poller from telegram_bot import PedritoBot # Configurar structlog structlog.configure( processors=[ structlog.contextvars.merge_contextvars, structlog.processors.add_log_level, structlog.processors.TimeStamper(fmt="iso"), structlog.dev.ConsoleRenderer() if sys.stderr.isatty() else structlog.processors.JSONRenderer(), ] ) logger = structlog.get_logger(__name__) async def _migrar_tenant_principal(settings) -> None: """ Migra el tenant principal del .env a la DB si todavía no existe. Permite que el dueño del bot siga funcionando sin re-registrarse. """ chat_id = settings.telegram_chat_id existente = await get_tenant(chat_id) if existente: logger.info("tenant_principal_ya_existe", chat_id=chat_id) return logger.info("migrando_tenant_principal_del_env", chat_id=chat_id) usuario = settings.descifrar_usuario() password = settings.descifrar_password() try: credentials_enc = vault_mod.cifrar_credenciales( chat_id=chat_id, usuario=usuario, password=password, fernet_key=settings.fernet_key, ) finally: del usuario, password await save_tenant( chat_id, nombre_preferido="Admin", tono="cotidiano", timezone="America/Asuncion", credentials_enc=credentials_enc, estado="activo", consentimiento_aceptado=1, ) logger.info("tenant_principal_migrado", chat_id=chat_id) async def main() -> None: settings = get_settings() logger.info( "pedrito_hechakuaa_iniciando", poll_interval_minutes=settings.poll_interval_minutes, horario=f"{settings.poll_hora_inicio}:00-{settings.poll_hora_fin}:00", chat_id=settings.telegram_chat_id, ) # Base de datos await init_db(settings.db_path) await _migrar_tenant_principal(settings) # Bot de Telegram onboarding = OnboardingFlow(settings) pedrito_bot = PedritoBot( token=settings.telegram_bot_token, settings=settings, onboarding=onboarding, ) app: Application = pedrito_bot.build() # Poller (sin snapshot global — maneja uno por tenant internamente) poller = Poller( settings=settings, bot=pedrito_bot, ) # El callback de /notif pasa el chat_id al poller pedrito_bot.set_notif_callback( lambda chat_id: poller.revisar_ahora(chat_id=chat_id, es_manual=True) ) # Manejo de señales para shutdown limpio loop = asyncio.get_running_loop() def handle_shutdown(sig: int) -> None: logger.info("shutdown_recibido", signal=sig) poller.detener() loop.stop() for sig in (signal.SIGINT, signal.SIGTERM): try: loop.add_signal_handler(sig, handle_shutdown, sig) except NotImplementedError: pass # Windows no soporta add_signal_handler # Arrancar bot y poller en paralelo async with app: await app.initialize() await app.start() logger.info("bot_telegram_iniciado") telegram_task = asyncio.create_task( app.updater.start_polling(drop_pending_updates=True) ) poller_task = asyncio.create_task(poller.run_forever()) logger.info("pedrito_hechakuaa_corriendo") try: await asyncio.gather(telegram_task, poller_task) except asyncio.CancelledError: pass finally: await app.updater.stop() await app.stop() logger.info("pedrito_hechakuaa_detenido") if __name__ == "__main__": asyncio.run(main())