diff --git a/Pruebas_notas.ipynb b/Pruebas_notas.ipynb new file mode 100644 index 0000000..e3d91bf --- /dev/null +++ b/Pruebas_notas.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 8, + "id": "255345d5", + "metadata": {}, + "outputs": [], + "source": [ + "from src.TextManager.biblioteca import Biblioteca\n", + "from src.TextManager.biblioteca_mmr import BibliotecaRepo\n", + "from src.Llms.Embedders.Openai_embedder import OpenAIEmbedder\n", + "from src.ApiKeys.openai_apikey_mmr import OpenAICredencialRepo\n", + "from src.ConexionSql.Postgres_conexion import PostgresConexion\n", + "from src.TextManager.nota import Nota\n", + "from src.TextManager.notas_biblioteca_mmr import generar_tabla_nota_para_biblioteca, NotaRepo\n", + "from sqlalchemy import MetaData\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b414a66c", + "metadata": {}, + "outputs": [], + "source": [ + "def agregar_nota_a_biblioteca(\n", + " conexion: PostgresConexion,\n", + " biblioteca_id: str,\n", + " titulo: str,\n", + " texto: str = \"\",\n", + " tags: list[str] = None,\n", + " conexiones: list[str] = None,\n", + " resumen: str = \"\"\n", + "):\n", + " print(\"[INFO] Iniciando el proceso de agregar nota a la biblioteca...\")\n", + "\n", + " # Obtener la biblioteca\n", + " print(f\"[INFO] Buscando biblioteca con ID: {biblioteca_id}\")\n", + " repo_biblioteca = BibliotecaRepo(conexion)\n", + " biblioteca = repo_biblioteca.get_by_id(biblioteca_id)\n", + " if biblioteca is None:\n", + " print(f\"[ERROR] No se encontró la biblioteca con ID {biblioteca_id}\")\n", + " raise ValueError(f\"No se encontró la biblioteca con ID {biblioteca_id}\")\n", + " print(f\"[INFO] Biblioteca encontrada: {biblioteca.nombre} (vector_dim={biblioteca.vector_dim})\")\n", + "\n", + " # Crear objeto Nota\n", + " print(f\"[INFO] Creando objeto Nota con título: {titulo}\")\n", + " nota = Nota(\n", + " titulo=titulo,\n", + " texto=texto,\n", + " tags=tags or [],\n", + " conexiones=conexiones or [],\n", + " resumen=resumen or \"\",\n", + " # vector=biblioteca.embedder.embed_text(texto),\n", + " # vector_resumen=biblioteca.embedder.embed_text(resumen) if resumen else None\n", + " )\n", + " # Mostrar atributos seguros\n", + " print(\n", + " f\"[DEBUG] Nota creada: titulo='{nota.titulo}', \"\n", + " f\"texto_len={len(nota.texto)}, \"\n", + " f\"tags={len(nota.tags)}, \"\n", + " f\"conexiones={len(nota.conexiones)}, \"\n", + " f\"resumen_len={len(nota.resumen)}\"\n", + " )\n", + "\n", + " # Preparar tabla y modelo de nota\n", + " print(f\"[INFO] Generando tabla y modelo de Nota para la biblioteca: {biblioteca.nombre}\")\n", + " metadata = MetaData()\n", + " tabla, ModeloNota = generar_tabla_nota_para_biblioteca(\n", + " biblioteca.nombre,\n", + " biblioteca.vector_dim,\n", + " metadata\n", + " )\n", + " print(f\"[INFO] Creando tabla en la base de datos si no existe...\")\n", + " metadata.create_all(conexion.get_engine())\n", + " print(f\"[INFO] Tabla '{tabla.name}' verificada/creada.\")\n", + "\n", + " # Guardar la nota\n", + " print(f\"[INFO] Guardando nota en la base de datos...\")\n", + " repo_nota = NotaRepo(conexion.get_session(), ModeloNota)\n", + " nota_id = repo_nota.add(nota)\n", + " print(f\"[INFO] Nota guardada con ID: {nota_id}\")\n", + "\n", + " resultado = {\n", + " \"mensaje\": f\"Nota '{titulo}' agregada con éxito a la biblioteca '{biblioteca.nombre}'.\",\n", + " \"nota_id\": nota_id\n", + " }\n", + "\n", + " print(f\"[SUCCESS] {resultado['mensaje']}\")\n", + " return resultado\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "8e57e511", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] Iniciando el proceso de agregar nota a la biblioteca...\n", + "[INFO] Buscando biblioteca con ID: BBLI20250511-a91dbb2168172979414\n", + "[INFO] Biblioteca encontrada: biblio_Pruebas_1 (vector_dim=3072)\n", + "[INFO] Creando objeto Nota con título: sajdhasjdhasjdh\n", + "[DEBUG] Nota creada: titulo='sajdhasjdhasjdh', texto_len=0, tags=0, conexiones=0, resumen_len=0\n", + "[INFO] Generando tabla y modelo de Nota para la biblioteca: biblio_Pruebas_1\n", + "[INFO] Creando tabla en la base de datos si no existe...\n", + "[INFO] Tabla 'biblio_pruebas_1' verificada/creada.\n", + "[INFO] Guardando nota en la base de datos...\n", + "[INFO] Nota guardada con ID: NOTA20250511-04dbb203a9126228444\n", + "[SUCCESS] Nota 'sajdhasjdhasjdh' agregada con éxito a la biblioteca 'biblio_Pruebas_1'.\n" + ] + }, + { + "data": { + "text/plain": [ + "{'mensaje': \"Nota 'sajdhasjdhasjdh' agregada con éxito a la biblioteca 'biblio_Pruebas_1'.\",\n", + " 'nota_id': 'NOTA20250511-04dbb203a9126228444'}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from entrypoint.init_db import db_credencial\n", + "conexion_admin = PostgresConexion(db_credencial)\n", + "\n", + "agregar_nota_a_biblioteca(\n", + " conexion=conexion_admin,\n", + " biblioteca_id=\"BBLI20250511-a91dbb2168172979414\",\n", + " titulo=\"sajdhasjdhasjdh\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "431f24f1", + "metadata": {}, + "outputs": [], + "source": [ + "def listar_notas_de_biblioteca(conexion: PostgresConexion, biblioteca_id: str) -> list[dict]:\n", + " repo_biblioteca = BibliotecaRepo(conexion)\n", + " biblioteca = repo_biblioteca.get_by_id(biblioteca_id)\n", + " if not biblioteca:\n", + " raise ValueError(f\"No se encontró la biblioteca con ID: {biblioteca_id}\")\n", + "\n", + " metadata = MetaData()\n", + " tabla, ModeloNota = generar_tabla_nota_para_biblioteca(biblioteca.nombre, biblioteca.vector_dim, metadata)\n", + " metadata.create_all(conexion.get_engine())\n", + "\n", + " repo_nota = NotaRepo(conexion.get_session(), ModeloNota)\n", + " notas = repo_nota.get_all()\n", + " return [\n", + " {\n", + " \"id\": n.id,\n", + " \"titulo\": n.titulo,\n", + " \"tags\": n.tags,\n", + " \"texto\": n.texto,\n", + " \"resumen\": n.resumen,\n", + " \"conexiones\": n.conexiones\n", + " }\n", + " for n in notas\n", + " ]" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ae4f2994", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "E:\\Fitz_Studio\\src\\TextManager\\notas_biblioteca_mmr.py:51: SAWarning: This declarative base already contains a class with the same class name and module name as src.TextManager.notas_biblioteca_mmr.NotaModel, and will be replaced in the string-lookup table.\n", + " mapper_registry.map_imperatively(NotaModel, tabla)\n" + ] + }, + { + "ename": "TypeError", + "evalue": "'NoneType' object is not iterable", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mTypeError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[14]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mlistar_notas_de_biblioteca\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 2\u001b[39m \u001b[43m \u001b[49m\u001b[43mconexion\u001b[49m\u001b[43m=\u001b[49m\u001b[43mconexion_admin\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 3\u001b[39m \u001b[43m \u001b[49m\u001b[43mbiblioteca_id\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mBBLI20250511-a91dbb2168172979414\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\n\u001b[32m 4\u001b[39m \u001b[43m)\u001b[49m\n", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[13]\u001b[39m\u001b[32m, line 12\u001b[39m, in \u001b[36mlistar_notas_de_biblioteca\u001b[39m\u001b[34m(conexion, biblioteca_id)\u001b[39m\n\u001b[32m 9\u001b[39m metadata.create_all(conexion.get_engine())\n\u001b[32m 11\u001b[39m repo_nota = NotaRepo(conexion.get_session(), ModeloNota)\n\u001b[32m---> \u001b[39m\u001b[32m12\u001b[39m notas = \u001b[43mrepo_nota\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget_all\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 13\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m [\n\u001b[32m 14\u001b[39m {\n\u001b[32m 15\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mid\u001b[39m\u001b[33m\"\u001b[39m: n.id,\n\u001b[32m (...)\u001b[39m\u001b[32m 22\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m n \u001b[38;5;129;01min\u001b[39;00m notas\n\u001b[32m 23\u001b[39m ]\n", + "\u001b[36mFile \u001b[39m\u001b[32mE:\\Fitz_Studio\\src\\TextManager\\notas_biblioteca_mmr.py:109\u001b[39m, in \u001b[36mget_all\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 0\u001b[39m \n", + "\u001b[36mFile \u001b[39m\u001b[32mE:\\Fitz_Studio\\src\\TextManager\\notas_biblioteca_mmr.py:109\u001b[39m, in \u001b[36m\u001b[39m\u001b[34m(.0)\u001b[39m\n\u001b[32m 0\u001b[39m \n", + "\u001b[36mFile \u001b[39m\u001b[32mE:\\Fitz_Studio\\src\\TextManager\\notas_biblioteca_mmr.py:82\u001b[39m, in \u001b[36mfrom_model\u001b[39m\u001b[34m(model)\u001b[39m\n\u001b[32m 76\u001b[39m \u001b[38;5;129m@staticmethod\u001b[39m\n\u001b[32m 77\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mfrom_model\u001b[39m(model) -> Nota:\n\u001b[32m 78\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m Nota(\n\u001b[32m 79\u001b[39m \u001b[38;5;28mid\u001b[39m=model.id,\n\u001b[32m 80\u001b[39m titulo=model.titulo,\n\u001b[32m 81\u001b[39m tags=model.tags.split(\u001b[33m\"\u001b[39m\u001b[33m,\u001b[39m\u001b[33m\"\u001b[39m) \u001b[38;5;28;01mif\u001b[39;00m model.tags \u001b[38;5;28;01melse\u001b[39;00m [],\n\u001b[32m---> \u001b[39m\u001b[32m82\u001b[39m conexiones=model.conexiones.split(\u001b[33m\"\u001b[39m\u001b[33m,\u001b[39m\u001b[33m\"\u001b[39m) \u001b[38;5;28;01mif\u001b[39;00m model.conexiones \u001b[38;5;28;01melse\u001b[39;00m [],\n\u001b[32m 83\u001b[39m texto=model.texto,\n\u001b[32m 84\u001b[39m resumen=model.resumen,\n\u001b[32m 85\u001b[39m vector=\u001b[38;5;28mlist\u001b[39m(model.vector),\n\u001b[32m 86\u001b[39m vector_resumen=\u001b[38;5;28mlist\u001b[39m(model.vector_resumen) \u001b[38;5;28;01mif\u001b[39;00m model.vector_resumen \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 87\u001b[39m )\n", + "\u001b[31mTypeError\u001b[39m: 'NoneType' object is not iterable" + ] + } + ], + "source": [ + "listar_notas_de_biblioteca(\n", + " conexion=conexion_admin,\n", + " biblioteca_id=\"BBLI20250511-a91dbb2168172979414\"\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/backend/api/v1/endpoints/text_manager_endpoint.py b/backend/api/v1/endpoints/text_manager_endpoint.py index f497391..6b15650 100644 --- a/backend/api/v1/endpoints/text_manager_endpoint.py +++ b/backend/api/v1/endpoints/text_manager_endpoint.py @@ -1,21 +1,99 @@ -# backend/api/v1/biblioteca.py -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel +from typing import List, Optional +from fastapi import Path + + from backend.db.conexion import get_conexion -from backend.services.text_manager import crear_biblioteca +from backend.services.text_manager import ( + crear_biblioteca, + listar_bibliotecas, + agregar_nota_a_biblioteca, + listar_notas_de_biblioteca +) from src.ConexionSql.Postgres_conexion import PostgresConexion router = APIRouter() +# --------------------------- +# MODELOS PARA BIBLIOTECAS +# --------------------------- + class BibliotecaInput(BaseModel): nombre_biblioteca: str descripcion: str -@router.post("/") +@router.post("/", summary="Crear una nueva biblioteca") def crear_biblioteca_endpoint( data: BibliotecaInput, conexion: PostgresConexion = Depends(get_conexion) ): - return crear_biblioteca(nombre_biblioteca=data.nombre_biblioteca, - descripcion=data.descripcion, - conexion=conexion) + try: + return crear_biblioteca( + nombre_biblioteca=data.nombre_biblioteca, + descripcion=data.descripcion, + conexion=conexion + ) + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail="Error interno al crear la biblioteca") + +@router.get("/list", summary="Listar todas las bibliotecas") +def listar_todas_bibliotecas( + conexion: PostgresConexion = Depends(get_conexion) +): + try: + return listar_bibliotecas(conexion=conexion) + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail="Error interno al listar las bibliotecas") + +# --------------------------- +# MODELOS PARA NOTAS +# --------------------------- + +class NotaInput(BaseModel): + titulo: str + texto: str = "" + tags: Optional[List[str]] = [] + conexiones: Optional[List[str]] = [] + resumen: Optional[str] = "" + + + +@router.post("/nota/{biblioteca_id}", summary="Agregar una nota a una biblioteca") +def agregar_nota( + biblioteca_id: str = Path(..., description="ID de la biblioteca a la que se agregará la nota"), + nota: NotaInput = ..., # viene del body + conexion: PostgresConexion = Depends(get_conexion) +): + try: + return agregar_nota_a_biblioteca( + conexion=conexion, + biblioteca_id=biblioteca_id, + titulo=nota.titulo, + texto=nota.texto, + tags=nota.tags, + conexiones=nota.conexiones, + resumen=nota.resumen + ) + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail="Error interno al agregar la nota") + + + +@router.get("/nota/list/{biblioteca_id}", summary="Listar todas las notas de una biblioteca") +def listar_notas( + biblioteca_id: str, + conexion: PostgresConexion = Depends(get_conexion) +): + try: + return listar_notas_de_biblioteca(conexion, biblioteca_id) + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail="Error interno al listar las notas") diff --git a/backend/services/text_manager.py b/backend/services/text_manager.py index fdc5e64..2ac1916 100644 --- a/backend/services/text_manager.py +++ b/backend/services/text_manager.py @@ -3,8 +3,9 @@ from src.TextManager.biblioteca_mmr import BibliotecaRepo from src.Llms.Embedders.Openai_embedder import OpenAIEmbedder from src.ApiKeys.openai_apikey_mmr import OpenAICredencialRepo from src.ConexionSql.Postgres_conexion import PostgresConexion -from backend.db.conexion import get_conexion - +from src.TextManager.nota import Nota +from src.TextManager.notas_biblioteca_mmr import generar_tabla_nota_para_biblioteca, NotaRepo +from sqlalchemy import MetaData def crear_biblioteca(nombre_biblioteca: str, conexion: PostgresConexion, descripcion: str): cred_repo = OpenAICredencialRepo(conexion) @@ -26,5 +27,106 @@ def crear_biblioteca(nombre_biblioteca: str, conexion: PostgresConexion, descrip } -if __name__ == "__main__": - crear_biblioteca("hola_intento5") \ No newline at end of file +def listar_bibliotecas(conexion: PostgresConexion) -> list[dict]: + repo = BibliotecaRepo(conexion) + bibliotecas: list[Biblioteca] = repo.get_all() + return [ + { + "id": b.id, + "nombre": b.nombre, + "descripcion": b.descripcion, + "vector_dim": b.vector_dim + } + for b in bibliotecas + ] + +def agregar_nota_a_biblioteca( + conexion: PostgresConexion, + biblioteca_id: str, + titulo: str, + texto: str = "", + tags: list[str] = None, + conexiones: list[str] = None, + resumen: str = "" +): + print("[INFO] Iniciando el proceso de agregar nota a la biblioteca...") + + # Obtener la biblioteca + print(f"[INFO] Buscando biblioteca con ID: {biblioteca_id}") + repo_biblioteca = BibliotecaRepo(conexion) + biblioteca = repo_biblioteca.get_by_id(biblioteca_id) + if biblioteca is None: + print(f"[ERROR] No se encontró la biblioteca con ID {biblioteca_id}") + raise ValueError(f"No se encontró la biblioteca con ID {biblioteca_id}") + print(f"[INFO] Biblioteca encontrada: {biblioteca.nombre} (vector_dim={biblioteca.vector_dim})") + + # Crear objeto Nota + print(f"[INFO] Creando objeto Nota con título: {titulo}") + nota = Nota( + titulo=titulo, + texto=texto, + tags=tags or [], + conexiones=conexiones or [], + resumen=resumen or "", + # vector=biblioteca.embedder.embed_text(texto), + # vector_resumen=biblioteca.embedder.embed_text(resumen) if resumen else None + ) + # Mostrar atributos seguros + print( + f"[DEBUG] Nota creada: titulo='{nota.titulo}', " + f"texto_len={len(nota.texto)}, " + f"tags={len(nota.tags)}, " + f"conexiones={len(nota.conexiones)}, " + f"resumen_len={len(nota.resumen)}" + ) + + # Preparar tabla y modelo de nota + print(f"[INFO] Generando tabla y modelo de Nota para la biblioteca: {biblioteca.nombre}") + metadata = MetaData() + tabla, ModeloNota = generar_tabla_nota_para_biblioteca( + biblioteca.nombre, + biblioteca.vector_dim, + metadata + ) + print(f"[INFO] Creando tabla en la base de datos si no existe...") + metadata.create_all(conexion.get_engine()) + print(f"[INFO] Tabla '{tabla.name}' verificada/creada.") + + # Guardar la nota + print(f"[INFO] Guardando nota en la base de datos...") + repo_nota = NotaRepo(conexion.get_session(), ModeloNota) + nota_id = repo_nota.add(nota) + print(f"[INFO] Nota guardada con ID: {nota_id}") + + resultado = { + "mensaje": f"Nota '{titulo}' agregada con éxito a la biblioteca '{biblioteca.nombre}'.", + "nota_id": nota_id + } + + print(f"[SUCCESS] {resultado['mensaje']}") + return resultado + + +def listar_notas_de_biblioteca(conexion: PostgresConexion, biblioteca_id: str) -> list[dict]: + repo_biblioteca = BibliotecaRepo(conexion) + biblioteca = repo_biblioteca.get_by_id(biblioteca_id) + if not biblioteca: + raise ValueError(f"No se encontró la biblioteca con ID: {biblioteca_id}") + + metadata = MetaData() + tabla, ModeloNota = generar_tabla_nota_para_biblioteca(biblioteca.nombre, biblioteca.vector_dim, metadata) + metadata.create_all(conexion.get_engine()) + + repo_nota = NotaRepo(conexion.get_session(), ModeloNota) + notas = repo_nota.get_all() + return [ + { + "id": n.id, + "titulo": n.titulo, + "tags": n.tags, + "texto": n.texto, + "resumen": n.resumen, + "conexiones": n.conexiones + } + for n in notas + ] \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e0d9c2a..36a8836 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "@react-three/fiber": "^9.1.2", "@tabler/icons": "^3.31.0", "@tabler/icons-react": "^3.31.0", + "axios": "^1.9.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-rnd": "^10.5.2", @@ -3304,6 +3305,12 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -3330,6 +3337,17 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -3552,7 +3570,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3713,6 +3730,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4053,6 +4082,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4127,7 +4165,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4279,7 +4316,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4289,7 +4325,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4334,7 +4369,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4347,7 +4381,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5061,6 +5094,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -5094,6 +5147,42 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5133,7 +5222,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5184,7 +5272,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5218,7 +5305,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5413,7 +5499,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5492,7 +5577,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5505,7 +5589,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -5521,7 +5604,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6579,7 +6661,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7556,6 +7637,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 92d8500..93319b7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "@react-three/fiber": "^9.1.2", "@tabler/icons": "^3.31.0", "@tabler/icons-react": "^3.31.0", + "axios": "^1.9.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-rnd": "^10.5.2", diff --git a/frontend/src/Router.tsx b/frontend/src/Router.tsx index e4f3c49..46408a3 100644 --- a/frontend/src/Router.tsx +++ b/frontend/src/Router.tsx @@ -3,6 +3,7 @@ import { HomePage } from './pages/Home.page'; import { Consulta_API } from './pages/Consulta_api'; import { Error_404 } from './pages/404'; // Ajusta si está en otra carpeta import { Grid_Dashboard } from './pages/Grid_dashboard'; // Ajusta si está en otra carpeta +import { Biblioteca } from './pages/Biblioteca'; const router = createBrowserRouter([ { @@ -17,7 +18,10 @@ const router = createBrowserRouter([ path: '/Grid_Dashboard', element: , }, - + { + path: '/Biblioteca', + element: , + }, { diff --git a/frontend/src/components/Biblioteca.tsx b/frontend/src/components/Biblioteca.tsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/data/submenuLinks_1.ts b/frontend/src/data/submenuLinks_1.ts index 8142585..9dd4182 100644 --- a/frontend/src/data/submenuLinks_1.ts +++ b/frontend/src/data/submenuLinks_1.ts @@ -4,6 +4,7 @@ export const submenuLinks = { Home: [ { label: 'Inicio', to: '/' }, { label: 'Consulta Api', to: '/Consulta_API' }, + { label: 'Biblioteca', to: '/Biblioteca' }, ], Dashboard: [ diff --git a/frontend/src/pages/Biblioteca.tsx b/frontend/src/pages/Biblioteca.tsx new file mode 100644 index 0000000..f098079 --- /dev/null +++ b/frontend/src/pages/Biblioteca.tsx @@ -0,0 +1,169 @@ +import { useEffect, useState } from 'react'; +import { + AppShell, + Stack, + Card, + Text, + Title, + ScrollArea, + Group, + Button, + TextInput, + Modal, + Box, + Loader, +} from '@mantine/core'; +import { AppShellWithMenu } from '../components/Appshell/Appshell'; +import axios from 'axios'; + +type Nota = { + id: string; + titulo: string; + texto: string; +}; + +type Biblioteca = { + id: string; + nombre: string; + descripcion: string; + notas: Nota[]; +}; + +export function Biblioteca() { + const [bibliotecas, setBibliotecas] = useState([]); + const [bibliotecaSeleccionada, setBibliotecaSeleccionada] = useState(null); + const [modalAbierto, setModalAbierto] = useState(false); + const [tituloNota, setTituloNota] = useState(''); + const [contenidoNota, setContenidoNota] = useState(''); + const [loadingNotas, setLoadingNotas] = useState(false); + + const fetchBibliotecas = async () => { + try { + const res = await axios.get('/api/v1/text_manager/list'); + console.log('📦 Respuesta del backend:', res.data); + + if (!Array.isArray(res.data)) { + console.error('❌ La respuesta no es un array:', res.data); + return; + } + + const bibliotecasConNotas = await Promise.all( + res.data.map(async (biblio: Omit) => { + const notas = await axios.get(`/api/v1/text_manager/nota/list/${biblio.id}`); + return { ...biblio, notas: notas.data as Nota[] }; + }) + ); + setBibliotecas(bibliotecasConNotas); + setBibliotecaSeleccionada(bibliotecasConNotas[0] || null); + } catch (error) { + console.error('Error al cargar bibliotecas:', error); + } + }; + + const agregarNota = async () => { + if (!bibliotecaSeleccionada) return; + + try { + await axios.post(`/api/v1/text_manager/nota/${bibliotecaSeleccionada.id}`, { + titulo: tituloNota, + texto: contenidoNota, + tags: [], + conexiones: [], + resumen: '', + }); + + setLoadingNotas(true); + const nuevasNotas = await axios.get(`/api/v1/text_manager/nota/list/${bibliotecaSeleccionada.id}`); + const nuevasBibliotecas = bibliotecas.map((b) => + b.id === bibliotecaSeleccionada.id ? { ...b, notas: nuevasNotas.data as Nota[] } : b + ); + setBibliotecas(nuevasBibliotecas); + setBibliotecaSeleccionada(nuevasBibliotecas.find((b) => b.id === bibliotecaSeleccionada.id) || null); + setTituloNota(''); + setContenidoNota(''); + setModalAbierto(false); + } catch (error) { + console.error('Error al agregar nota:', error); + } finally { + setLoadingNotas(false); + } + }; + + useEffect(() => { + fetchBibliotecas(); + }, []); + + return ( + + + + + + {bibliotecas.map((biblio) => ( + + ))} + + + + + + {bibliotecaSeleccionada ? ( + + {bibliotecaSeleccionada.nombre} + + + + + + {loadingNotas ? ( + + ) : ( + bibliotecaSeleccionada.notas.map((nota) => ( + + {nota.titulo} + {nota.texto} + + )) + )} + + + ) : ( + + Selecciona una biblioteca + + + )} + + + + setModalAbierto(false)} title="Agregar nueva nota"> + + setTituloNota(event.currentTarget.value)} + /> + setContenidoNota(event.currentTarget.value)} + /> + + + + + ); +} diff --git a/frontend/vite.config.js b/frontend/vite.config.js index a11f2e1..0ba7128 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -5,6 +5,15 @@ import svgr from 'vite-plugin-svgr'; export default defineConfig({ plugins: [react(), tsconfigPaths(), svgr()], + server: { + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + secure: false, + }, + }, + }, test: { globals: true, environment: 'jsdom', diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 268c12a..88f21c0 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1224,6 +1224,11 @@ async-function@^1.0.0: resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz" integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz" @@ -1236,6 +1241,15 @@ axe-core@^4.10.0: resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz" integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg== +axios@^1.9.0: + version "1.9.0" + resolved "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz" + integrity sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz" @@ -1446,6 +1460,13 @@ colord@^2.9.3: resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -1641,6 +1662,11 @@ define-properties@^1.1.3, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@^2.0.0, depd@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" @@ -2273,6 +2299,11 @@ flatted@^3.2.9, flatted@^3.3.3: resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-each@^0.3.3, for-each@^0.3.5: version "0.3.5" resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz" @@ -2288,6 +2319,16 @@ foreground-child@^3.1.0: cross-spawn "^7.0.6" signal-exit "^4.0.1" +form-data@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz" + integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" @@ -3162,6 +3203,18 @@ mime-db@^1.54.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime-types@^3.0.0, mime-types@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz" @@ -3586,6 +3639,11 @@ proxy-addr@^2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" diff --git a/src/TextManager/biblioteca.py b/src/TextManager/biblioteca.py index 29112d0..a43b732 100644 --- a/src/TextManager/biblioteca.py +++ b/src/TextManager/biblioteca.py @@ -4,6 +4,8 @@ from typing import List, Optional from src.ConexionSql.Base_conexion import ConexionBase from sqlalchemy import MetaData # Asegúrate de importar esto from src.TextManager.notas_biblioteca_mmr import generar_tabla_nota_para_biblioteca # Ajusta si es necesario +from sqlalchemy import inspect + class Biblioteca: def __init__( @@ -24,7 +26,7 @@ class Biblioteca: :param vector_dim: Dimensión del vector si no se proporciona un embedder. """ self.id = id if id is not None else GeneradorIDUnico("BBLI").generar() - self.nombre = nombre + self.nombre = nombre if "biblio" in nombre else f"biblio_{nombre}" self.descripcion = descripcion self.embedder = embedder @@ -38,14 +40,16 @@ class Biblioteca: def generar_modelo_notas(self, conexion: ConexionBase): """ Genera dinámicamente un modelo de notas asociado a esta biblioteca y lo crea en la base de datos. - - :param conexion: Objeto de conexión a la base de datos. - :return: Clase del modelo SQLAlchemy correspondiente a las notas. + Previene la creación si la tabla ya existe. """ - nombre_tabla = f"biblio_{self.nombre}" - - metadata = MetaData() + nombre_tabla = f"{self.nombre}" engine = conexion.get_engine() + + inspector = inspect(engine) + if inspector.has_table(nombre_tabla): + raise ValueError(f"Ya existe una tabla con el nombre '{nombre_tabla}' en la base de datos.") + + metadata = MetaData() tabla, NotaModel = generar_tabla_nota_para_biblioteca(nombre_tabla, self.vector_dim, metadata) metadata.create_all(engine) - return NotaModel + return NotaModel \ No newline at end of file diff --git a/src/TextManager/biblioteca_mmr.py b/src/TextManager/biblioteca_mmr.py index 814b256..cc0e5bb 100644 --- a/src/TextManager/biblioteca_mmr.py +++ b/src/TextManager/biblioteca_mmr.py @@ -26,8 +26,8 @@ if pssword is None: class BibliotecaModel(Base): __tablename__ = "bibliotecas" - id = Column(String, primary_key=True) - nombre = Column(String, nullable=False) + id = Column(String, primary_key=True, unique=True) + nombre = Column(String, nullable=False, unique=True) descripcion = Column(String, default="") vector_dim = Column(Integer, nullable=False) embedder_info = Column(String, nullable=True) # Se puede guardar nombre de clase o config encriptada @@ -77,6 +77,11 @@ class BibliotecaRepo: self.session = conexion.get_session() def add(self, biblioteca: Biblioteca) -> str: + # Verificar si ya existe una biblioteca con el mismo nombre + existente = self.session.query(BibliotecaModel).filter_by(nombre=biblioteca.nombre).first() + if existente: + raise ValueError(f"Ya existe una biblioteca con el nombre '{biblioteca.nombre}'") + data = BibliotecaMapper.to_dict(biblioteca) model = BibliotecaModel(**data) self.session.add(model) diff --git a/src/TextManager/notas_biblioteca_mmr.py b/src/TextManager/notas_biblioteca_mmr.py index 2d13ca5..1e946c5 100644 --- a/src/TextManager/notas_biblioteca_mmr.py +++ b/src/TextManager/notas_biblioteca_mmr.py @@ -33,8 +33,8 @@ def generar_tabla_nota_para_biblioteca(biblioteca_nombre: str, vector_dim: int, Column("conexiones", String), Column("texto", String), Column("resumen", String), - Column("vector", Vector(vector_dim), nullable=False), - Column("vector_resumen", Vector(vector_dim), nullable=True), # AHORA ES OPCIONAL + Column("vector", Vector(vector_dim), nullable=True), + Column("vector_resumen", Vector(vector_dim), nullable=True), ) class NotaModel: @@ -46,9 +46,12 @@ def generar_tabla_nota_para_biblioteca(biblioteca_nombre: str, vector_dim: int, self.texto = nota.texto self.resumen = nota.resumen self.vector = nota.vector - self.vector_resumen = getattr(nota, "vector_resumen", None) # AHORA ES OPCIONAL + self.vector_resumen = getattr(nota, "vector_resumen", None) + + # Evitar mapear dos veces la misma clase + if NotaModel not in mapper_registry._class_registry.values(): + mapper_registry.map_imperatively(NotaModel, tabla) - mapper_registry.map_imperatively(NotaModel, tabla) return tabla, NotaModel @@ -79,7 +82,7 @@ class NotaMapper: conexiones=model.conexiones.split(",") if model.conexiones else [], texto=model.texto, resumen=model.resumen, - vector=list(model.vector), + vector=list(model.vector) if model.vector is not None else None, vector_resumen=list(model.vector_resumen) if model.vector_resumen is not None else None ) @@ -89,6 +92,8 @@ class NotaMapper: class NotaRepo: def __init__(self, session: Session, modelo_nota): + if modelo_nota is None: + raise ValueError("No se puede instanciar NotaRepo sin un modelo válido de nota.") self.session = session self.ModeloNota = modelo_nota