diff --git a/app.py b/app.py index f5c50ae..19a9176 100644 --- a/app.py +++ b/app.py @@ -15,6 +15,9 @@ # 1. IMPORTANTE: Importar ProxyFix para Google Cloud Run/Render from werkzeug.middleware.proxy_fix import ProxyFix +from flask_apscheduler import APScheduler +from sqlalchemy import text + # Imports de modelos y rutas from core.models import db, Usuario from auth.routes import auth_bp @@ -33,6 +36,8 @@ from components.funcionesAdmin.routes import admin_bp from components.categorias.routes import categorias_bp from components.contactos.routes import contactos_bp +from core.models import db, Usuario, Notificacion +from datetime import datetime, timezone from dotenv import load_dotenv import firebase_admin @@ -51,6 +56,11 @@ x_prefix=1 ) + +app.config['SCHEDULER_API_ENABLED'] = True +scheduler = APScheduler() + + def cerrar_sesion(): """ Cierra la sesión de base de datos de forma segura. @@ -146,15 +156,62 @@ def handle_options(): headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS" return resp - -@app.route('/') -def health_check(): - return "Ok" - - -# MAIN -# Inicializas socketio con la app -socketio.init_app(app) - +def tarea_archivar_publicaciones(): + with app.app_context(): + try: + print(f"[{datetime.now()}] Iniciando proceso de archivado...") + sql_query = text(""" + UPDATE publicaciones + SET estado = 1 + WHERE estado = 0 + AND COALESCE(fecha_modificacion, fecha_creacion) < NOW() - INTERVAL '6 months' + RETURNING id, id_usuario, titulo + """) + + result = db.session.execute(sql_query) + archivos_procesados = result.fetchall() + if archivos_procesados: + print(f"Se encontraron {len(archivos_procesados)} publicaciones para archivar.") + + for pub in archivos_procesados: + p_id = pub.id + p_usuario = pub.id_usuario + p_titulo = pub.titulo + + nueva_noti = Notificacion( + id_usuario=p_usuario, + titulo='Publicación Archivada', + descripcion=f'Tu publicación "{p_titulo}" ha sido archivada automáticamente por inactividad (6 meses). Puedes desarchivarla desde tu perfil.', + tipo='sistema', + fecha_creacion=datetime.now(timezone.utc), + leido=False, + id_publicacion=p_id, + id_referencia=None + ) + db.session.add(nueva_noti) + + db.session.commit() + print(f"ÉXITO: {len(archivos_procesados)} publicaciones archivadas y usuarios notificados.") + + else: + # Si no hay nada que archivar, hacemos commit igual para cerrar la transacción limpia + db.session.commit() + print("Sin cambios: No hay publicaciones antiguas pendientes.") + + except Exception as e: + print(f"ERROR CRÍTICO en tarea programada: {e}") + db.session.rollback() # Deshace todo si algo falla + + if __name__ == '__main__': + scheduler.init_app(app) + scheduler.start() + scheduler.add_job( + id='archivar_job', + func=tarea_archivar_publicaciones, + trigger='cron', + hour=3, + minute=0 + ) + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/components/publicaciones/services.py b/components/publicaciones/services.py index 8f9b2ab..009cb1f 100644 --- a/components/publicaciones/services.py +++ b/components/publicaciones/services.py @@ -37,6 +37,7 @@ def serializar_publicacion_lista(pub): "etiquetas": [et.nombre for et in pub.etiquetas], "fecha_creacion": pub.fecha_creacion.astimezone(zona_arg).isoformat() if pub.fecha_creacion else None, "coordenadas": pub.coordenadas, + "estado": pub.estado, # No enviamos descripción completa para ahorrar datos en listas } diff --git a/requirements.txt b/requirements.txt index f855af6..e69de29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,102 +0,0 @@ -alembic==1.16.1 -annotated-types==0.7.0 -anyio==4.11.0 -astroid==3.3.11 -bidict==0.23.1 -blinker==1.9.0 -CacheControl==0.14.3 -cachetools==5.5.2 -certifi==2025.4.26 -cffi==1.17.1 -charset-normalizer==3.4.2 -click==8.2.0 -cloudinary==1.44.1 -colorama==0.4.6 -cryptography==45.0.2 -deprecation==2.1.0 -dill==0.4.0 -dnspython==2.8.0 -eventlet==0.40.3 -firebase==4.0.1 -firebase-admin==6.8.0 -Flask==3.1.1 -flask-cors==6.0.0 -Flask-Migrate==4.1.0 -Flask-SocketIO==5.5.1 -Flask-SQLAlchemy==3.1.1 -google-api-core==2.25.0rc1 -google-api-python-client==2.169.0 -google-auth==2.40.2 -google-auth-httplib2==0.2.0 -google-cloud-core==2.4.3 -google-cloud-firestore==2.20.2 -google-cloud-storage==3.1.0 -google-crc32c==1.7.1 -google-resumable-media==2.7.2 -googleapis-common-protos==1.70.0 -greenlet==3.2.2 -grpcio==1.71.0 -grpcio-status==1.71.0 -h11==0.16.0 -h2==4.3.0 -hpack==4.1.0 -httpcore==1.0.9 -httplib2==0.22.0 -httpx==0.28.1 -hyperframe==6.1.0 -idna==3.10 -isort==6.0.1 -itsdangerous==2.2.0 -Jinja2==3.1.6 -Mako==1.3.10 -MarkupSafe==3.0.2 -mccabe==0.7.0 -msgpack==1.1.0 -packaging==25.0 -pillow==11.3.0 -platformdirs==4.4.0 -postgrest==2.20.0 -proto-plus==1.26.1 -protobuf==5.29.4 -psycopg2==2.9.10 -psycopg2-binary==2.9.10 -pyasn1==0.6.1 -pyasn1_modules==0.4.2 -pycparser==2.22 -pydantic==2.11.9 -pydantic_core==2.33.2 -PyJWT==2.10.1 -pylint==3.3.8 -pylint-flask==0.6 -pylint-plugin-utils==0.9.0 -pylint-sqlalchemy==0.3.0 -pyparsing==3.2.3 -python-dotenv==1.1.1 -python-engineio==4.12.2 -python-socketio==5.13.0 -pytz==2025.2 -qrcode==8.2 -realtime==2.20.0 -reportlab==4.4.2 -requests==2.32.3 -rsa==4.9.1 -simple-websocket==1.1.0 -six==1.17.0 -slugify==0.0.1 -sniffio==1.3.1 -SQLAlchemy==2.0.41 -storage3==2.20.0 -StrEnum==0.4.15 -supabase==2.20.0 -supabase-auth==2.20.0 -supabase-functions==2.20.0 -tomlkit==0.13.3 -typing-inspection==0.4.1 -typing_extensions==4.15.0 -uritemplate==4.1.1 -urllib3==2.4.0 -waitress==3.0.2 -websockets==15.0.1 -Werkzeug==3.1.3 -wsproto==1.2.0 -