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
+521
View File
@@ -0,0 +1,521 @@
import marimo
__generated_with = "0.15.5"
app = marimo.App(width="columns")
@app.cell
def _():
import marimo as mo
return (mo,)
@app.cell
def _():
from backend.db.session import engine
import pandas as pd
return engine, pd
@app.cell
def _(engine, mo, pd):
# Query directo a information_schema
query_tables = """
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY table_name;
"""
tables_df = pd.read_sql(query_tables, engine)
# Dropdown para elegir tabla
table_selector = mo.ui.dropdown(
options=tables_df["table_name"].tolist(),
label="Selecciona una tabla",
allow_select_none= False,
value=tables_df["table_name"][0]
)
table_selector
return (table_selector,)
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Leer Datos""")
return
@app.cell
def _(
delete_status,
engine,
final_status,
insert_button,
inserted_data,
mo,
pd,
soft_delete_status,
soft_restore_status,
table_selector,
):
inserted_data
final_status
delete_status
soft_delete_status
soft_restore_status
insert_button
query_data = f'SELECT * FROM "{table_selector.value}" LIMIT 1000;'
df_preview = pd.read_sql(query_data, engine)
mo.ui.table(df_preview) # editor visual
return
@app.cell
def _(mo):
get_obj_id_input = mo.ui.number(label="ID a recuperar", value=1)
get_obj_button = mo.ui.run_button(label="Recuperar objeto", kind="neutral")
mo.hstack([get_obj_id_input, get_obj_button])
return get_obj_button, get_obj_id_input
@app.cell
def _(get_obj_button, get_obj_id_input, mo, repo, session, table_selector):
obj_status = "️ Introduce un ID y pulsa *Recuperar*."
obj_python = None
if get_obj_button.value and table_selector.value:
try:
# Usamos el repo → devuelve un dominio (no un dict)
_fetched_obj = repo.get_by_id(get_obj_id_input.value)
if _fetched_obj:
obj_status = f"✅ Objeto recuperado con ID {get_obj_id_input.value}"
obj_python = _fetched_obj
else:
obj_status = f"⚠️ No se encontró registro con ID {get_obj_id_input.value}"
except Exception as exc:
session.rollback()
obj_status = f"❌ Error al recuperar: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {obj_status}"),
mo.inspect(obj_python)
])
return
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Crear datos""")
return
@app.cell(hide_code=True)
def _(engine, pd, table_selector):
from sqlalchemy import text
table_selector
query_columns = text("""
SELECT column_name, data_type, column_default
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = :table_name
AND column_name NOT LIKE 'sys_%'
ORDER BY ordinal_position;
""")
columns_df = pd.read_sql(query_columns, engine, params={"table_name": table_selector.value})
columns_df = columns_df[
~(
(columns_df["column_name"] == "id") &
(columns_df["column_default"].notnull()) &
(columns_df["column_default"].str.contains("nextval"))
)
]
return (columns_df,)
@app.cell
def _(columns_df, mo, table_selector):
# Generamos inputs dinámicamente con soporte para fechas
ui_inputs = {}
for _, row in columns_df.iterrows():
col, dtype = row["column_name"], row["data_type"]
if dtype in ("integer", "numeric", "bigint", "smallint"):
ui_inputs[col] = mo.ui.number(label=col)
elif dtype in ("boolean",):
ui_inputs[col] = mo.ui.checkbox(label=col)
elif dtype in ("date", "timestamp", "timestamp without time zone", "timestamp with time zone"):
ui_inputs[col] = mo.ui.date(label=col) # 👉 usar date picker de Marimo
else:
ui_inputs[col] = mo.ui.text(label=col)
# Botón
run_button_add = mo.ui.run_button(
label=f"Agregar fila a {table_selector.value}",
kind="neutral"
)
# Layout de inputs + botón
mo.vstack(list(ui_inputs.values()) + [run_button_add])
return run_button_add, ui_inputs
@app.cell
def _(MapperCls, mo, repo, run_button_add, table_selector, ui_inputs):
_status_message = "️ Completa los campos y pulsa *Agregar fila*."
inserted_id = None
inserted_data = None
if run_button_add.value and table_selector.value:
try:
# Recoger inputs y normalizar fechas a ISO
raw_data = {}
for _col, ui in ui_inputs.items():
value = ui.value
if hasattr(value, "isoformat"): # 👉 si es date/datetime
raw_data[_col] = value.isoformat()
elif value == "" or value is None: # 👉 convertir '' → None
raw_data[_col] = None
else:
raw_data[_col] = value
# Convertir dict → dominio con el mapper correcto
dominio_obj = MapperCls.from_dict(raw_data)
# Insertar en la tabla usando el repo correcto
inserted_id = repo.add(dominio_obj, created_by="admin")
_status_message = f"✅ Registro insertado en {table_selector.value} con ID: {inserted_id}"
inserted_data = raw_data
except Exception as exc:
_status_message = f"❌ Error al insertar: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {_status_message}"),
mo.json(inserted_data) if inserted_data else mo.md("")
])
return (inserted_data,)
@app.cell
def _(mo):
mo.md(r"""Para añadir un objeto dentro de la base de datos:""")
return
@app.cell
def _(mo):
from domains.nota import notaDom
# Aqui genera tu objeto de python
# objeto_python = notaDom(
# nombre="Prueba",
# descripcion="Registro de prueba insertado desde Python",
# activo=True,
# )
insert_button = mo.ui.run_button(label="Insertar objeto en la bbdd", kind="neutral")
mo.vstack([
# mo.inspect(objeto_python),
insert_button])
return (insert_button,)
@app.cell
def _(ejemploRepo, insert_button, mo, objeto_python, session):
insert_status = "️ Pulsa el botón para insertar el objeto."
nuevo_id = None
if insert_button.value:
try:
# Instancia del repo
repo_objeto = ejemploRepo(session)
# Inserción en la BD
nuevo_id = repo_objeto.add(
objeto_python,
created_by="admin",
notes="Inserción desde código Python"
)
insert_status = f"✅ Registro insertado con ID: {nuevo_id}"
except Exception as exc:
session.rollback()
insert_status = f"❌ Error al insertar: {exc!s}"
mo.md(insert_status)
return
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Actualizar datos""")
return
@app.cell(hide_code=True)
def _(mo):
update_id_input = mo.ui.number(label="ID a actualizar", value=1)
update_fetch_button = mo.ui.run_button(label="Buscar registro", kind="neutral")
confirm_update_button = mo.ui.run_button(label="Confirmar actualización", kind="success")
mo.hstack([update_id_input, update_fetch_button, confirm_update_button])
return confirm_update_button, update_fetch_button, update_id_input
@app.cell
def _(
MapperCls,
mo,
repo,
session,
table_selector,
update_fetch_button,
update_id_input,
):
# Valores iniciales
_update_status = "️ Introduce un ID y pulsa *Buscar*."
update_dic = None
if update_fetch_button.value and table_selector.value:
try:
fetched_obj = repo.get_by_id(update_id_input.value)
if fetched_obj:
fetched_dict = MapperCls.to_dict(fetched_obj)
update_dic = {
col: (
mo.ui.checkbox(value=bool(fetched_dict[col]), label=col)
if isinstance(fetched_dict[col], bool) else
mo.ui.number(value=fetched_dict[col], label=col)
if isinstance(fetched_dict[col], (int, float)) else
mo.ui.date(value=fetched_dict[col], label=col)
if hasattr(fetched_dict[col], "isoformat") else
mo.ui.text(value=str(fetched_dict[col]) if fetched_dict[col] is not None else "", label=col)
)
for col in fetched_dict.keys()
if not col.startswith("sys_") and col != "id"
}
_update_status = f"✅ Registro encontrado con ID: {update_id_input.value}"
else:
_update_status = f"⚠️ No se encontró registro con ID: {update_id_input.value}"
except Exception as exc:
session.rollback()
_update_status = f"❌ Error al buscar: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {_update_status}"),
mo.vstack(list(update_dic.values())) if update_dic else mo.md("")
])
return (update_dic,)
@app.cell(hide_code=True)
def _(confirm_update_button, mo, repo, session, update_dic, update_id_input):
# Estado por defecto
final_status = "️ Esperando confirmación de actualización."
if confirm_update_button.value and update_dic is not None:
try:
updated_data = {}
for _col, _ui in update_dic.items():
val = _ui.value
# Normalización: "" → None
if val == "":
updated_data[_col] = None
# Normalización: fechas → ISO string
elif hasattr(val, "isoformat"):
updated_data[_col] = val.isoformat()
else:
updated_data[_col] = val
ok = repo.update(
update_id_input.value,
updated_data,
updated_by="admin",
notes="Actualización desde UI"
)
if ok:
final_status = f"✅ Registro con ID {update_id_input.value} actualizado correctamente."
else:
final_status = f"⚠️ No se pudo actualizar el registro con ID {update_id_input.value}."
except Exception as exc:
session.rollback()
final_status = f"❌ Error al actualizar: {exc!s}"
mo.md(f"**Estado final:** {final_status}")
return (final_status,)
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Eliminar datos""")
return
@app.cell
def _(mo):
delete_id_input = mo.ui.number(label="ID a eliminar", value=1)
delete_button = mo.ui.run_button(label="Hard delete", kind="danger")
mo.hstack([delete_id_input, delete_button])
return delete_button, delete_id_input
@app.cell
def _(delete_button, delete_id_input, mo, repo, session, table_selector):
delete_status = "️ Introduce un ID y pulsa *Eliminar*."
if delete_button.value and table_selector.value:
try:
_ok = repo.delete_by_id(delete_id_input.value)
if _ok:
delete_status = f"✅ Registro con ID {delete_id_input.value} eliminado permanentemente."
else:
delete_status = f"⚠️ No se encontró registro con ID {delete_id_input.value}."
except Exception as exc:
session.rollback()
delete_status = f"❌ Error al eliminar: {exc!s}"
mo.md(f"**Estado:** {delete_status}")
return (delete_status,)
@app.cell
def _(mo):
soft_delete_id_input = mo.ui.number(label="ID a eliminar (soft)", value=1)
soft_delete_button = mo.ui.run_button(label="Soft delete", kind="warn")
mo.hstack([soft_delete_id_input, soft_delete_button])
return soft_delete_button, soft_delete_id_input
@app.cell
def _(
mo,
repo,
session,
soft_delete_button,
soft_delete_id_input,
table_selector,
):
soft_delete_status = "️ Introduce un ID y pulsa *Soft delete*."
if soft_delete_button.value and table_selector.value:
try:
_ok = repo.soft_delete(
soft_delete_id_input.value,
deleted_by="admin",
notes="Eliminado desde UI"
)
if _ok:
soft_delete_status = f"✅ Registro con ID {soft_delete_id_input.value} marcado como eliminado (soft delete)."
else:
soft_delete_status = f"⚠️ No se encontró registro con ID {soft_delete_id_input.value} o ya estaba eliminado."
except Exception as exc:
session.rollback()
soft_delete_status = f"❌ Error al aplicar soft delete: {exc!s}"
mo.md(f"**Estado:** {soft_delete_status}")
return (soft_delete_status,)
@app.cell
def _(mo):
soft_restore_id_input = mo.ui.number(label="ID a restaurar (soft)", value=1)
soft_restore_button = mo.ui.run_button(label="Restaurar registro", kind="success")
mo.hstack([soft_restore_id_input, soft_restore_button])
return soft_restore_button, soft_restore_id_input
@app.cell
def _(
mo,
repo,
session,
soft_restore_button,
soft_restore_id_input,
table_selector,
):
soft_restore_status = "️ Introduce un ID y pulsa *Restaurar*."
if soft_restore_button.value and table_selector.value:
try:
_ok = repo.soft_restore(
soft_restore_id_input.value,
restored_by="admin",
notes="Restaurado desde UI"
)
if _ok:
soft_restore_status = f"✅ Registro con ID {soft_restore_id_input.value} restaurado correctamente."
else:
soft_restore_status = f"⚠️ No se encontró registro con ID {soft_restore_id_input.value} o no estaba eliminado."
except Exception as exc:
session.rollback()
soft_restore_status = f"❌ Error al restaurar: {exc!s}"
mo.md(f"**Estado:** {soft_restore_status}")
return (soft_restore_status,)
@app.cell
def _():
# # Arregla transacciones rotas
# try:
# _dominio_obj = MapperCls.from_dict(raw_data)
# _inserted_id = repo.add(_dominio_obj, created_by="admin")
# _status_message = f"✅ Insertado con ID {_inserted_id}"
# except Exception as exc:
# session.rollback() # 👈 limpia la transacción rota
# _status_message = f"❌ Error al insertar: {exc!s}"
return
@app.cell(hide_code=True)
def _(table_selector):
import importlib
from backend.db.session import SessionLocal
def load_classes(entidad: str):
"""
Importa dinámicamente Repo, Mapper y Dom según convención:
- Módulo: domains.{entidad} (minúsculas)
- Clases: {EntidadCapitalizada}Repo, {EntidadCapitalizada}Mapper, {EntidadCapitalizada}Dom
"""
entidad = entidad.lower() # siempre minúscula para el módulo
module_name = f"domains.{entidad}"
module = importlib.import_module(module_name)
# Nombre base: capitalizar primera letra
base_name = entidad.lower()
repo_class = getattr(module, f"{base_name}Repo")
mapper_class = getattr(module, f"{base_name}Mapper")
dom_class = getattr(module, f"{base_name}Dom")
return repo_class, mapper_class, dom_class
# 👉 Uso interactivo en Marimo
session = SessionLocal()
if table_selector.value: # valor que escribiste en el textarea
RepoCls, MapperCls, DomCls = load_classes(table_selector.value)
repo = RepoCls(session)
return MapperCls, repo, session
if __name__ == "__main__":
app.run()