from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey, Enum from sqlalchemy.orm import sessionmaker, relationship, declarative_base from datetime import datetime import pytz import os import requests from dotenv import load_dotenv from pydantic import BaseModel from typing import List from flask import Flask, jsonify, render_template from flask import request, redirect, url_for, flash from flask_cors import CORS app = Flask(__name__) app.secret_key = "Jairo_y_Diana" CORS(app) @app.after_request def add_cors_headers(response): response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Headers"] = "*" response.headers["Access-Control-Allow-Methods"] = "*" response.headers["Access-Control-Allow-Credentials"] = "true" return response # Cargar variables de entorno load_dotenv() # URL de la API de Google Apps Script GOOGLE_SHEET_API_URL = os.getenv("GOOGLE_SHEET_API_URL") # Conexión con la base de datos SQLite (Finanzas.db) DATABASE_URL = "sqlite:///finanzas.db" engine = create_engine(DATABASE_URL) # Crear una clase base para el ORM Base = declarative_base() # Definición de modelos ORM class Usuario(Base): __tablename__ = "usuarios" id_usuario = Column(Integer, primary_key=True, index=True) nombre = Column(String, nullable=False) class Categoria(Base): __tablename__ = "categorias" id_categoria = Column(Integer, primary_key=True, autoincrement=True) nombre_categoria = Column(String, nullable=False) tipo = Column(Enum("Ingreso", "Gasto", name="tipo_categoria"), nullable=False) class Subcategoria(Base): __tablename__ = "subcategorias" id_subcategoria = Column(Integer, primary_key=True, autoincrement=True) id_categoria = Column( Integer, ForeignKey("categorias.id_categoria"), nullable=False ) nombre_subcategoria = Column(String, nullable=False) categoria = relationship("Categoria", backref="subcategorias") class Transaccion(Base): __tablename__ = "transacciones" id_transaccion = Column(Integer, primary_key=True, autoincrement=True) fecha = Column(String, nullable=False) tipo = Column(Enum("Ingreso", "Gasto", name="tipo_transaccion"), nullable=False) monto = Column(Float, nullable=False) id_subcategoria = Column( Integer, ForeignKey("subcategorias.id_subcategoria"), nullable=False ) descripcion = Column(String) id_usuario = Column(Integer, ForeignKey("usuarios.id_usuario"), nullable=False) subcategoria = relationship("Subcategoria", backref="transacciones") usuario = relationship("Usuario", backref="transacciones") # Función para obtener la fecha y hora precisa en la zona horaria de Perú def get_peru_time(): peru_tz = pytz.timezone("America/Lima") return datetime.now(peru_tz).strftime("%Y-%m-%d %H:%M:%S") # Función para enviar datos a la hoja de Google Sheets def send_to_google_sheet(data_list): data_dicts = [] for data in data_list: # Convertir la fecha a un string en el formato adecuado data_dict = data.model_dump() if isinstance(data_dict["fecha"], datetime): data_dict["fecha"] = data_dict["fecha"].strftime("%Y-%m-%d %H:%M:%S") data_dicts.append(data_dict) response = requests.post(GOOGLE_SHEET_API_URL, json=data_dicts) if response.status_code == 200: print("Datos enviados con éxito a la Google Sheet.") else: print(f"Error al enviar datos: {response.status_code}, {response.text}") # def send_to_google_sheet(data: List[BaseModel]): # """ # Envía los datos a la Google Sheet mediante la API de Google Apps Script. # :param data: Lista de registros con toda la información. # """ # data_dicts = [record.model_dump() for record in data] # response = requests.post(GOOGLE_SHEET_API_URL, json=data_dicts) # if response.status_code == 200: # print("Datos enviados con éxito a la Google Sheet.") # else: # print(f"Error al enviar datos: {response.status_code}, {response.text}") SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) def insert_transaccion(fecha, tipo, monto, id_subcategoria, descripcion, id_usuario): db = SessionLocal() try: # Crear la transacción transaccion = Transaccion( fecha=fecha, tipo=tipo, monto=monto, descripcion=descripcion, id_subcategoria=id_subcategoria, id_usuario=id_usuario, ) db.add(transaccion) db.commit() transaccion_db = ( db.query(Transaccion) .filter(Transaccion.id_transaccion == transaccion.id_transaccion) .first() ) if transaccion_db: subcategoria = transaccion_db.subcategoria.nombre_subcategoria categoria = transaccion_db.subcategoria.categoria.nombre_categoria usuario = transaccion_db.usuario.nombre transaccion_pydantic = TransaccionPydantic( fecha=fecha, # Usar fecha directamente tipo=tipo, monto=monto, descripcion=descripcion, subcategoria=subcategoria, categoria=categoria, usuario=usuario, ) print(transaccion_pydantic.model_dump()) send_to_google_sheet([transaccion_pydantic]) finally: db.close() # Función para obtener los usuarios def get_usuarios(): db = SessionLocal() try: return db.query(Usuario).all() finally: db.close() # Función para obtener las categorías def get_categorias(): db = SessionLocal() try: return db.query(Categoria).all() finally: db.close() # Función para obtener las categorías por tipo def get_categorias_por_tipo(tipo): db = SessionLocal() try: return db.query(Categoria).filter(Categoria.tipo == tipo).all() finally: db.close() # Función para obtener las subcategorías por categoría def get_subcategorias(id_categoria=None): db = SessionLocal() try: if id_categoria: return ( db.query(Subcategoria) .filter(Subcategoria.id_categoria == id_categoria) .all() ) return db.query(Subcategoria).all() finally: db.close() # Función para obtener las transacciones completas def get_full_transacciones(): db = SessionLocal() try: result = ( db.query(Transaccion).join(Subcategoria).join(Categoria).join(Usuario).all() ) return [ { "id_transaccion": t.id_transaccion, "fecha": t.fecha, "tipo": t.tipo, "monto": t.monto, "descripcion": t.descripcion, "nombre_subcategoria": t.subcategoria.nombre_subcategoria, "nombre_categoria": t.subcategoria.categoria.nombre_categoria, "usuario": t.usuario.nombre, } for t in result ] finally: db.close() def get_tipos(): return ["Ingreso", "Gasto"] # Definición de modelos Pydantic class TransaccionPydantic(BaseModel): fecha: datetime tipo: str monto: float descripcion: str subcategoria: str categoria: str usuario: str class CategoriaPydantic(BaseModel): id_categoria: int nombre_categoria: str tipo: str class SubcategoriaPydantic(BaseModel): id_subcategoria: int nombre_subcategoria: str class UsuarioPydantic(BaseModel): id_usuario: int nombre: str class TransaccionIngreso(BaseModel): fecha: datetime monto: float tipo: str id_subcategoria: int descripcion: str id_usuario: int # rutas @app.route("/usuarios") def usuarios(): usuarios = get_usuarios() return {"usuarios": usuarios} @app.route("/categorias") def categorias(): categorias = get_categorias() return {"categorias": categorias} # Ruta para obtener categorías por tipo a través de AJAX @app.route("/categorias/", methods=["GET"]) def categorias_por_tipo(tipo): categorias = get_categorias_por_tipo(tipo) # Obtener las categorías por el tipo return jsonify( [ { "id_categoria": categoria.id_categoria, "nombre_categoria": categoria.nombre_categoria, "tipo": categoria.tipo, } for categoria in categorias ] ) @app.route("/subcategorias") def subcategorias(): subcategorias = get_subcategorias() return {"subcategorias": subcategorias} # Ruta para obtener subcategorías por categoría a través de AJAX @app.route("/subcategorias/", methods=["GET"]) def subcategorias_por_categoria(id_categoria): subcategorias = get_subcategorias(id_categoria) return jsonify( [ { "id_subcategoria": subcategoria.id_subcategoria, "nombre_subcategoria": subcategoria.nombre_subcategoria, } for subcategoria in subcategorias ] ) # Rutas del API @app.get("/") def home(): usuarios = get_usuarios() categorias = get_categorias() subcategorias = get_subcategorias() tipos = get_tipos() return render_template( "index.html", usuarios=usuarios, categorias=categorias, subcategorias=subcategorias, tipos=tipos, ) @app.post("/guardar-transaccion") def guardar_transaccion(): try: # Obtener datos del formulario id_usuario = request.form.get("usuario") fecha = datetime.now() # Asigna la fecha actual tipo = request.form.get("tipo") monto = float(request.form.get("monto", 0)) # Convertir a float id_subcategoria = request.form.get("subcategoria") descripcion = request.form.get("descripcion") # Validar que todos los campos estén presentes if not all([id_usuario, tipo, monto, id_subcategoria]): flash("Por favor complete todos los campos", "error") return redirect(url_for("home")) # Redirigir al formulario principal # Insertar la transacción insert_transaccion( id_usuario=int(id_usuario), fecha=fecha, tipo=tipo, monto=monto, id_subcategoria=int(id_subcategoria), descripcion=descripcion, ) flash("Transacción guardada exitosamente", "success") return redirect(url_for("home")) # Redirigir al formulario principal except Exception as e: flash(f"Error al guardar la transacción: {str(e)}", "error") return redirect(url_for("home")) # Redirigir al formulario principal if __name__ == "__main__": app.run(host="0.0.0.0", port=7860, debug=False)