JairoDanielMT's picture
Update main.py
a051218 verified
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
from typing import List, Literal
from pydantic import ValidationError
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/<tipo>", 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/<id_categoria>", 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
class SubcategoriaCreate(BaseModel):
nombre_categoria: str
tipo: str # Validación estricta
subcategorias: List[str]
@app.route("/subcategorias", methods=["POST"])
def insertar_subcategorias():
try:
# Obtener los datos de la solicitud
datos = SubcategoriaCreate.parse_obj(request.get_json())
db = SessionLocal()
with db.begin(): # Manejo automático de transacciones
# Verificar si la categoría ya existe
categoria = db.query(Categoria).filter(Categoria.nombre_categoria == datos.nombre_categoria).first()
# Crear la categoría si no existe
if not categoria:
categoria = Categoria(nombre_categoria=datos.nombre_categoria, tipo=datos.tipo)
db.add(categoria)
db.commit()
db.refresh(categoria)
# Insertar subcategorías
subcategorias_existentes = {sub.nombre_subcategoria for sub in categoria.subcategorias}
subcategorias_nuevas = []
for nombre_subcategoria in datos.subcategorias:
if nombre_subcategoria not in subcategorias_existentes:
nueva_subcategoria = Subcategoria(
id_categoria=categoria.id_categoria,
nombre_subcategoria=nombre_subcategoria,
)
db.add(nueva_subcategoria)
subcategorias_nuevas.append(nombre_subcategoria)
db.commit()
# Respuesta final
if subcategorias_nuevas:
return jsonify({
"message": "Subcategorías añadidas exitosamente",
"nuevas_subcategorias": subcategorias_nuevas,
}), 201
else:
return jsonify({
"message": "No se añadieron subcategorías nuevas (ya existían).",
}), 200
except ValidationError as e:
return jsonify({"error": e.errors()}), 400
except Exception as e:
return jsonify({"error": str(e)}), 500
finally:
db.close()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860, debug=False)