Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
```
1 change: 1 addition & 0 deletions front/.env.local
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MONGODB_CONNECTION_STRING=mongodb://localhost:27017/embalse-info
4 changes: 3 additions & 1 deletion front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
21 changes: 16 additions & 5 deletions front/src/app/embalse/[embalse]/page.tsx
Original file line number Diff line number Diff line change
@@ -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 <EmbalsePod embalse={embalse} />;
}
const embalseDoc = await getEmbalseBySlug(embalse);
if (!embalseDoc) {
notFound();
}
//mapeamos el documento a ReservoirData y lo pasamos al pod
const reservoirData = mapEmbalseToReservoirData(embalseDoc);
return <EmbalsePod reservoirData={reservoirData} />;
}
26 changes: 26 additions & 0 deletions front/src/lib/mongodb.ts
Original file line number Diff line number Diff line change
@@ -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();
}
14 changes: 6 additions & 8 deletions front/src/pods/embalse/components/reservoir-card-gauge.tsx
Original file line number Diff line number Diff line change
@@ -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> = (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 (
<div className="card bg-base-100 mx-auto w-full max-w-[400px] items-center gap-6 rounded-2xl p-4 shadow-lg">
<h2 className="text-center">Embalse de {name}</h2>
<GaugeChart percentage={0.67} measurementDate={measurementDate} />
<h2 className="text-center">{name}</h2>
<GaugeChart percentage={percentage} measurementDate=
{measurementDate} />
<GaugeLegend
currentVolume={currentVolume}
totalCapacity={totalCapacity}
/>
</div>
);
};
};
1 change: 1 addition & 0 deletions front/src/pods/embalse/embalse-mock-data.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
24 changes: 16 additions & 8 deletions front/src/pods/embalse/embalse.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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> = (props) => {
const { embalse } = props;
const { reservoirData } = props;
return (
<div className="flex flex-col gap-8">
<div className="space-y-6">
<ReservoirCardGauge name={embalse} reservoirData={MOCK_DATA} />
<ReservoirCardGauge
name={reservoirData.nombre}
reservoirData={reservoirData}
/>
<div className="card bg-base-100 mx-auto w-full max-w-[400px] items-center gap-6 rounded-2xl p-4 shadow-lg">
<ReservoirCardInfo reservoirInfo={MOCK_DATA.reservoirInfo} />
<ReservoirCardInfo reservoirInfo={reservoirData.reservoirInfo}
/>
</div>
<div className="card bg-base-100 mx-auto w-full max-w-[400px] items-center gap-6 rounded-2xl p-4 shadow-lg">
<ReservoirCardDetail datosEmbalse={MOCK_DATA.datosEmbalse} />
<ReservoirCardDetail datosEmbalse={reservoirData.datosEmbalse}
/>
</div>
</div>
</div>
);
};
};
51 changes: 51 additions & 0 deletions front/src/pods/embalse/embalse.mapper.ts
Original file line number Diff line number Diff line change
@@ -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: "",
},
};
}
10 changes: 4 additions & 6 deletions front/src/pods/embalse/embalse.pod.tsx
Original file line number Diff line number Diff line change
@@ -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> = (props) => {
const { embalse } = props;

return <Embalse embalse={embalse} />;
const { reservoirData } = props;
return <Embalse reservoirData={reservoirData} />;
};
33 changes: 33 additions & 0 deletions front/src/pods/embalse/embalse.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use server";

import { getDb } from "@/lib/mongodb";
import type { Embalse } from "db-model";

export async function getEmbalseBySlug(slug: string): Promise<Embalse | null> {
//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 ?? "",
};
}
1 change: 1 addition & 0 deletions front/src/pods/embalse/embalse.vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface ReservoirInfo {
}

export interface ReservoirData {
nombre: string;
currentVolume: number;
totalCapacity: number;
measurementDate: string;
Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.