frontend añadido y backend de creacion de notas

This commit is contained in:
2025-09-18 00:31:23 +02:00
parent 1deabb8a26
commit a76b13ec33
75 changed files with 20364 additions and 317 deletions
+627
View File
@@ -0,0 +1,627 @@
import marimo
__generated_with = "0.15.5"
app = marimo.App(width="columns")
@app.cell(column=0)
def _():
import marimo as mo
return (mo,)
@app.cell
def _():
PUERTO_POSTGRES = 55455
SERVICIO = "postgres_ext"
DB_PASSWORD = "mipassword"
DB_USER = "postgres"
DB_NAME = "basededatos"
DB_HOST = "localhost"
return DB_HOST, DB_NAME, DB_PASSWORD, DB_USER, PUERTO_POSTGRES
@app.cell
def _(DB_HOST, DB_NAME, DB_PASSWORD, DB_USER, PUERTO_POSTGRES):
# scaffold_backend.py
import os
from pathlib import Path
BASE_DIR = Path.cwd() / "backend"
DB_DIR = BASE_DIR / "db"
DB_DIR.mkdir(parents=True, exist_ok=True)
# __init__.py
(DB_DIR / "__init__.py").write_text("", encoding="utf-8")
# base.py
base_py = """\
# backend/db/base.py
from sqlalchemy.orm import declarative_base
# Declarative Base común para todos los modelos ORM
Base = declarative_base()
"""
(DB_DIR / "base.py").write_text(base_py, encoding="utf-8")
# session.py
session_py = f"""\
# backend/db/session.py
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from urllib.parse import quote_plus
from dotenv import load_dotenv
# Cargar variables de entorno (.env) desde backend/.env
load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), "..", ".env"))
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = quote_plus(os.getenv("DB_PASSWORD"))
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT", "{PUERTO_POSTGRES}")
DB_NAME = os.getenv("DB_NAME")
DATABASE_URL = (
f"postgresql+psycopg2://{{DB_USER}}:{{DB_PASSWORD}}@{{DB_HOST}}:{{DB_PORT}}/{{DB_NAME}}"
)
# Crear engine
engine = create_engine(
DATABASE_URL,
pool_size=5,
max_overflow=10,
future=True,
echo=False, # pon True para ver las queries en consola
)
# Fábrica de sesiones
SessionLocal = sessionmaker(
bind=engine,
autoflush=False,
expire_on_commit=False,
future=True,
)
"""
(DB_DIR / "session.py").write_text(session_py, encoding="utf-8")
# .env handling
env_file = BASE_DIR / ".env"
default_env = {
"DB_USER": DB_USER,
"DB_PASSWORD": DB_PASSWORD,
"DB_HOST": DB_HOST,
"DB_PORT": PUERTO_POSTGRES,
"DB_NAME": DB_NAME,
}
if not env_file.exists():
# Crear archivo nuevo con todas las claves
env_content = "\n".join([f"{k}={v}" for k, v in default_env.items()]) + "\n"
env_file.write_text(env_content, encoding="utf-8")
print(f"✅ Creado {env_file}")
else:
# Leer y comprobar claves
existing = env_file.read_text(encoding="utf-8").splitlines()
existing_keys = {line.split("=")[0] for line in existing if "=" in line}
missing = {k: v for k, v in default_env.items() if k not in existing_keys}
if missing:
with env_file.open("a", encoding="utf-8") as f:
for k, v in missing.items():
f.write(f"{k}={v}\n")
print(f"📝 Añadidas claves faltantes: {', '.join(missing.keys())}")
else:
print(f"{env_file} ya tiene todas las claves necesarias.")
print(f"✅ Creado {DB_DIR/'base.py'}")
print(f"✅ Creado {DB_DIR/'session.py'}")
return Path, os
@app.cell
def _(Path, os):
# import os
RUTA_CARPETA_SNIPPETS = os.environ["SNIPPETS_ROUTE"]
DEST_BASE = Path("./domains/arquitecture_layer")
DEST_BASE.mkdir(parents=True, exist_ok=True)
archivos = {
"utils/ArquitectureLayer/Repo.py": "Repo.py",
"utils/ArquitectureLayer/Mapper.py": "Mapper.py",
"utils/ArquitectureLayer/Model.py": "Model.py",
}
# Copiar los archivos
for origen_rel, nombre_dest in archivos.items():
origen = Path(RUTA_CARPETA_SNIPPETS) / origen_rel
destino = DEST_BASE / nombre_dest
destino.write_text(origen.read_text(encoding="utf-8"), encoding="utf-8")
print(f"{origen}{destino}")
# Crear __init__.py para exponer los módulos
init_file = DEST_BASE / "__init__.py"
init_code = """\
# domains/arquitecture_layer/__init__.py
from .Repo import *
from .Mapper import *
from .Model import *
__all__ = ["Repo", "Mapper", "Model"]
"""
init_file.write_text(init_code, encoding="utf-8")
print(f"✅ Creado {init_file}")
return (RUTA_CARPETA_SNIPPETS,)
@app.cell(column=1)
def _(os):
#Obtenemos las variables
# import os
def obtener_texto_archivo(ruta_archivo):
"""Lee un archivo o todos los archivos de un directorio."""
if os.path.isdir(ruta_archivo):
textos = {}
for archivo in os.listdir(ruta_archivo):
ruta = os.path.join(ruta_archivo, archivo)
if os.path.isfile(ruta):
with open(ruta, 'r', encoding='utf-8') as f:
textos[archivo] = f.read()
return textos
elif os.path.isfile(ruta_archivo):
with open(ruta_archivo, 'r', encoding='utf-8') as f:
return f.read()
else:
raise FileNotFoundError(f"No se encontró archivo o directorio: {ruta_archivo}")
repo_code = obtener_texto_archivo("domains/arquitecture_layer/Repo.py")
mapper_code = obtener_texto_archivo("domains/arquitecture_layer/Mapper.py")
model_code = obtener_texto_archivo("domains/arquitecture_layer/Model.py")
repo_ruta_code = "from domains.arquitecture_layer.Model import Model_base"
mapper_ruta_code = "domains.arquitecture_layer.Mapper import Mapper_base"
model_ruta_code = "domains.arquitecture_layer.Repo import Repo_base"
return (
mapper_code,
mapper_ruta_code,
model_code,
model_ruta_code,
repo_code,
repo_ruta_code,
)
@app.cell
def _(mo):
input_entidad = mo.ui.text_area("", label="Entidad a crear", rows=1, full_width=True)
input_descripcion = mo.ui.text_area("", label="Descripcion de la Entidad", rows=3, full_width=True)
mo.vstack([input_entidad, input_descripcion])
return input_descripcion, input_entidad
@app.cell
def _(mo):
esquema = mo.ui.text(
value="public",
label="Esquema:"
)
esquema
return (esquema,)
@app.cell
def _(
RUTA_CARPETA_SNIPPETS,
esquema,
input_descripcion,
input_entidad,
mo,
os,
):
from jinja2 import Template
# from pathlib import Path
# RUTA_CARPETA_SNIPPETS = os.environ["SNIPPETS_ROUTE"]
with open(os.path.join(RUTA_CARPETA_SNIPPETS, "markdown/plantilla_generacion_campos_detalles.md"), "r", encoding="utf-8") as _f:
plantilla_detalles = Template(_f.read())
entidad = input_entidad.value.lower()
descripcion = input_descripcion.value.lower()
_contexto = {
"ENTIDAD" : entidad, # Nombre de la entidad
"DESCRIPCION" : descripcion,
"SCHEMA" : esquema.value
}
# Pasar el diccionario con **
resultado_plantilla = plantilla_detalles.render(**_contexto)
mo.output.append(mo.md("### Prompt para la base del modelo:"))
mo.output.append(mo.md(resultado_plantilla))
return Template, entidad, resultado_plantilla
@app.cell
def _(mo):
run_button = mo.ui.run_button(label="Generar JSON", kind="neutral")
run_button
return (run_button,)
@app.cell
def _(mo, os, resultado_plantilla, run_button):
from openai import OpenAI
import json
def call_openai_to_json(template_text: str) -> dict:
"""Call OpenAI and return parsed JSON; raises on errors."""
# NOTE: Read API key from environment for reproducibility
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
resp = client.chat.completions.create(
model="gpt-4o", # or "gpt-5" if available
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": "Eres un asistente estricto que SIEMPRE devuelve JSON válido y nada más."},
{"role": "user", "content": resultado_plantilla}
],
temperature=0.2
)
json_text = resp.choices[0].message.content
return json.loads(json_text)
status_message = "Pulsa el botón para generar."
json_text = None # Default sentinel so dependents can run
if run_button.value:
try:
json_text = call_openai_to_json(resultado_plantilla)
status_message = "JSON generado correctamente."
except Exception as exc:
# Safe error surface without stopping the graph
status_message = f"Error al generar: {exc!s}"
# Return a UI element; if no resultado, mostramos solo el mensaje
mo.vstack([
mo.md(f"**Estado:** {status_message}"),
mo.json(json_text) if json_text is not None else mo.md("")
])
return OpenAI, json, json_text
@app.cell
def _(json, json_text, mo):
from glom import glom, Coalesce
# --- 1) Normalizar entrada: str JSON -> dict
def to_obj(x):
if isinstance(x, str):
return json.loads(x) # <- parsea el texto JSON
return x
data = to_obj(json_text) # json_text puede ser str o dict
# Extraer listas
campos_list = glom(data, Coalesce("CAMPOS", default=[]))
relaciones_list = glom(data, Coalesce("RELACIONES", default=[]))
indices_list = glom(data, Coalesce("INDEX_LIST", default=[]))
unicos_list = glom(data, Coalesce("UNIQUE_LIST", default=[]))
# Por si vienen como string en lugar de lista:
def ensure_list(v):
if v is None:
return []
if isinstance(v, list):
return v
return [str(v)]
# Convertir a texto multilínea
campos_list = ensure_list(campos_list)
relaciones_list = ensure_list(relaciones_list)
indices_list = ensure_list(indices_list)
unicos_list = ensure_list(unicos_list)
campos = "\n".join(map(str, campos_list))
relaciones = "\n".join(map(str, relaciones_list))
indices = "\n".join(map(str, indices_list))
unicos = "\n".join(map(str, unicos_list))
campos_final = mo.ui.text_area(campos, label="Campos", rows=15, full_width=True)
relaciones_final = mo.ui.text_area(relaciones, label="Relaciones", rows=3, full_width=True)
indices_final = mo.ui.text_area(indices, label="Indices", rows=3, full_width=True)
unicos_final = mo.ui.text_area(unicos, label="Unicos", rows=3, full_width=True)
layout = mo.hstack(
[
campos_final,
mo.vstack([relaciones_final, indices_final, unicos_final])
],
widths=[2, 1], # t1 ocupa el doble de ancho que el stack vertical
gap=1 # margen entre los elementos en rem (1 rem ≈ 16 px)
)
layout
return campos_final, indices_final, relaciones_final, unicos_final
@app.cell
def _(
RUTA_CARPETA_SNIPPETS,
Template,
campos_final,
entidad,
esquema,
indices_final,
mapper_code,
mapper_ruta_code,
mo,
model_code,
model_ruta_code,
os,
relaciones_final,
repo_code,
repo_ruta_code,
unicos_final,
):
with open(os.path.join(RUTA_CARPETA_SNIPPETS, "markdown/plantilla_generacion_mmr.md"), "r", encoding="utf-8") as _f:
plantilla = Template(_f.read())
with open(os.path.join(RUTA_CARPETA_SNIPPETS, "markdown/ejemplos_base_mmr.md"), "r", encoding="utf-8") as _f:
ejemplo = _f.read()
# Diccionario con variables
_contexto = {
"ENTIDAD" : entidad, # Nombre de la entidad
"RUTA_REPO_BASE": repo_ruta_code,
"RUTA_MAPPER_BASE": mapper_ruta_code,
"RUTA_MODEL_BASE": model_ruta_code,
"REPO_BASE": repo_code,
"MAPPER_BASE": mapper_code,
"MODEL_BASE": model_code,
"EJEMPLO": ejemplo,
"SCHEMA": esquema.value,
"TABLA": entidad,
"CAMPOS": campos_final.value,
"RELACIONES" : relaciones_final.value,
"INDEX_LIST": indices_final.value,
"UNIQUE_LIST": unicos_final.value,
}
# Pasar el diccionario con **
resultado = plantilla.render(**_contexto)
mo.output.append(mo.md("### Prompt para el modelo:"))
mo.output.append(mo.md(resultado))
return (resultado,)
@app.cell
def _(mo):
run_button_code = mo.ui.run_button(label="Generar código Python", kind="neutral")
run_button_code
return (run_button_code,)
@app.cell
def _(OpenAI, mo, os, resultado, run_button_code):
# from openai import OpenAI
def call_openai_to_code(user_prompt: str) -> str:
"""Call OpenAI and return Python code as plain text."""
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
resp = client.chat.completions.create(
model="gpt-4o", # o "gpt-5" si lo tienes
messages=[
{
"role": "system",
"content": "Eres un asistente estricto que SIEMPRE devuelve codigo python de acuerdo a sus reglas"
},
{"role": "user", "content": user_prompt}
],
temperature=0.2
)
return resp.choices[0].message.content
_status_message = "Pulsa el botón para generar."
respuesta_codigo = None # valor por defecto
if run_button_code.value:
try:
respuesta_codigo = call_openai_to_code(resultado)
_status_message = "Código generado correctamente."
except Exception as exc:
_status_message = f"Error al generar: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {_status_message}"),
mo.md(f"```python\n{respuesta_codigo}\n```") if respuesta_codigo else mo.md("")
])
return (respuesta_codigo,)
@app.cell
def _(entidad, mo, os, respuesta_codigo):
# Botón para guardar
boton_guardar = mo.ui.button(
label="Guardar archivo",
on_click=lambda _: guardar()
)
def guardar():
codigo_limpio = (
respuesta_codigo.strip()
.removeprefix("```python")
.removesuffix("```")
.strip()
)
# Carpeta relativa (ej: ./domains dentro de tu proyecto)
carpeta = "domains"
os.makedirs(carpeta, exist_ok=True)
ruta = os.path.join(carpeta, f"{entidad}.py")
with open(ruta, "w", encoding="utf-8") as f:
f.write(codigo_limpio)
print(f"✅ Archivo guardado en {ruta}")
boton_guardar
return
@app.cell
def _(mo, run_button_db):
from backend.db.session import engine
from backend.db.base import Base
# # -> AQUI IMPORTA TUS MODELOS para que se registren en Base.metadata
# from domains.Clientes import ClientesModel
# from domains.Vehiculos import VehiculosModel
# from domains.ejemplo import ejemploModel
from domains.nota import notaModel
def create_all_tables() -> dict:
"""Create all tables bound to Base.metadata and return a summary."""
# Ensure models are imported so their metadata is registered (done in Cell 1)
Base.metadata.create_all(bind=engine)
created_tables = list(Base.metadata.tables.keys())
return {
"message": "✔ Tablas creadas",
"tables": created_tables,
"count": len(created_tables),
}
_status_message = "Importa tus modelos y pulsa el botón para crear las tablas."
result_payload = None # Default so dependents don't break
if run_button_db.value:
try:
result_payload = create_all_tables()
_status_message = "Operación completada correctamente."
except Exception as exc:
_status_message = f"Error al crear tablas: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {_status_message}"),
mo.json(result_payload) if result_payload is not None else mo.md("")
])
return (engine,)
@app.cell
def _(mo):
run_button_db = mo.ui.run_button(label="Crear tablas en la base de datos", kind="neutral")
mo.vstack([mo.md("Finalmente registrar las tablas en la bbdd"),
run_button_db])
return (run_button_db,)
@app.cell
def _(mo):
# Cell 2
run_button_drop = mo.ui.run_button(label="Eliminar todas las tablas vacías", kind = "danger" )
run_button_drop
return (run_button_drop,)
@app.cell
def _(engine, mo, run_button_drop):
from sqlalchemy import inspect, func, select
from sqlalchemy import MetaData
from sqlalchemy import MetaData, select, exists
def drop_all_empty_tables_improved():
engine.dispose()
metadata = MetaData()
metadata.reflect(bind=engine, resolve_fks=False)
dropped, skipped = [], []
with engine.connect() as connection:
trans = connection.begin()
try:
for table in metadata.sorted_tables:
col = list(table.c)[0] # primera columna para usar en exists()
has_data = connection.execute(
select(exists().where(col != None))
).scalar()
if not has_data:
table.drop(engine, checkfirst=True)
dropped.append(table.name)
else:
skipped.append(table.name)
trans.commit()
except:
trans.rollback()
raise
return {"dropped": dropped, "skipped": skipped}
_status_message = "Pulsa el botón para eliminar tablas vacías."
_result_payload = None
if run_button_drop.value:
try:
_result_payload = drop_all_empty_tables_improved()
_status_message = "Operación completada correctamente."
except Exception as exc:
_status_message = f"Error al eliminar tablas: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {_status_message}"),
mo.json(_result_payload) if _result_payload else mo.md("")
])
return
@app.cell
def _():
# # Obtenemos las variables de nuestro template
# from jinja2 import Environment, meta
# # Crear un entorno de Jinja
# env = Environment()
# with open(os.path.join(RUTA_CARPETA_SNIPPETS, "markdown/plantilla_mmr.md"), "r", encoding="utf-8") as _f:
# source = _f.read()
# # Parsear la plantilla sin renderizar
# parsed_content = env.parse(source)
# # Obtener las variables no declaradas
# variables = meta.find_undeclared_variables(parsed_content)
# print("Variables encontradas:", variables)
return
if __name__ == "__main__":
app.run()