diff --git a/README.md b/README.md index 6046f49..a28eb03 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. @@ -126,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 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 7636e89..50f2d7c 100644 --- a/front/package.json +++ b/front/package.json @@ -15,7 +15,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 15e4d09..dda2f34 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",