From 82c9f03e7961730f2c2b87ab42c100082f9adad7 Mon Sep 17 00:00:00 2001 From: Gorka Reguero Date: Tue, 3 Feb 2026 15:56:08 +0100 Subject: [PATCH 1/2] conexion de la base de datos de mongoDB con el front --- README.md | 5 +- front/.env.local | 1 + front/package.json | 4 +- front/src/app/embalse/[embalse]/page.tsx | 21 +++-- front/src/lib/mongodb.ts | 26 ++++++ .../components/reservoir-card-gauge.tsx | 14 ++-- front/src/pods/embalse/embalse-mock-data.ts | 1 + front/src/pods/embalse/embalse.component.tsx | 24 ++++-- front/src/pods/embalse/embalse.mapper.ts | 51 ++++++++++++ front/src/pods/embalse/embalse.pod.tsx | 10 +-- front/src/pods/embalse/embalse.repository.ts | 33 ++++++++ front/src/pods/embalse/embalse.vm.ts | 1 + package-lock.json | 81 +++++++++++-------- 13 files changed, 208 insertions(+), 64 deletions(-) create mode 100644 front/.env.local create mode 100644 front/src/lib/mongodb.ts create mode 100644 front/src/pods/embalse/embalse.mapper.ts create mode 100644 front/src/pods/embalse/embalse.repository.ts diff --git a/README.md b/README.md index 6046f49..bc05842 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,8 @@ Antes de arrancar las funciones de Azure, asegurate de tener instaladas su cli. Para instalarlo en Mac, ```bash -brew tap azure functions - -brew install azure-functions-core-tools@4 + brew tap azure/functions + brew install azure-functions-core-tools@4 ``` > Ojo tienes que tener homebrew instalado. diff --git a/front/.env.local b/front/.env.local new file mode 100644 index 0000000..70b982f --- /dev/null +++ b/front/.env.local @@ -0,0 +1 @@ +MONGODB_CONNECTION_STRING=mongodb://localhost:27017/embalse-info \ No newline at end of file diff --git a/front/package.json b/front/package.json index d7ccf80..d6b8b54 100644 --- a/front/package.json +++ b/front/package.json @@ -14,7 +14,9 @@ "postcss": "^8.5.6", "react": "^19.1.0", "react-dom": "^19.1.0", - "tailwindcss": "^4.1.17" + "tailwindcss": "^4.1.17", + "mongodb": "^6.12.0", + "db-model": "file:../packages/db-model" }, "devDependencies": { "@types/react": "^19.1.8", diff --git a/front/src/app/embalse/[embalse]/page.tsx b/front/src/app/embalse/[embalse]/page.tsx index 86a6d43..bc5b605 100644 --- a/front/src/app/embalse/[embalse]/page.tsx +++ b/front/src/app/embalse/[embalse]/page.tsx @@ -1,11 +1,22 @@ +import { notFound } from "next/navigation"; import { EmbalsePod } from "@/pods/embalse"; - +import { getEmbalseBySlug } from "@/pods/embalse/embalse.repository"; +import { mapEmbalseToReservoirData } from "@/pods/embalse/embalse.mapper"; +export const revalidate = 300; // ISR: regenerar cada 5 minutos interface Props { params: Promise<{ embalse: string }>; } - export default async function EmbalseDetallePage({ params }: Props) { + /** + * Llamamos a getEmbalseBySlug con el slug de la URL. + Si no se encuentra el embalse, llamamos a notFound() que muestra la pagina 404 de Next.js + */ const { embalse } = await params; - - return ; -} + const embalseDoc = await getEmbalseBySlug(embalse); + if (!embalseDoc) { + notFound(); + } + //mapeamos el documento a ReservoirData y lo pasamos al pod + const reservoirData = mapEmbalseToReservoirData(embalseDoc); + return ; +} \ No newline at end of file diff --git a/front/src/lib/mongodb.ts b/front/src/lib/mongodb.ts new file mode 100644 index 0000000..fd51aa2 --- /dev/null +++ b/front/src/lib/mongodb.ts @@ -0,0 +1,26 @@ +import { MongoClient, type Db } from "mongodb"; + +//lee la URL de MongoDB de las variables de entorno +const connectionString = process.env.MONGODB_CONNECTION_STRING; +if (!connectionString) { + throw new Error( + "Please define the MONGODB_CONNECTION_STRING environment variable in .env.local" + ); +} +//hack de TypeScript para tipar globalThis con nuestra propiedad custom _mongoClient +const globalForMongo = globalThis as typeof globalThis & { + _mongoClient?: MongoClient; +}; + +//crea el cliente solo si no existe ya (singleton) +function getClient(): MongoClient { + if (!globalForMongo._mongoClient) { + globalForMongo._mongoClient = new MongoClient(connectionString); + } + + return globalForMongo._mongoClient; +} +//lo que exportamo devuelve la instancia de Db lista para hacer queries +export function getDb(): Db { + return getClient().db(); +} diff --git a/front/src/pods/embalse/components/reservoir-card-gauge.tsx b/front/src/pods/embalse/components/reservoir-card-gauge.tsx index f3d4a35..d4e9a54 100644 --- a/front/src/pods/embalse/components/reservoir-card-gauge.tsx +++ b/front/src/pods/embalse/components/reservoir-card-gauge.tsx @@ -1,26 +1,24 @@ import { ReservoirData } from "../embalse.vm"; import { GaugeChart } from "./reservoir-gauge"; import { GaugeLegend } from "./reservoir-gauge/gauge-chart/components/gauge-legend.component"; - interface Props { name: string; reservoirData: ReservoirData; } - export const ReservoirCardGauge: React.FC = (props) => { const { name, reservoirData } = props; const { currentVolume, totalCapacity, measurementDate } = reservoirData; - // const percentage = currentVolume / totalCapacity; - // TODO: replace hardcoded % for real reservoir filled water percentage - + const percentage = totalCapacity > 0 ? currentVolume / totalCapacity : + 0; return (
-

Embalse de {name}

- +

{name}

+
); -}; +}; \ No newline at end of file diff --git a/front/src/pods/embalse/embalse-mock-data.ts b/front/src/pods/embalse/embalse-mock-data.ts index b9a3892..768ad1f 100644 --- a/front/src/pods/embalse/embalse-mock-data.ts +++ b/front/src/pods/embalse/embalse-mock-data.ts @@ -1,6 +1,7 @@ import { ReservoirData } from "./embalse.vm"; export const MOCK_DATA: ReservoirData = { + nombre: "Embalse Ejemplo", currentVolume: 1500, totalCapacity: 50000, measurementDate: "25/12/2025", diff --git a/front/src/pods/embalse/embalse.component.tsx b/front/src/pods/embalse/embalse.component.tsx index 8b52f41..175beba 100644 --- a/front/src/pods/embalse/embalse.component.tsx +++ b/front/src/pods/embalse/embalse.component.tsx @@ -4,25 +4,33 @@ import { ReservoirCardGauge, ReservoirCardInfo, } from "./components"; -import { MOCK_DATA } from "./embalse-mock-data"; - +import { ReservoirData } from "./embalse.vm"; interface Props { - embalse: string; + reservoirData: ReservoirData; } + /** + La prop name de ReservoirCardGauge ahora recibe reservoirData.nombre (el nombre real del embalse desde la BD). + El resto de componentes hijos (ReservoirCardInfo, ReservoirCardDetail) reciben los datos mapeados + */ export const Embalse: React.FC = (props) => { - const { embalse } = props; + const { reservoirData } = props; return (
- +
- +
- +
); -}; +}; \ No newline at end of file diff --git a/front/src/pods/embalse/embalse.mapper.ts b/front/src/pods/embalse/embalse.mapper.ts new file mode 100644 index 0000000..95f19e0 --- /dev/null +++ b/front/src/pods/embalse/embalse.mapper.ts @@ -0,0 +1,51 @@ +//Este fichero transforma un documento Embalse (de BD) al view model ReservoirData (de UI). +/** + * Decisiones del mapeo: + currentVolume: prioriza el dato SAIH sobre el de Aemet (SAIH es mas frecuente). Si ambos son + null, pone 0. + measurementDate: mismo criterio de prioridad, formateado a DD/MM/YYYY. + nombre: viene directamente del campo nombre del documento. + Campos vacios: municipio, rio, tipoDePresa, etc. no estan en el modelo de BD actual, asi que + van como string vacio / 0. + formatDate: acepta tanto Date como string (MongoDB puede devolver ISO strings en vez de Date + dependiendo de la serializacion) + */ + +import type { Embalse } from "db-model"; +import type { ReservoirData } from "./embalse.vm"; + +function formatDate(date: Date | string | null | undefined): string { + if (!date) return ""; + const d = typeof date === "string" ? new Date(date) : date; + if (isNaN(d.getTime())) return ""; + const day = String(d.getDate()).padStart(2, "0"); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const year = d.getFullYear(); + return `${day}/${month}/${year}`; +} +export function mapEmbalseToReservoirData(embalse: Embalse): ReservoirData { + const currentVolume = embalse.aguaActualSAIH ?? embalse.aguaActualAemet ?? 0; + const measurementDate = formatDate( + embalse.fechaMedidaAguaActualSAIH ?? embalse.fechaMedidaAguaActualAemet, + ); + return { + nombre: embalse.nombre, + currentVolume, + totalCapacity: embalse.capacidad, + measurementDate, + datosEmbalse: { + cuenca: embalse.cuenca?.nombre ?? "", + provincia: embalse.provincia ?? "", + municipio: "", + rio: "", + embalsesAguasAbajo: 0, + tipoDePresa: "", + anioConstruccion: 0, + superficie: 0, + localizacion: "", + }, + reservoirInfo: { + Description: "", + }, + }; +} diff --git a/front/src/pods/embalse/embalse.pod.tsx b/front/src/pods/embalse/embalse.pod.tsx index e1c978d..d59888d 100644 --- a/front/src/pods/embalse/embalse.pod.tsx +++ b/front/src/pods/embalse/embalse.pod.tsx @@ -1,12 +1,10 @@ import React from "react"; import { Embalse } from "./embalse.component"; - +import { ReservoirData } from "./embalse.vm"; interface Props { - embalse: string; + reservoirData: ReservoirData; } - export const EmbalsePod: React.FC = (props) => { - const { embalse } = props; - - return ; + const { reservoirData } = props; + return ; }; diff --git a/front/src/pods/embalse/embalse.repository.ts b/front/src/pods/embalse/embalse.repository.ts new file mode 100644 index 0000000..4388027 --- /dev/null +++ b/front/src/pods/embalse/embalse.repository.ts @@ -0,0 +1,33 @@ +"use server"; + +import { getDb } from "@/lib/mongodb"; +import type { Embalse } from "db-model"; + +export async function getEmbalseBySlug(slug: string): Promise { + //conecta con BD y trae datos en base al slug + const db = getDb(); + const doc = await db.collection("embalses").findOne({ slug }); + + if (!doc) { + return null; + } + //mapea el resultado de la BD al tipo Embalse y nos aseguramos de que lleven los tipos correctos (tienen que ser primitivos) + return { + _id: doc._id.toString(), + embalse_id: doc.embalse_id, + nombre: doc.nombre, + slug: doc.slug, + cuenca: { + _id: doc.cuenca?._id?.toString() ?? "", + nombre: doc.cuenca?.nombre ?? "", + }, + provincia: doc.provincia ?? null, + capacidad: doc.capacidad, + aguaActualAemet: doc.aguaActualAemet ?? null, + fechaMedidaAguaActualAemet: doc.fechaMedidaAguaActualAemet ?? null, + aguaActualSAIH: doc.aguaActualSAIH ?? null, + fechaMedidaAguaActualSAIH: doc.fechaMedidaAguaActualSAIH ?? null, + descripcion_id: doc.descripcion_id ?? null, + uso: doc.uso ?? "", + }; +} diff --git a/front/src/pods/embalse/embalse.vm.ts b/front/src/pods/embalse/embalse.vm.ts index 2f778f8..7e7b598 100644 --- a/front/src/pods/embalse/embalse.vm.ts +++ b/front/src/pods/embalse/embalse.vm.ts @@ -15,6 +15,7 @@ export interface ReservoirInfo { } export interface ReservoirData { + nombre: string; currentVolume: number; totalCapacity: number; measurementDate: string; diff --git a/package-lock.json b/package-lock.json index f1b3622..0004009 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,8 @@ "@fontsource/nunito-sans": "^5.2.7", "@tailwindcss/postcss": "^4.1.17", "d3": "^7.9.0", + "db-model": "file:../packages/db-model", + "mongodb": "^6.12.0", "next": "^15.4.1", "postcss": "^8.5.6", "react": "^19.1.0", @@ -923,11 +925,15 @@ } }, "node_modules/@next/env": { - "version": "15.5.0", + "version": "15.5.11", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.11.tgz", + "integrity": "sha512-g9s5SS9gC7GJCEOR3OV3zqs7C5VddqxP9X+/6BpMbdXRkqsWfFf2CJPBZNvNEtAkKTNuRgRXAgNxSAXzfLdaTg==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.0", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz", + "integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==", "cpu": [ "arm64" ], @@ -941,12 +947,13 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.0.tgz", - "integrity": "sha512-s2Nk6ec+pmYmAb/utawuURy7uvyYKDk+TRE5aqLRsdnj3AhwC9IKUBmhfnLmY/+P+DnwqpeXEFIKe9tlG0p6CA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz", + "integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -956,12 +963,13 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.0.tgz", - "integrity": "sha512-mGlPJMZReU4yP5fSHjOxiTYvZmwPSWn/eF/dcg21pwfmiUCKS1amFvf1F1RkLHPIMPfocxLViNWFvkvDB14Isg==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz", + "integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -971,12 +979,13 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.0.tgz", - "integrity": "sha512-biWqIOE17OW/6S34t1X8K/3vb1+svp5ji5QQT/IKR+VfM3B7GvlCwmz5XtlEan2ukOUf9tj2vJJBffaGH4fGRw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz", + "integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -986,12 +995,13 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.0.tgz", - "integrity": "sha512-zPisT+obYypM/l6EZ0yRkK3LEuoZqHaSoYKj+5jiD9ESHwdr6QhnabnNxYkdy34uCigNlWIaCbjFmQ8FY5AlxA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz", + "integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1001,12 +1011,13 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.0.tgz", - "integrity": "sha512-+t3+7GoU9IYmk+N+FHKBNFdahaReoAktdOpXHFIPOU1ixxtdge26NgQEEkJkCw2dHT9UwwK5zw4mAsURw4E8jA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz", + "integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1016,12 +1027,13 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.0.tgz", - "integrity": "sha512-d8MrXKh0A+c9DLiy1BUFwtg3Hu90Lucj3k6iKTUdPOv42Ve2UiIG8HYi3UAb8kFVluXxEfdpCoPPCSODk5fDcw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz", + "integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1031,12 +1043,13 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.0.tgz", - "integrity": "sha512-Fe1tGHxOWEyQjmygWkkXSwhFcTJuimrNu52JEuwItrKJVV4iRjbWp9I7zZjwqtiNnQmxoEvoisn8wueFLrNpvQ==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz", + "integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -3677,10 +3690,12 @@ } }, "node_modules/next": { - "version": "15.5.0", + "version": "15.5.11", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.11.tgz", + "integrity": "sha512-L2KPiKmqTDpRdeVDdPjhf43g2/VPe0NCNndq7OKDCgOLWtxe1kbr/zXGIZtYY7kZEAjRf7Bj/mwUFSr+tYC2Yg==", "license": "MIT", "dependencies": { - "@next/env": "15.5.0", + "@next/env": "15.5.11", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -3693,14 +3708,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.0", - "@next/swc-darwin-x64": "15.5.0", - "@next/swc-linux-arm64-gnu": "15.5.0", - "@next/swc-linux-arm64-musl": "15.5.0", - "@next/swc-linux-x64-gnu": "15.5.0", - "@next/swc-linux-x64-musl": "15.5.0", - "@next/swc-win32-arm64-msvc": "15.5.0", - "@next/swc-win32-x64-msvc": "15.5.0", + "@next/swc-darwin-arm64": "15.5.7", + "@next/swc-darwin-x64": "15.5.7", + "@next/swc-linux-arm64-gnu": "15.5.7", + "@next/swc-linux-arm64-musl": "15.5.7", + "@next/swc-linux-x64-gnu": "15.5.7", + "@next/swc-linux-x64-musl": "15.5.7", + "@next/swc-win32-arm64-msvc": "15.5.7", + "@next/swc-win32-x64-msvc": "15.5.7", "sharp": "^0.34.3" }, "peerDependencies": { From cc3a891ee05a8740358a3702daceb328fffd7486 Mon Sep 17 00:00:00 2001 From: Braulio Date: Wed, 4 Feb 2026 14:49:59 +0100 Subject: [PATCH 2/2] minor update readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index bc05842..a28eb03 100644 --- a/README.md +++ b/README.md @@ -125,3 +125,13 @@ Y ya estarán las funciones levantadas y ejecutándose. ```bash docker start azurite ``` + +# [Temporal] Probando detalle embalse + +Está pendiente conectar typeahead con el detalle del embalse. De momento, se puede probar accediendo directamente a las siguientes URLs: + +```bash +http://localhost:3000/embalse/vinuela-la + +http://localhost:3000/embalse/villagudin +``` \ No newline at end of file