diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..21318f4
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+FROM postgres:15
+
+# Variables de entorno útiles
+ENV POSTGRES_USER=postgres \
+ POSTGRES_DB=basededatos
+
+RUN apt-get update && \
+DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
+ gdal-bin \
+ postgresql-15-pgvector \
+ postgresql-15-postgis-3 \
+ postgresql-15-postgis-3-scripts \
+ proj-bin \
+&& rm -rf /var/lib/apt/lists/*
+
+# Copiamos scripts de inicialización
+COPY docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/
diff --git a/Rag_de_textos.py b/Rag_de_textos.py
new file mode 100644
index 0000000..4909dd9
--- /dev/null
+++ b/Rag_de_textos.py
@@ -0,0 +1,318 @@
+import marimo
+
+__generated_with = "0.14.17"
+app = marimo.App()
+
+
+@app.cell
+def _():
+ import marimo as mo
+ return (mo,)
+
+
+@app.cell
+def _():
+ import os
+ import textwrap
+ import yaml
+ import subprocess
+ from pathlib import Path
+
+ # ==== Parámetros ====
+ PUERTO_POSTGRES = 55432
+ SERVICIO = "postgres_ext"
+ PASSWORD = "mipassword"
+ USER = "postgres"
+ DBNAME = "basededatos"
+
+ # Lista de extensiones que quieres habilitar
+ EXTENSIONES = [
+ # builtin
+ "hstore", "citext", "uuid-ossp", "pg_trgm",
+ # requieren paquetes
+ "postgis", "pgvector",
+ # "timescaledb" # <- si quieres usar base de timescaledb, activa la bandera abajo
+ ]
+
+ # Usa imagen base de timescaledb cuando la extensión 'timescaledb' esté en la lista
+ timescaledb_base_image = False # pon True si quieres usar la imagen base de TimescaleDB
+
+ RUTA_PROYECTO = Path(".").resolve()
+
+ return (
+ DBNAME,
+ EXTENSIONES,
+ PASSWORD,
+ PUERTO_POSTGRES,
+ Path,
+ RUTA_PROYECTO,
+ SERVICIO,
+ USER,
+ subprocess,
+ textwrap,
+ timescaledb_base_image,
+ yaml,
+ )
+
+
+@app.function
+def pkgs_para_extensiones(exts, pg_major=15, use_timescale_base=False):
+ """
+ Devuelve (pkgs_apt, builtins) para las extensiones solicitadas.
+ builtins = extensiones que no requieren apt
+ pkgs_apt = paquetes apt necesarios para otras extensiones
+ """
+ builtins = []
+ pkgs_apt = []
+
+ for e in exts:
+ e_low = e.lower()
+ if e_low in {"hstore", "citext", "uuid-ossp", "pg_trgm"}:
+ builtins.append(e_low)
+ elif e_low == "postgis":
+ pkgs_apt += [
+ f"postgresql-{pg_major}-postgis-3",
+ f"postgresql-{pg_major}-postgis-3-scripts",
+ "gdal-bin",
+ "proj-bin",
+ ]
+ elif e_low == "pgvector":
+ pkgs_apt += [f"postgresql-{pg_major}-pgvector"]
+ elif e_low == "timescaledb":
+ if not use_timescale_base:
+ # Para instalar timescaledb necesitarías repos adicionales
+ pass
+ else:
+ raise ValueError(f"Extensión no soportada en este helper: {e}")
+
+ pkgs_apt = sorted(set(pkgs_apt))
+ return pkgs_apt, builtins
+
+
+@app.cell
+def _(DBNAME, Path, USER, textwrap):
+ def generar_dockerfile(ruta: Path, exts, use_timescale_base=False, pg_major=15):
+ ruta.mkdir(parents=True, exist_ok=True)
+ pkgs_apt, builtins = pkgs_para_extensiones(exts, pg_major, use_timescale_base)
+
+ base_image = (
+ f"timescale/timescaledb:2.16-pg{pg_major}" if use_timescale_base else f"postgres:{pg_major}"
+ )
+
+ apt_block = ""
+ if pkgs_apt:
+ apt_lines = [
+ "RUN apt-get update && \\",
+ " DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \\",
+ ]
+ # 👇 Backslash en **todas** las líneas de paquetes
+ for pkg in pkgs_apt:
+ apt_lines.append(f" {pkg} \\")
+ # y ya colgamos el rm del último paquete
+ apt_lines.append(" && rm -rf /var/lib/apt/lists/*")
+ apt_block = "\n".join(apt_lines)
+
+ dockerfile = f"""
+ FROM {base_image}
+
+ # Variables de entorno útiles
+ ENV POSTGRES_USER={USER} \\
+ POSTGRES_DB={DBNAME}
+
+ {apt_block}
+
+ # Copiamos scripts de inicialización
+ COPY docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/
+ """
+ (ruta / "Dockerfile").write_text(textwrap.dedent(dockerfile).strip() + "\n", encoding="utf-8")
+
+ return (generar_dockerfile,)
+
+
+@app.cell
+def _(Path):
+ def generar_init_sql(ruta: Path, exts):
+ init_dir = ruta / "docker-entrypoint-initdb.d"
+ init_dir.mkdir(parents=True, exist_ok=True)
+
+ lines = ["-- Habilitar extensiones solicitadas"]
+ for e in exts:
+ e_low = e.lower()
+ ext_name = {
+ "pgvector": "vector",
+ "postgis": "postgis",
+ "hstore": "hstore",
+ "citext": "citext",
+ "uuid-ossp": "\"uuid-ossp\"",
+ "pg_trgm": "pg_trgm",
+ "timescaledb": "timescaledb",
+ }.get(e_low, e_low)
+ lines.append(f"CREATE EXTENSION IF NOT EXISTS {ext_name};")
+
+ sql = "\n".join(lines) + "\n"
+ (init_dir / "10-extensions.sql").write_text(sql, encoding="utf-8")
+
+ return (generar_init_sql,)
+
+
+@app.cell
+def _(DBNAME, Path, USER, yaml):
+ def crear_docker_compose(ruta: Path, servicio: str, puerto_host: int, password: str):
+ compose = {
+ "version": "3.8",
+ "services": {
+ servicio: {
+ "build": {"context": "."},
+ "restart": "always",
+ "ports": [f"{puerto_host}:5432"],
+ "environment": {
+ "POSTGRES_PASSWORD": password,
+ "POSTGRES_USER": USER,
+ "POSTGRES_DB": DBNAME,
+ },
+ "healthcheck": {
+ "test": ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"],
+ "interval": "10s",
+ "timeout": "5s",
+ "retries": 5,
+ },
+ "volumes": [
+ "postgres_data:/var/lib/postgresql/data"
+ ],
+ }
+ },
+ "volumes": {"postgres_data": {}}
+ }
+ (ruta / "docker-compose.yml").write_text(yaml.dump(compose, sort_keys=False), encoding="utf-8")
+
+ return (crear_docker_compose,)
+
+
+@app.cell
+def _(Path, subprocess):
+ def construir_y_levantar(ruta: Path):
+ def _run(cmd):
+ subprocess.run(cmd, cwd=ruta, check=True)
+
+ try:
+ _run(["docker", "compose", "build"])
+ _run(["docker", "compose", "up", "-d"])
+ except Exception:
+ _run(["docker-compose", "build"])
+ _run(["docker-compose", "up", "-d"])
+
+ return (construir_y_levantar,)
+
+
+@app.cell
+def _(
+ EXTENSIONES,
+ PASSWORD,
+ PUERTO_POSTGRES,
+ RUTA_PROYECTO,
+ SERVICIO,
+ construir_y_levantar,
+ crear_docker_compose,
+ generar_dockerfile,
+ generar_init_sql,
+ timescaledb_base_image,
+):
+ if __name__ == "__main__":
+ RUTA_PROYECTO.mkdir(parents=True, exist_ok=True)
+ generar_dockerfile(RUTA_PROYECTO, EXTENSIONES, timescaledb_base_image)
+ generar_init_sql(RUTA_PROYECTO, EXTENSIONES)
+ crear_docker_compose(RUTA_PROYECTO, SERVICIO, PUERTO_POSTGRES, PASSWORD)
+ construir_y_levantar(RUTA_PROYECTO)
+ return
+
+
+@app.cell
+def _(PUERTO_POSTGRES):
+ from sqlalchemy import create_engine
+ from sqlalchemy.engine import Engine
+ from urllib.parse import quote_plus
+
+ def conectar_postgres(
+ host: str = "localhost",
+ port: int = PUERTO_POSTGRES,
+ dbname: str = "basededatos",
+ user: str = "postgres",
+ password: str = "mipassword"
+ ) -> Engine:
+ """
+ Devuelve un objeto SQLAlchemy Engine conectado a PostgreSQL.
+ Compatible con pandas.to_sql y read_sql.
+ """
+ pwd = quote_plus(password) # Escapar caracteres especiales del password
+ url = f"postgresql+psycopg2://{user}:{pwd}@{host}:{port}/{dbname}"
+
+ engine = create_engine(
+ url,
+ pool_size=5,
+ max_overflow=10,
+ future=True
+ )
+ return engine
+
+ return Engine, conectar_postgres
+
+
+@app.cell
+def _(Engine):
+ import pandas as pd
+
+ def consultar_df(conn: Engine, query: str, params: dict | None = None) -> pd.DataFrame:
+ """
+ Ejecuta una consulta SQL usando SQLAlchemy y devuelve los resultados como un DataFrame.
+ - conn: Engine devuelto por conectar_postgres()
+ - query: str con la consulta SQL
+ - params: dict opcional con parámetros de la consulta
+ """
+ return pd.read_sql(query, con=conn, params=params)
+
+ return (consultar_df,)
+
+
+@app.cell
+def _(conectar_postgres, consultar_df):
+ conn = conectar_postgres()
+
+ query = """
+ SELECT *
+ FROM information_schema.tables
+
+ -- WHERE table_type = 'BASE TABLE'
+ -- AND table_schema NOT IN ('pg_catalog', 'information_schema')
+
+ ORDER BY table_schema, table_name;
+ """
+
+ consultar_df(conn, query)
+ return (conn,)
+
+
+@app.cell
+def _(conn, mo):
+ _df = mo.sql(
+ f"""
+ SELECT * FROM information_schema.tables
+ """,
+ engine=conn
+ )
+ return
+
+
+@app.cell
+def _(mo):
+ mo.md(r"""# Generacion de esquemas con sqlalchemy""")
+ return
+
+
+@app.cell
+def _():
+ return
+
+
+if __name__ == "__main__":
+ app.run()
+
diff --git a/__marimo__/session/Rag_de_textos.py.json b/__marimo__/session/Rag_de_textos.py.json
new file mode 100644
index 0000000..a227945
--- /dev/null
+++ b/__marimo__/session/Rag_de_textos.py.json
@@ -0,0 +1,259 @@
+{
+ "version": "1",
+ "metadata": {
+ "marimo_version": "0.14.17"
+ },
+ "cells": [
+ {
+ "id": "Hbol",
+ "code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "Jbtt",
+ "code_hash": "fde985cc15f0093552fff888b240816c",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "plNZ",
+ "code_hash": "b2c1f26f82d1f970b9d43928212acde9",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "oZMf",
+ "code_hash": "b0b5cf4dac9ad1d2d8aa2d44ed0f20ce",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "mSjp",
+ "code_hash": "eccad4a6b0e5c3fd744d0203ad46d0a9",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "AUBn",
+ "code_hash": "5e55408f8b81b90732752beb11570e05",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "FHCW",
+ "code_hash": "11c9307c9267a2df95f9264fff9e1206",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "ybCH",
+ "code_hash": "a5bd6b98ad6169496877f80431f7c095",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": [
+ {
+ "type": "stream",
+ "name": "stderr",
+ "text": "time=\"2025-08-19T15:16:04+02:00\" level=warning msg=\"/home/lucas/DataProyects/pdf_extraccion/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion\"\n"
+ },
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "#1 [internal] load local bake definitions\n"
+ },
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "#1 reading from stdin 569B done\n#1 DONE 0.0s\n"
+ },
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "\n#2 [internal] load build definition from Dockerfile\n#2 transferring dockerfile: 505B done\n#2 DONE 0.0s\n\n#3 [internal] load metadata for docker.io/library/postgres:15\n#3 DONE 0.0s\n\n#4 [internal] load .dockerignore\n#4 transferring context: 2B done\n#4 DONE 0.0s\n"
+ },
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "\n#5 [1/3] FROM docker.io/library/postgres:15@sha256:bc51cf4f1fe02cce7ed2370b20128a9b00b4eb804573a77d2a0d877aaa9c82b1\n#5 resolve docker.io/library/postgres:15@sha256:bc51cf4f1fe02cce7ed2370b20128a9b00b4eb804573a77d2a0d877aaa9c82b1 0.0s done\n#5 DONE 0.0s\n\n#6 [internal] load build context\n#6 transferring context: 401B done\n#6 DONE 0.0s\n\n#7 [2/3] RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends gdal-bin postgresql-15-pgvector postgresql-15-postgis-3 postgresql-15-postgis-3-scripts proj-bin && rm -rf /var/lib/apt/lists/*\n#7 CACHED\n\n#8 [3/3] COPY docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/\n#8 CACHED\n\n#9 exporting to image\n#9 exporting layers done\n#9 exporting manifest sha256:ddf2aa5c1127ef36f0583f757538ec9d9188c522cdf7192fd3c46873d403abb7 0.0s done\n#9 exporting config sha256:c2aa2387ddcb66d7f496ed5a6efd9be5da5cc966b3dcacfd23c58c1caefdf806 0.0s done\n#9 exporting attestation manifest sha256:7c1b9488e66ca92e2104f30369539c7fd0d9b9fb42f84ae97cc88cccbd118f86 0.0s done\n#9 exporting manifest list sha256:8f9fc603b962916902c09a3d1ecef8a7c50def8cb9346642e8a7c9ae2033fcf7\n"
+ },
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "#9 exporting manifest list sha256:8f9fc603b962916902c09a3d1ecef8a7c50def8cb9346642e8a7c9ae2033fcf7 0.0s done\n#9 naming to docker.io/library/pdf_extraccion-postgres_ext:latest done\n#9 unpacking to docker.io/library/pdf_extraccion-postgres_ext:latest done\n#9 DONE 0.1s\n\n#10 resolving provenance for metadata file\n"
+ },
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "#10 DONE 0.0s\n"
+ },
+ {
+ "type": "stream",
+ "name": "stderr",
+ "text": " pdf_extraccion-postgres_ext Built\n"
+ },
+ {
+ "type": "stream",
+ "name": "stderr",
+ "text": "time=\"2025-08-19T15:16:05+02:00\" level=warning msg=\"/home/lucas/DataProyects/pdf_extraccion/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion\"\n"
+ },
+ {
+ "type": "stream",
+ "name": "stderr",
+ "text": " Network pdf_extraccion_default Creating\n"
+ },
+ {
+ "type": "stream",
+ "name": "stderr",
+ "text": " Network pdf_extraccion_default Created\n Volume \"pdf_extraccion_postgres_data\" Creating\n Volume \"pdf_extraccion_postgres_data\" Created\n"
+ },
+ {
+ "type": "stream",
+ "name": "stderr",
+ "text": " Container pdf_extraccion-postgres_ext-1 Creating\n"
+ },
+ {
+ "type": "stream",
+ "name": "stderr",
+ "text": " Container pdf_extraccion-postgres_ext-1 Created\n"
+ },
+ {
+ "type": "stream",
+ "name": "stderr",
+ "text": " Container pdf_extraccion-postgres_ext-1 Starting\n"
+ },
+ {
+ "type": "stream",
+ "name": "stderr",
+ "text": " Container pdf_extraccion-postgres_ext-1 Started\n"
+ }
+ ]
+ },
+ {
+ "id": "lHhD",
+ "code_hash": "0bb04e3bef0447e54fef3eac8f7f3117",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "kkmm",
+ "code_hash": "28ff78f15580f50ed3fc3cf6779825b6",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "uOeO",
+ "code_hash": "36e82227d7318e471f358e2772315864",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "kNuO",
+ "code_hash": "6ffd13eedba1a63ac0a20ab64b8f7e8c",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "PIZD",
+ "code_hash": "73a56a298e50653cdd8a2f3c836b0a13",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "Generacion de esquemas con sqlalchemy
"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "BWdf",
+ "code_hash": null,
+ "outputs": [],
+ "console": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/__marimo__/session/prompts_plantillas.py.json b/__marimo__/session/prompts_plantillas.py.json
new file mode 100644
index 0000000..91411c9
--- /dev/null
+++ b/__marimo__/session/prompts_plantillas.py.json
@@ -0,0 +1,27 @@
+{
+ "version": "1",
+ "metadata": {
+ "marimo_version": "0.14.17"
+ },
+ "cells": [
+ {
+ "id": "Hbol",
+ "code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "LPNa",
+ "code_hash": null,
+ "outputs": [],
+ "console": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/__marimo__/session/prueba_excel_again.py.json b/__marimo__/session/prueba_excel_again.py.json
new file mode 100644
index 0000000..3805305
--- /dev/null
+++ b/__marimo__/session/prueba_excel_again.py.json
@@ -0,0 +1,137 @@
+{
+ "version": "1",
+ "metadata": {
+ "marimo_version": "0.14.17"
+ },
+ "cells": [
+ {
+ "id": "Hbol",
+ "code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "MJUe",
+ "code_hash": "36193dfa033cd2be8e7b1bc67c72eb92",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "No se ha introducido ningun excel"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "vblA",
+ "code_hash": "072dd5735109eb97e01dc1c8dd2ac33b",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": ""
+ }
+ }
+ ],
+ "console": [
+ {
+ "type": "stream",
+ "name": "stderr",
+ "text": "/home/lucas/DataProyects/pdf_extraccion/.venv/lib/python3.10/site-packages/openpyxl/styles/stylesheet.py:237: UserWarning: Workbook contains no default style, apply openpyxl's default\n warn(\"Workbook contains no default style, apply openpyxl's default\")\n"
+ }
+ ]
+ },
+ {
+ "id": "bkHC",
+ "code_hash": "b73d872652e4a8e4937dec04d65b4456",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "lEQa",
+ "code_hash": "3a6c948a1f859a38537bc684e5f430f5",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "
"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "PKri",
+ "code_hash": "fa2b5880fdfd5de20113e8e50090e744",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "Excel: Ventas_Diarias_AURGI_-310125.xlsxHoja: Informe
"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "Xref",
+ "code_hash": "94bec97921afbb4d3f0d66d6359780b1",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "SFPL",
+ "code_hash": "603dca0774218b4898894b77707d4173",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "BYtC",
+ "code_hash": "83ccf5086147a1c5d5c6d80e90319b63",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "
"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "tMil",
+ "code_hash": null,
+ "outputs": [],
+ "console": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/__marimo__/session/sqlalchemy_inicializacion.py.json b/__marimo__/session/sqlalchemy_inicializacion.py.json
new file mode 100644
index 0000000..12207ab
--- /dev/null
+++ b/__marimo__/session/sqlalchemy_inicializacion.py.json
@@ -0,0 +1,85 @@
+{
+ "version": "1",
+ "metadata": {
+ "marimo_version": "0.14.17"
+ },
+ "cells": [
+ {
+ "id": "Hbol",
+ "code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "MJUe",
+ "code_hash": "0df0bfd7ff72ff9c8e46d08b9cafe41f",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "SQLAlchemy 1: Inicializaci\u00f3n del backend
"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "vblA",
+ "code_hash": "431dc7f7109c8c3f501ddfb63cd22792",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "Aqui vamos a generar la Base y la session de el backend usando SQLAlchemy, que usar\u00e1n todos los objetos que se registren en la bbdd ademas de las capas de arquitecture_layer que hereden nuestras clases"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "bkHC",
+ "code_hash": "1b2dbc3e0e1a520310bb37cab8c2b812",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": [
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "\u2139\ufe0f /home/lucas/DataProyects/pdf_extraccion/backend/.env ya tiene todas las claves necesarias.\n\u2705 Creado /home/lucas/DataProyects/pdf_extraccion/backend/db/base.py\n\u2705 Creado /home/lucas/DataProyects/pdf_extraccion/backend/db/session.py\n"
+ }
+ ]
+ },
+ {
+ "id": "lEQa",
+ "code_hash": "8a9301f35990da46a5875fb8c2d57351",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": [
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "\u2705 /home/lucas/DataProyects/Snippets_marimo_noteebooks/utils/ArquitectureLayer/Repo.py \u2192 domains/arquitecture_layer/Repo.py\n\u2705 /home/lucas/DataProyects/Snippets_marimo_noteebooks/utils/ArquitectureLayer/Mapper.py \u2192 domains/arquitecture_layer/Mapper.py\n\u2705 /home/lucas/DataProyects/Snippets_marimo_noteebooks/utils/ArquitectureLayer/Model.py \u2192 domains/arquitecture_layer/Model.py\n\u2705 Creado domains/arquitecture_layer/__init__.py\n"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/__marimo__/session/sqlalchemy_prompts_mmr.py.json b/__marimo__/session/sqlalchemy_prompts_mmr.py.json
new file mode 100644
index 0000000..2739cf8
--- /dev/null
+++ b/__marimo__/session/sqlalchemy_prompts_mmr.py.json
@@ -0,0 +1,195 @@
+{
+ "version": "1",
+ "metadata": {
+ "marimo_version": "0.14.17"
+ },
+ "cells": [
+ {
+ "id": "Hbol",
+ "code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "MJUe",
+ "code_hash": "c4f05c85676b46ca0fe7e6ba2c7451cb",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "SQLAlchemy 2: generacion de DB_MMR con LLMs
"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "vblA",
+ "code_hash": "7869775eccc51f1498a040ec3dd84bd0",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "Aqui podremos hacer que el LLM pueda generar objetos y sus conexiones con la base de datos de forma que generemos un backend s\u00f3lido y consistente. La idea es que con esto generemos la plantilla que podamos pasarle al LLM para que \u00e9l genere el c\u00f3digo de manera organizada"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "bkHC",
+ "code_hash": "e0801e8126ea6513ac1888571363f0c0",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "lEQa",
+ "code_hash": "1e0fb1180758d2b29ee2b3266ab63169",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "Definimos la entidad o entidades a generar y su descripcion para generar los campos usando un llm y su plantilla para la bbdd"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "PKri",
+ "code_hash": "b34c8a33b26c85ee9344fb55d9b11301",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "Xref",
+ "code_hash": "030b2a7d1cf6c50221fdfed467780a24",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": [
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "\n\ud83d\udccc Nombre de dominio: **productos**\n\n\ud83d\udcdd Explicaci\u00f3n: \nItems para una tienda online\n\n---\n\ud83c\udfaf **Objetivo del prompt** \nGenera una lista de posibles campos para el modelo **productos**, en el siguiente formato:\n\n# Formato sugerido por l\u00ednea:\n# nombre_columna: tipo_python | tipo_sqlalchemy(opcional) | nullable={True/False} | default={valor/opcional} | comentarios\n# - Ya contienen : columnas sys_* (created_at, updated_at, etc.), __json__, etc.\n\nEjemplo esperado:\n\n```\n\nnombre: str | String(120) | nullable=False | comentarios=\"Nombre del productos\"\ndescripcion: str | Text | nullable=True | default=None | comentarios=\"Descripci\u00f3n opcional\"\nactivo: bool | Boolean | nullable=False | default=True | comentarios=\"Si el registro est\u00e1 activo\"\n\n```\n\n---\n\ud83d\udd01 Posibles relaciones en formato:\n```\n\n\"RELACIONES\": \"\",\n\n# Ej.: cliente_id -> FK a ventas.clientes.id (ondelete='RESTRICT'),\n\"RELACIONES\": \"usuario_id -> FK a seguridad.usuarios.id (ondelete='CASCADE')\"\n\n```\n\n---\n\ud83d\udcd1 Posibles \u00edndices y restricciones:\n```\n\n\"INDEX\\_LIST\": \"\", # Opcional\n\"UNIQUE\\_LIST\": \"\", # Opcional\n\n```\n\n\u26a1 Nota: Usa tu criterio para proponer campos, relaciones y restricciones que sean coherentes con el dominio **productos**.\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "yeSV",
+ "code_hash": "e26c00e88371c15203ce806dc5b41aeb",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "SFPL",
+ "code_hash": "47a5275bf6221951a2342125bca26d3e",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": [
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "Te paso mis clases base para que heredes correctamente:\n\n1) Model_base \n\n- Codigo de la clase Model: \n```\n# Model.py\nfrom sqlalchemy import Column, DateTime, String, Integer, Text, BigInteger, func\nfrom sqlalchemy.ext.declarative import declared_attr, as_declarative\nfrom datetime import datetime\nfrom backend.db.base import Base # tu Base declarativa\n\nclass Model_base(Base):\n __abstract__ = True\n\n # ID autoincremental por defecto en todos los modelos\n id = Column(BigInteger, primary_key=True, autoincrement=True)\n\n @declared_attr\n def sys_created_at(cls):\n # timestamptz + default en BD\n return Column(DateTime(timezone=True), server_default=func.now(), nullable=False)\n\n @declared_attr\n def sys_created_by(cls):\n return Column(String, nullable=True)\n\n @declared_attr\n def sys_updated_at(cls):\n # onupdate lo pone la BD al actualizar\n return Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)\n\n @declared_attr\n def sys_updated_by(cls):\n return Column(String, nullable=True)\n\n @declared_attr\n def sys_version(cls):\n return Column(Integer, default=1, nullable=False)\n\n @declared_attr\n def sys_notes(cls):\n return Column(Text, nullable=True)\n\n @declared_attr\n def sys_deleted_at(cls):\n return Column(DateTime(timezone=True), nullable=True)\n\n def __repr__(self):\n id_val = getattr(self, \"id\", None)\n return f\"<{self.__class__.__name__} id={id_val}>\"\n\n def __str__(self):\n cls = self.__class__.__name__\n id_val = getattr(self, \"id\", None)\n return f\"{cls}(id={id_val})\"\n\n def __json__(self) -> dict:\n out = {}\n if not hasattr(self, \"__table__\"): return out\n for c in self.__table__.columns:\n v = getattr(self, c.name, None)\n out[c.name] = v.isoformat() if isinstance(v, datetime) else v\n return out\n```\n\n- Ruta de import: domains.arquitecture_layer.Repo import Repo_base\n- Contiene: columnas sys_* (created_at, updated_at, etc.), __json__, etc.\n\n2) Mapper_base\n\n- Codigo de la clase Model: \n```\n# Mapper.py\n\nfrom abc import ABC, abstractmethod\nfrom typing import TypeVar, Generic, Type\nimport json\n\nTDominio = TypeVar(\"TDominio\")\nTModelo = TypeVar(\"TModelo\")\n\nclass Mapper_base(ABC, Generic[TDominio, TModelo]):\n # ----------------------------\n # Conversiones individuales\n # ----------------------------\n\n @staticmethod\n @abstractmethod\n def to_model(obj: TDominio) -> TModelo:\n \"\"\"Convierte objeto de dominio a modelo ORM\"\"\"\n pass\n\n @staticmethod\n @abstractmethod\n def from_model(model: TModelo) -> TDominio:\n \"\"\"Convierte modelo ORM a objeto de dominio\"\"\"\n pass\n\n @staticmethod\n @abstractmethod\n def to_dict(obj: TDominio) -> dict:\n \"\"\"Convierte objeto de dominio a diccionario plano\"\"\"\n pass\n\n @staticmethod\n @abstractmethod\n def from_dict(d: dict) -> TDominio:\n \"\"\"Convierte diccionario plano a objeto de dominio\"\"\"\n pass\n\n @classmethod\n def to_json(cls, obj: TDominio) -> str:\n \"\"\"Convierte objeto de dominio a JSON string\"\"\"\n return json.dumps(cls.to_dict(obj), default=str)\n\n @classmethod\n def from_json(cls, json_str: str) -> TDominio:\n \"\"\"Convierte JSON string a objeto de dominio\"\"\"\n return cls.from_dict(json.loads(json_str))\n\n # ----------------------------\n # Conversiones en lote (bulk)\n # ----------------------------\n\n @classmethod\n def to_model_list(cls, objs: list[TDominio]) -> list[TModelo]:\n return [cls.to_model(o) for o in objs]\n\n @classmethod\n def from_model_list(cls, models: list[TModelo]) -> list[TDominio]:\n return [cls.from_model(m) for m in models]\n\n @classmethod\n def to_dict_list(cls, objs: list[TDominio]) -> list[dict]:\n return [cls.to_dict(o) for o in objs]\n\n @classmethod\n def from_dict_list(cls, dicts: list[dict]) -> list[TDominio]:\n return [cls.from_dict(d) for d in dicts]\n\n @classmethod\n def to_json_list(cls, objs: list[TDominio]) -> str:\n return json.dumps(cls.to_dict_list(objs), default=str)\n\n @classmethod\n def from_json_list(cls, json_str: str) -> list[TDominio]:\n return cls.from_dict_list(json.loads(json_str))\n```\n\n- Ruta de import: domains.arquitecture_layer.Mapper import Mapper_base\n\n3) Repo_base\n- Codigo de la clase Model: \n```\n# Repo.py\n\nfrom abc import ABC\nfrom typing import Type, TypeVar, Generic, Optional\nfrom sqlalchemy.orm import Session\nfrom sqlalchemy import func\nfrom datetime import datetime\nfrom datetime import datetime, timezone\n\nfrom .Mapper import Mapper_base # Aseg\u00farate de importar tu ABC base\n\nTModelo = TypeVar(\"TModelo\")\nTDominio = TypeVar(\"TDominio\")\n\nclass Repo_base(ABC, Generic[TModelo, TDominio]):\n def __init__(self, session: Session, modelo: type[TModelo], mapper: type[Mapper_base[TDominio, TModelo]]):\n self.session = session\n self.Modelo = modelo\n self.Mapper = mapper\n\n # ----------------------\n # ADD\n # ----------------------\n\n def add(self, dominio: TDominio, created_by: Optional[str] = None, notes: Optional[str] = None) -> str:\n data = self.Mapper.to_dict(dominio)\n data.update({\n \"sys_created_by\": created_by,\n \"sys_notes\": notes,\n \"sys_version\": 1\n })\n model = self.Modelo(**data)\n self.session.add(model)\n self.session.commit()\n return model.id\n\n def add_many(self, dominios: list[TDominio], created_by: Optional[str] = None, notes: Optional[str] = None) -> list[str]:\n ids = []\n for dominio in dominios:\n data = self.Mapper.to_dict(dominio)\n data.update({\n \"sys_created_by\": created_by,\n \"sys_notes\": notes,\n \"sys_version\": 1\n })\n model = self.Modelo(**data)\n self.session.add(model)\n ids.append(model.id)\n self.session.commit()\n return ids\n\n # ----------------------\n # GET\n # ----------------------\n\n def get_by_id(self, id_: str) -> Optional[TDominio]:\n model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()\n return self.Mapper.from_model(model) if model else None\n\n def get_all(self) -> list[TDominio]:\n models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).all()\n return self.Mapper.from_model_list(models)\n\n def get_paginated(self, offset: int = 0, limit: int = 10) -> list[TDominio]:\n models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).offset(offset).limit(limit).all()\n return self.Mapper.from_model_list(models)\n\n def get_deleted(self) -> list[TDominio]:\n models = self.session.query(self.Modelo).filter(self.Modelo.sys_deleted_at.isnot(None)).all()\n return self.Mapper.from_model_list(models)\n\n # ----------------------\n # UPDATE\n # ----------------------\n\n def update(self, id_: str, new_data: dict, updated_by: Optional[str] = None, notes: Optional[str] = None) -> bool:\n model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()\n if not model:\n return False\n\n for key, value in new_data.items():\n if hasattr(model, key):\n setattr(model, key, value)\n\n model.sys_updated_by = updated_by\n model.sys_notes = notes\n model.sys_version = (model.sys_version or 1) + 1\n self.session.commit()\n return True\n\n def bulk_update(self, updates: list[tuple[str, dict]], updated_by: Optional[str] = None, notes: Optional[str] = None) -> int:\n count = 0\n for id_, data in updates:\n if self.update(id_, data, updated_by=updated_by, notes=notes):\n count += 1\n return count\n\n # ----------------------\n # DELETE\n # ----------------------\n\n def delete_by_id(self, id_: str) -> bool:\n model = self.session.query(self.Modelo).filter_by(id=id_).first()\n if model:\n self.session.delete(model)\n self.session.commit()\n return True\n return False\n\n def delete_all(self) -> int:\n deleted = self.session.query(self.Modelo).delete()\n self.session.commit()\n return deleted\n\n # ----------------------\n # SOFT DELETE\n # ----------------------\n\n def soft_delete(self, id_: str, deleted_by: Optional[str] = None, notes: Optional[str] = None) -> bool:\n model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()\n if model:\n model.sys_deleted_at = datetime.now(timezone.utc) # TZ-aware\n model.sys_updated_by = deleted_by\n model.sys_notes = notes\n model.sys_version = (model.sys_version or 1) + 1\n self.session.commit()\n return True\n return False\n\n def soft_restore(self, id_: str, restored_by: Optional[str] = None, notes: Optional[str] = None) -> bool:\n model = self.session.query(self.Modelo).filter_by(id=id_).first()\n if model and model.sys_deleted_at is not None:\n model.sys_deleted_at = None\n model.sys_updated_by = restored_by\n model.sys_notes = notes\n model.sys_version = (model.sys_version or 1) + 1\n self.session.commit()\n return True\n return False\n\n # ----------------------\n # OTROS\n # ----------------------\n\n def exists(self, id_: str) -> bool:\n return self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first() is not None\n\n def count(self) -> int:\n return self.session.query(self.Modelo).filter_by(sys_deleted_at=None).count()\n\n```\n\n- Ruta de import: from domains.arquitecture_layer.Model import Model_base\n\n\ud83e\udde9 Tarea:\nGenera en **un solo archivo** las 3 piezas siguientes para la entidad **productos**:\n- Dominio (dataclass): productosDom\n- Modelo ORM (SQLAlchemy): productosModel\n- Mapper concreto: productosMapper\n- Repo concreto (especializaci\u00f3n del gen\u00e9rico): productosRepo\n\nAqui puedes ver como hacerlo con este ejemplo: \n\n```python\nfrom pydantic import BaseModel, Field\n\nclass ItemIn(BaseModel):\n nombre: str = Field(min_length=1)\n\nclass ItemOut(ItemIn):\n id: int\n\n##############################\n\nfrom sqlalchemy import Column, String\nfrom backend.db.model_base import Model_base\n\nclass Item(Model_base):\n __tablename__ = \"item\" # opcional; si no, usa el nombre por defecto\n __table_args__ = {\"schema\": \"public\"}\n nombre = Column(String, nullable=False, index=True)\n\n##############################\n\nfrom domains.ejemplo.domain import ItemIn, ItemOut\nfrom domains.ejemplo.model import Item\n\nclass ItemMapper:\n @staticmethod\n def to_model(d: ItemIn) -> Item:\n return Item(nombre=d.nombre)\n\n @staticmethod\n def from_model(m: Item) -> ItemOut:\n return ItemOut(id=m.id, nombre=m.nombre)\n\n @staticmethod\n def to_dict(d: ItemIn) -> dict:\n return {\"nombre\": d.nombre}\n\n @staticmethod\n def from_dict(data: dict) -> ItemIn:\n return ItemIn(**data)\n\n\n#############################\n\n\nfrom typing import Optional\nfrom sqlalchemy.orm import Session\nfrom domains.ejemplo.model import Item\nfrom domains.ejemplo.mapper import ItemMapper\nfrom domains.arquitecture_layer.Repo import Repo_base # tu base\n\nclass ItemRepo(Repo_base[Item, \"ItemIn|ItemOut\"]):\n def __init__(self, session: Session):\n super().__init__(session=session, modelo=Item, mapper=ItemMapper)\n\n```\n\n\n\ud83e\uddf1 Esquema y tabla\n- schema: public (ej. \"ventas\")\n- __tablename__: productos (ej. \"clientes\")\n\n\ud83d\uddc2\ufe0f Campos (excluyendo sys_*, que ya aporta Model_base):\n\n\nid: int | Integer | nullable=False | comentarios=\"PK del producto\"\nsku: str | String(64) | nullable=False | comentarios=\"C\u00f3digo \u00fanico del producto\"\nnombre: str | String(200) | nullable=False | comentarios=\"Nombre del producto\"\ndescripcion: str | Text | nullable=True | default=None | comentarios=\"Descripci\u00f3n detallada\"\nprecio: Decimal | Numeric(12,2) | nullable=False | default=0.00 | comentarios=\"Precio base\"\nstock: int | Integer | nullable=False | default=0 | comentarios=\"Unidades disponibles\"\nactivo: bool | Boolean | nullable=False | default=True | comentarios=\"Si el producto est\u00e1 activo\"\n\n\n\n\ud83d\udd11 Clave primaria\n- Usa el ID autoincremental por defecto si est\u00e1 disponible. Si no, crea una columna id BigInteger autoincremental.\n\n\ud83d\udd17 Restricciones / \u00edndices (opcional)\n- uniques: uq_productos_sku (sku)\n- indexes: idx_productos_nombre (nombre); idx_productos_sku (sku)\n\n\ud83d\udd01 Relaciones (opcional)\n\n\n\ud83d\udd52 Zona horaria\n- Mant\u00e9n timestamps timezone-aware (ya lo hace Model_base).\n\n\ud83c\udfaf Reglas de salida / estilo\n- **Devuelve SOLO c\u00f3digo Python v\u00e1lido** (un \u00fanico archivo), sin comentarios fuera del c\u00f3digo ni explicaciones.\n- Importa exactamente desde mis rutas:\n from domains.arquitecture_layer.Repo import Repo_base import Model_base\n from domains.arquitecture_layer.Mapper import Mapper_base import Mapper_base\n from from domains.arquitecture_layer.Model import Model_base import Repo_base\n- Tipado fuerte (Python 3.10+), `from __future__ import annotations` opcional.\n- Dominio como `@dataclass` -> `productosDom`.\n- Modelo ORM hereda de `Model_base` y (si existe) `IdIntMixin`.\n- Usa `__table_args__ = {\"schema\": \"public\"}`.\n- Tipos SQLAlchemy razonables (String(N), Integer, Numeric(p,s), Boolean, DateTime, JSON, etc.).\n- En `Mapper`:\n - `to_model`, `from_model`, `to_dict`, `from_dict` (respetando tipos; Numeric -> float en dominio).\n- `Repo`:\n - Clase `productosRepo(Repo_base[productosModel, productosDom])` con `__init__(self, session)` que fija `Modelo` y `Mapper`.\n - A\u00f1ade 1\u20133 m\u00e9todos de consulta comunes (ej.: `get_by_nombre`, `buscar_por_rango_gasto`, paginado por pa\u00eds), usando `self.session`.\n- No generes `create_all` ni conexi\u00f3n.\n- No dupliques columnas sys_*.\n- Mant\u00e9n nombre de columnas igual a las llaves del dominio y del dict del mapper.\n\n\ud83d\udce6 Salida esperada (estructura del archivo):\n- imports\n- dataclass de dominio `productosDom`\n- clase ORM `productosModel`\n- clase `productosMapper`\n- clase `productosRepo`\n\n\nGenera ahora el archivo `.py` final.\n"
+ }
+ ]
+ },
+ {
+ "id": "RGSE",
+ "code_hash": "a1204eb54a53c4ed520511cd637e8d2a",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "Conexion con bbdd
"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "Kclp",
+ "code_hash": "d19ff2ccfb6db6a077b4575908247605",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": [
+ {
+ "type": "stream",
+ "name": "stdout",
+ "text": "\u2714 Tablas creadas\n"
+ }
+ ]
+ },
+ {
+ "id": "emfo",
+ "code_hash": "efa869609408ddb6b406bf9d23dcd7bc",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/html": "Falta por generar los bloques de codigos o prompt/templates que me ayuden a generar los usos con Repo y la logica para el Dominio
"
+ }
+ }
+ ],
+ "console": []
+ },
+ {
+ "id": "BYtC",
+ "code_hash": "87ee1ef84a53699c468cc44edb65111c",
+ "outputs": [
+ {
+ "type": "data",
+ "data": {
+ "text/plain": ""
+ }
+ }
+ ],
+ "console": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..5edc732
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,23 @@
+version: '3.8'
+services:
+ postgres_ext:
+ build:
+ context: .
+ restart: always
+ ports:
+ - 55432:5432
+ environment:
+ POSTGRES_PASSWORD: mipassword
+ POSTGRES_USER: postgres
+ POSTGRES_DB: basededatos
+ healthcheck:
+ test:
+ - CMD-SHELL
+ - pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+volumes:
+ postgres_data: {}
diff --git a/docker-entrypoint-initdb.d/10-extensions.sql b/docker-entrypoint-initdb.d/10-extensions.sql
new file mode 100644
index 0000000..d63eb53
--- /dev/null
+++ b/docker-entrypoint-initdb.d/10-extensions.sql
@@ -0,0 +1,7 @@
+-- Habilitar extensiones solicitadas
+CREATE EXTENSION IF NOT EXISTS hstore;
+CREATE EXTENSION IF NOT EXISTS citext;
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+CREATE EXTENSION IF NOT EXISTS pg_trgm;
+CREATE EXTENSION IF NOT EXISTS postgis;
+CREATE EXTENSION IF NOT EXISTS vector;