+
)}
-
diff --git a/front/src/pods/embalse/embalse.mapper.spec.ts b/front/src/pods/embalse/embalse.mapper.spec.ts
index f335f36..07ba85e 100644
--- a/front/src/pods/embalse/embalse.mapper.spec.ts
+++ b/front/src/pods/embalse/embalse.mapper.spec.ts
@@ -1,6 +1,17 @@
import { describe, it, expect } from "vitest";
-import { mapEmbalseToReservoirData } from "./embalse.mapper";
+import {
+ mapEmbalseToReservoirData,
+ mapHistoricalReservoirToViewModel,
+ mapReservoirLastYearToViewModel,
+} from "./embalse.mapper";
import type { Embalse } from "db-model";
+import * as historicalApi from "./api/embalse.api-model";
+import {
+ createEmptyDataLastYearModel,
+ createEmptyHistoricalAverageReservoir,
+ DataLastYearModel,
+ HistoricalAverageReservoir,
+} from "./embalse.vm";
describe("mapEmbalseToReservoirData", () => {
const mockEmbalseBase: Partial
= {
@@ -184,3 +195,71 @@ describe("mapEmbalseToReservoirData", () => {
expect(result.datosEmbalse.provincia).toBe("");
});
});
+
+describe("mapReservoirLastYearToViewModel", () => {
+ it("should correctly map the fields from the last year", () => {
+ const mockLastYearData: historicalApi.ReservoirLastYearModel = {
+ mes: 3,
+ promedio_agua_actual: 54,
+ };
+
+ const result: DataLastYearModel =
+ mapReservoirLastYearToViewModel(mockLastYearData);
+ const expectResult = {
+ month: 3,
+ average: 54,
+ year: new Date().getFullYear() - 1,
+ };
+
+ expect(result).toEqual(expectResult);
+ });
+
+ it("should return empty data when fill a null value", () => {
+ const result: DataLastYearModel = mapReservoirLastYearToViewModel(null);
+
+ expect(result).toEqual(createEmptyDataLastYearModel());
+ });
+
+ it("should return empty data when fill a undefined value", () => {
+ const result: DataLastYearModel =
+ mapReservoirLastYearToViewModel(undefined);
+
+ expect(result).toEqual(createEmptyDataLastYearModel());
+ });
+});
+
+describe("mapHistoricalReservoirToViewModel", () => {
+ it("should accurately map the fields for the last ten years", () => {
+ const mockHistoricalData: historicalApi.HistoricalAverageReservoir = {
+ año: 2026,
+ embalse: "Viñuela, La",
+ mes: 3,
+ promedio_agua_actual: 149.21,
+ };
+
+ const result: HistoricalAverageReservoir =
+ mapHistoricalReservoirToViewModel(mockHistoricalData);
+
+ const expectResult = {
+ year: 2026,
+ nameReservoir: "Viñuela, La",
+ month: 3,
+ average: 149.21,
+ };
+
+ expect(result).toEqual(expectResult);
+ });
+ it("should return empty data when fill a null value", () => {
+ const result: HistoricalAverageReservoir =
+ mapHistoricalReservoirToViewModel(null);
+
+ expect(result).toEqual(createEmptyHistoricalAverageReservoir());
+ });
+
+ it("should return empty data when fill a undefined value", () => {
+ const result: HistoricalAverageReservoir =
+ mapHistoricalReservoirToViewModel(undefined);
+
+ expect(result).toEqual(createEmptyHistoricalAverageReservoir());
+ });
+});
diff --git a/front/src/pods/embalse/embalse.mapper.ts b/front/src/pods/embalse/embalse.mapper.ts
index 1b5fa9e..abe6866 100644
--- a/front/src/pods/embalse/embalse.mapper.ts
+++ b/front/src/pods/embalse/embalse.mapper.ts
@@ -12,7 +12,14 @@
*/
import type { Embalse } from "db-model";
-import type { ReservoirData, ReservoirInfo } from "./embalse.vm";
+import {
+ ReservoirData,
+ DataLastYearModel,
+ ReservoirInfo,
+ HistoricalAverageReservoir,
+ createEmptyDataLastYearModel,
+ createEmptyHistoricalAverageReservoir,
+} from "./embalse.vm";
import * as apiModel from "./api";
function formatDate(date: Date | string | null | undefined): string {
@@ -61,3 +68,28 @@ const mapReservoirInfoFromContentIslandToViewModel = (
authorUrl: embalseInfo.authorUrl ?? "",
description: embalseInfo.description ?? "",
});
+
+export const mapReservoirLastYearToViewModel = (
+ apiData: apiModel.ReservoirLastYearModel,
+): DataLastYearModel => {
+ return Boolean(apiData)
+ ? {
+ month: apiData.mes,
+ average: apiData.promedio_agua_actual,
+ year: new Date().getFullYear() - 1,
+ }
+ : createEmptyDataLastYearModel();
+};
+
+export const mapHistoricalReservoirToViewModel = (
+ apiData: apiModel.HistoricalAverageReservoir,
+): HistoricalAverageReservoir => {
+ return Boolean(apiData)
+ ? {
+ nameReservoir: apiData.embalse,
+ year: apiData.año,
+ month: apiData.mes,
+ average: apiData.promedio_agua_actual,
+ }
+ : createEmptyHistoricalAverageReservoir();
+};
diff --git a/front/src/pods/embalse/embalse.pod.tsx b/front/src/pods/embalse/embalse.pod.tsx
index 65c7a9a..1289e2e 100644
--- a/front/src/pods/embalse/embalse.pod.tsx
+++ b/front/src/pods/embalse/embalse.pod.tsx
@@ -1,11 +1,26 @@
import React from "react";
import { Embalse } from "./embalse.component";
-import { ReservoirData, ReservoirInfo } from "./embalse.vm";
+import {
+ DataLastYearModel,
+ HistoricalAverageReservoir,
+ ReservoirData,
+} from "./embalse.vm";
interface Props {
reservoirData: ReservoirData;
+ dataOneYearAgo: DataLastYearModel;
+ dataTenYearsAgo: HistoricalAverageReservoir;
}
+
export const EmbalsePod: React.FC = (props) => {
- const { reservoirData } = props;
- return ;
+ const { reservoirData, dataOneYearAgo, dataTenYearsAgo } = props;
+ return (
+ <>
+
+ >
+ );
};
diff --git a/front/src/pods/embalse/embalse.repository.ts b/front/src/pods/embalse/embalse.repository.ts
index 6e3ec70..d97312c 100644
--- a/front/src/pods/embalse/embalse.repository.ts
+++ b/front/src/pods/embalse/embalse.repository.ts
@@ -2,7 +2,9 @@
import { getDb } from "@/lib/mongodb";
import type { Embalse } from "db-model";
+import * as ApiModel from "./api";
import { mapEmbalse } from "./embalse.repository.mapper";
+
export async function getEmbalseBySlug(slug: string): Promise {
//conecta con BD y trae datos en base al slug
const db = await getDb();
@@ -15,3 +17,88 @@ export async function getEmbalseBySlug(slug: string): Promise {
const mappedEmbalse = mapEmbalse(embalse);
return mappedEmbalse;
}
+
+/**
+ * Obtain the monthly average for the last year.
+ * @param name:string Reservoir name
+ * @returns ReservoirHistoryModelApi:
+ */
+export const getAverageLastYearByMonth = async (
+ reservoirName: string,
+ month: number,
+): Promise => {
+ try {
+ const db = await getDb();
+ const results = await db
+ .collection(
+ "embalsesPromedioHistoricoPorMeses",
+ )
+ .aggregate([
+ { $match: { embalse: reservoirName } },
+ { $unwind: "$meses" },
+ { $match: { "meses.mes": month } },
+ {
+ $project: {
+ mes: "$meses.mes",
+ promedio_agua_actual: "$meses.promedio_agua_actual",
+ },
+ },
+ { $limit: 1 },
+ ])
+ .next();
+
+ return results;
+ } catch (error) {
+ console.warn(
+ "getAverageByMonths: MongoDB not available (build time?), returning empty array.",
+ "Error:",
+ error instanceof Error ? error.message : error,
+ );
+ }
+};
+
+/**
+ * Obtain the average monthly and annual values received per parameter
+ *
+ * @param name:string Reservoir name
+ * @param month:number Month number (1-12)
+ * @param year:number Year number (yyyy)
+ * @returns HistoricalAverageReservoir:
+ */
+export const getAverageHistoricalByMonth = async (
+ reservoirName: string,
+ month: number,
+ year: number,
+): Promise => {
+ try {
+ const db = await getDb();
+ const results = await db
+ .collection(
+ "embalsesPromedioHistoricoDiezAnios",
+ )
+ .aggregate([
+ { $match: { embalse: reservoirName } },
+ { $unwind: "$meses" },
+ { $match: { "meses.año": year, "meses.mes": month } },
+ {
+ $project: {
+ _id: 0,
+ embalse: 1,
+ año: "$meses.año",
+ mes: "$meses.mes",
+ promedio_agua_actual: "$meses.promedio_agua_actual",
+ },
+ },
+ ])
+ .next();
+
+ return results;
+ } catch (error) {
+ console.warn(
+ "getAverageHistorical: MongoDB not available (build time?), returning empty array.",
+ "Error:",
+ error instanceof Error ? error.message : error,
+ );
+ return;
+ }
+};
diff --git a/front/src/pods/embalse/embalse.vm.ts b/front/src/pods/embalse/embalse.vm.ts
index 58362db..536189e 100644
--- a/front/src/pods/embalse/embalse.vm.ts
+++ b/front/src/pods/embalse/embalse.vm.ts
@@ -53,3 +53,28 @@ export const createEmptyEmbalseInfo = (): ReservoirInfo => ({
authorUrl: "",
description: "",
});
+
+export interface DataLastYearModel {
+ year?: number;
+ month: number;
+ average: number;
+}
+export const createEmptyDataLastYearModel = (): DataLastYearModel => ({
+ year: new Date().getFullYear() - 1,
+ month: new Date().getMonth(),
+ average: 0,
+});
+
+export interface HistoricalAverageReservoir {
+ nameReservoir: string;
+ month: number;
+ year: number;
+ average: number;
+}
+export const createEmptyHistoricalAverageReservoir =
+ (): HistoricalAverageReservoir => ({
+ nameReservoir: "",
+ month: new Date().getMonth(),
+ year: new Date().getFullYear() - 10,
+ average: 0,
+ });
diff --git a/packages/db/src/console-runners/historical-seed/api.ts b/packages/db/src/console-runners/historical-seed/api.ts
new file mode 100644
index 0000000..c7929ee
--- /dev/null
+++ b/packages/db/src/console-runners/historical-seed/api.ts
@@ -0,0 +1,80 @@
+import axios from "axios";
+import { ArcGISStatResponse } from "./types.js";
+
+const BASE_URL =
+ "https://services-eu1.arcgis.com/RvnYk1PBUJ9rrAuT/arcgis/rest/services/Embalses_Total/FeatureServer/0/query";
+
+const DELAY_MS = 200; // pausa entre requests para no saturar la API
+
+function sleep(ms: number): Promise {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+/**
+ * Consulta estadística genérica con paginación por offset.
+ */
+async function fetchStatQuery(
+ where: string,
+): Promise> {
+ const results: Array<{ embalse_nombre: string; avg_agua_actual: number }> =
+ [];
+ let offset = 0;
+
+ while (true) {
+ const params = {
+ where,
+ outStatistics: JSON.stringify([
+ {
+ statisticType: "avg",
+ onStatisticField: "agua_actual",
+ outStatisticFieldName: "avg_agua_actual",
+ },
+ ]),
+ groupByFieldsForStatistics: "embalse_nombre",
+ resultOffset: offset,
+ resultRecordCount: 2000,
+ f: "json",
+ };
+
+ const response = await axios.get(BASE_URL, { params });
+ const { features, exceededTransferLimit } = response.data;
+
+ for (const feature of features) {
+ const nombre = feature.attributes.embalse_nombre as string;
+ const avg = feature.attributes.avg_agua_actual as number;
+ if (nombre && avg != null) {
+ results.push({ embalse_nombre: nombre, avg_agua_actual: avg });
+ }
+ }
+
+ if (exceededTransferLimit) {
+ offset += 2000;
+ await sleep(DELAY_MS);
+ } else {
+ break;
+ }
+ }
+
+ return results;
+}
+
+/**
+ * Promedio mensual de agua_actual por embalse para un año-mes dado.
+ */
+export async function fetchMonthlyAvg(
+ year: number,
+ month: number,
+): Promise> {
+ // Rango: primer día del mes a primer día del mes siguiente
+ const start = new Date(Date.UTC(year, month - 1, 1));
+ const end = new Date(Date.UTC(year, month, 1)); // mes siguiente
+
+ const where = `fecha >= timestamp '${formatTimestamp(start)}' AND fecha < timestamp '${formatTimestamp(end)}'`;
+
+ await sleep(DELAY_MS);
+ return fetchStatQuery(where);
+}
+
+function formatTimestamp(date: Date): string {
+ return date.toISOString().replace("T", " ").replace("Z", "");
+}
diff --git a/packages/db/src/console-runners/historical-seed/historical.mapped.spec.ts b/packages/db/src/console-runners/historical-seed/historical.mapped.spec.ts
new file mode 100644
index 0000000..1a395ad
--- /dev/null
+++ b/packages/db/src/console-runners/historical-seed/historical.mapped.spec.ts
@@ -0,0 +1,131 @@
+import {
+ mapHistoricalAverageToDB,
+ mapLastYearAverageToDB,
+} from "./historical.mapped.js";
+import {
+ createEmptyHistoricalLastYear,
+ createEmptyHistoricalTenYearsAgo,
+ HistoricalLastYear,
+ HistoricalTenYearsAgo,
+ PromedioAnualPorMesOutput,
+ PromedioMensualOutput,
+} from "./types.js";
+import { describe, it, expect } from "vitest";
+
+describe("mapHistoricalAverageToDB", () => {
+ it("should accurately map the fields for the last ten years", () => {
+ const mockHistoricalData: PromedioMensualOutput = {
+ metadata: {
+ generatedAt: "2026-03-05T10:45:15.199Z",
+ periodoInicio: "2016-03",
+ periodoFin: "2026-03",
+ },
+ data: {
+ Aceña: [
+ {
+ año: 2016,
+ mes: 3,
+ promedio_agua_actual: 15.2,
+ },
+ {
+ año: 2016,
+ mes: 4,
+ promedio_agua_actual: 18.75,
+ },
+ ],
+ },
+ };
+ const result: HistoricalTenYearsAgo[] =
+ mapHistoricalAverageToDB(mockHistoricalData);
+
+ const expectResult = [
+ {
+ embalse: "Aceña",
+ meses: [
+ {
+ mes: 3,
+ año: 2016,
+ promedio_agua_actual: 15.2,
+ },
+ {
+ mes: 4,
+ año: 2016,
+ promedio_agua_actual: 18.75,
+ },
+ ],
+ metadata: {
+ generatedAt: "2026-03-05T10:45:15.199Z",
+ periodoInicio: "2016-03",
+ periodoFin: "2026-03",
+ },
+ },
+ ];
+ expect(result).toEqual(expectResult);
+ });
+
+ it("should return empty data when fill a null value", () => {
+ const result: HistoricalTenYearsAgo[] = mapHistoricalAverageToDB(null);
+ expect(result).toEqual(createEmptyHistoricalTenYearsAgo());
+ });
+ it("should return empty data when fill a undefined value", () => {
+ const result: HistoricalTenYearsAgo[] = mapHistoricalAverageToDB(undefined);
+ expect(result).toEqual(createEmptyHistoricalTenYearsAgo());
+ });
+});
+
+describe("mapHistoricalAverageToDB", () => {
+ it("should accurately map the fields for last years", () => {
+ const mockHistoricalData: PromedioAnualPorMesOutput = {
+ metadata: {
+ generatedAt: "2026-03-05T10:45:15.199Z",
+ periodoInicio: "2016-03",
+ periodoFin: "2026-03",
+ },
+ data: {
+ Aceña: [
+ {
+ mes: 3,
+ promedio_agua_actual: 15.2,
+ },
+ {
+ mes: 4,
+ promedio_agua_actual: 18.75,
+ },
+ ],
+ },
+ };
+ const result: HistoricalLastYear[] =
+ mapLastYearAverageToDB(mockHistoricalData);
+
+ const expectResult = [
+ {
+ embalse: "Aceña",
+ meses: [
+ {
+ mes: 3,
+ promedio_agua_actual: 15.2,
+ },
+ {
+ mes: 4,
+ promedio_agua_actual: 18.75,
+ },
+ ],
+ metadata: {
+ generatedAt: "2026-03-05T10:45:15.199Z",
+ periodoInicio: "2016-03",
+ periodoFin: "2026-03",
+ },
+ },
+ ];
+ expect(result).toEqual(expectResult);
+ });
+
+ it("should return empty data when fill a null value", () => {
+ const result: HistoricalLastYear[] = mapLastYearAverageToDB(null);
+ expect(result).toEqual(createEmptyHistoricalLastYear());
+ });
+ it("should return empty data when fill a undefined value", () => {
+ const result: HistoricalLastYear[] = mapLastYearAverageToDB(undefined);
+ expect(result).toEqual(createEmptyHistoricalLastYear());
+ });
+});
diff --git a/packages/db/src/console-runners/historical-seed/historical.mapped.ts b/packages/db/src/console-runners/historical-seed/historical.mapped.ts
new file mode 100644
index 0000000..5dcc84d
--- /dev/null
+++ b/packages/db/src/console-runners/historical-seed/historical.mapped.ts
@@ -0,0 +1,67 @@
+import {
+ PromedioAnualPorMesOutput,
+ PromedioMensualOutput,
+ HistoricalTenYearsAgo,
+ HistoricalLastYear,
+ createEmptyHistoricalTenYearsAgo,
+ createEmptyHistoricalLastYear,
+} from "./types.js";
+
+export const mapHistoricalAverageToDB = (
+ historicalData: PromedioMensualOutput,
+): HistoricalTenYearsAgo[] => {
+ if (Boolean(historicalData)) {
+ const { data, metadata } = historicalData;
+
+ const dataMapped: HistoricalTenYearsAgo[] = Object.entries(data).map(
+ ([reservoirName, monthly]) => {
+ return {
+ embalse: reservoirName,
+ meses: monthly.map((month) => ({
+ mes: month.mes,
+ año: month.año,
+ promedio_agua_actual: month.promedio_agua_actual,
+ })),
+ metadata: {
+ generatedAt: metadata.generatedAt,
+ periodoInicio: metadata.periodoInicio,
+ periodoFin: metadata.periodoFin,
+ },
+ };
+ },
+ );
+
+ return dataMapped;
+ } else {
+ return createEmptyHistoricalTenYearsAgo();
+ }
+};
+
+export const mapLastYearAverageToDB = (
+ lastYearData: PromedioAnualPorMesOutput,
+): HistoricalLastYear[] => {
+ if (Boolean(lastYearData)) {
+ const { data, metadata } = lastYearData;
+
+ const dataMapped: HistoricalLastYear[] = Object.entries(data).map(
+ ([reservoirName, monthly]) => {
+ return {
+ embalse: reservoirName,
+ meses: monthly.map((month) => ({
+ mes: month.mes,
+ promedio_agua_actual: month.promedio_agua_actual,
+ })),
+ metadata: {
+ generatedAt: metadata.generatedAt,
+ periodoInicio: metadata.periodoInicio,
+ periodoFin: metadata.periodoFin,
+ },
+ };
+ },
+ );
+
+ return dataMapped;
+ } else {
+ return createEmptyHistoricalLastYear();
+ }
+};
diff --git a/packages/db/src/console-runners/historical-seed/index.ts b/packages/db/src/console-runners/historical-seed/index.ts
new file mode 100644
index 0000000..0687be4
--- /dev/null
+++ b/packages/db/src/console-runners/historical-seed/index.ts
@@ -0,0 +1,160 @@
+import * as fs from "fs";
+import * as path from "path";
+import { fetchMonthlyAvg } from "./api.js";
+import {
+ PromedioMensualOutput,
+ PromedioAnualPorMesOutput,
+ PromedioMensualEntry,
+ PromedioAnualPorMesEntry,
+} from "./types.js";
+import {
+ mapHistoricalAverageToDB,
+ mapLastYearAverageToDB,
+} from "./historical.mapped.js";
+import { historicalRepository } from "#dals/index.js";
+import { dbServer } from "#core/servers/db.server.js";
+
+const END_YEAR = new Date().getFullYear();
+const END_MONTH = new Date().getMonth() + 1;
+const START_YEAR = END_YEAR - 10;
+const START_MONTH = END_MONTH;
+
+async function generateMonthlyAverages(): Promise {
+ const data: Record = {};
+
+ let year = START_YEAR;
+ let month = START_MONTH;
+ let count = 0;
+
+ while (year < END_YEAR || (year === END_YEAR && month <= END_MONTH)) {
+ count++;
+ console.log(
+ `[Mensual] Consultando ${year}-${String(month).padStart(2, "0")}...`,
+ );
+
+ const results = await fetchMonthlyAvg(year, month);
+
+ for (const r of results) {
+ if (!data[r.embalse_nombre]) {
+ data[r.embalse_nombre] = [];
+ }
+ data[r.embalse_nombre].push({
+ año: year,
+ mes: month,
+ promedio_agua_actual: Math.round(r.avg_agua_actual * 100) / 100,
+ });
+ }
+
+ // Avanzar al siguiente mes
+ month++;
+ if (month > 12) {
+ month = 1;
+ year++;
+ }
+ }
+ console.log(
+ `[Mensual] Completado: ${count} meses, ${Object.keys(data).length} embalses`,
+ );
+
+ return {
+ metadata: {
+ generatedAt: new Date().toISOString(),
+ periodoInicio: `${START_YEAR}-${String(START_MONTH).padStart(2, "0")}`,
+ periodoFin: `${END_YEAR}-${String(END_MONTH).padStart(2, "0")}`,
+ },
+ data,
+ };
+}
+
+/**
+ * generateMonthlyGlobalAverages
+ *
+ * Esta función recibe los datos mensuales de todos los embalses y extrae
+ * únicamente los datos del año anterior (el año previo al END_YEAR).
+ *
+ * Para cada embalse, filtra las entradas que corresponden al año anterior
+ * y devuelve un array con la medida de agua_actual de cada mes de ese año.
+ *
+ * Ejemplo: si END_YEAR es 2026, la función devuelve los datos de 2025.
+ * Para el embalse "Buendía" devolvería: enero 2025 -> 100, febrero 2025 -> 95, etc.
+ *
+ * @param monthlyData - Diccionario donde la clave es el nombre del embalse y el valor
+ * es un array con las entradas mensuales (año, mes, promedio_agua_actual).
+ * @returns Un objeto con metadata (fecha de generación, periodo) y los datos del año anterior por mes.
+ */
+function generateMonthlyGlobalAverages(
+ monthlyData: Record,
+): PromedioAnualPorMesOutput {
+ // El año anterior al año final configurado
+ const previousYear = END_YEAR - 1;
+
+ // Objeto donde almacenaremos el resultado: clave = nombre del embalse, valor = array de datos por mes
+ const data: Record = {};
+
+ // Iteramos sobre cada embalse y sus entradas mensuales
+ for (const [embalse, entries] of Object.entries(monthlyData)) {
+ // Filtramos solo las entradas que pertenecen al año anterior
+ data[embalse] = entries
+ .filter((entry) => entry.año === previousYear)
+ // Ordenamos por mes (enero a diciembre)
+ .sort((a, b) => a.mes - b.mes)
+ // Mapeamos al formato de salida, manteniendo el valor original (sin promediar)
+ .map((entry) => ({
+ mes: entry.mes,
+ promedio_agua_actual: entry.promedio_agua_actual,
+ }));
+ }
+
+ // Log informativo: cuántos embalses se han procesado
+ console.log(
+ `[Anual por mes] Completado: ${Object.keys(data).length} embalses (año ${previousYear})`,
+ );
+
+ // Devolvemos el resultado con metadata y los datos del año anterior
+ return {
+ metadata: {
+ generatedAt: new Date().toISOString(),
+ periodoInicio: `${previousYear}-01`,
+ periodoFin: `${previousYear}-12`,
+ },
+ data,
+ };
+}
+
+export async function run() {
+ try {
+ /** Promedio por meses de los últimos 10 años **/
+ console.log("=== Generando promedios mensuales (10 años) ===");
+ const historicalData = await generateMonthlyAverages();
+
+ const historicalDataMapped = mapHistoricalAverageToDB(historicalData);
+ if (historicalDataMapped.length === 0) {
+ console.log("No se encontraron datos de histórico para actualizar");
+ return;
+ }
+ await historicalRepository.updateHistoricalTenYearsAgo(
+ historicalDataMapped,
+ );
+
+ /** Promedio por mes del último año **/
+ console.log("=== Generando promedios por mes del último año ===");
+ const lastYearData = generateMonthlyGlobalAverages(historicalData.data);
+ const lastYearDataMapped = mapLastYearAverageToDB(lastYearData);
+ if (lastYearDataMapped.length === 0) {
+ console.log("No se encontraron datos del último año para actualizar");
+ return;
+ }
+ await historicalRepository.updateHistoricalLastYear(lastYearDataMapped);
+
+ await dbServer.disconnect();
+ } catch (error) {
+ console.error("Console-runners historical data: ", error);
+ }
+
+ console.log("Listo!");
+}
+
+run().catch((err) => {
+ console.error("Error:", err);
+ process.exit(1);
+});
diff --git a/packages/db/src/console-runners/historical-seed/types.ts b/packages/db/src/console-runners/historical-seed/types.ts
new file mode 100644
index 0000000..c59eab6
--- /dev/null
+++ b/packages/db/src/console-runners/historical-seed/types.ts
@@ -0,0 +1,104 @@
+// Respuesta de ArcGIS para consultas estadísticas
+export interface ArcGISStatResponse {
+ features: Array<{
+ attributes: Record;
+ }>;
+ exceededTransferLimit?: boolean;
+}
+
+// Entrada mensual por embalse
+export interface PromedioMensualEntry {
+ año: number;
+ mes: number;
+ promedio_agua_actual: number;
+}
+
+// Entrada de promedio global por mes (agregado sobre todos los años)
+export interface PromedioAnualPorMesEntry {
+ mes: number;
+ promedio_agua_actual: number;
+}
+
+// Estructura del JSON de salida mensual
+export interface PromedioMensualOutput {
+ metadata: {
+ generatedAt: string;
+ periodoInicio: string;
+ periodoFin: string;
+ };
+ data: Record;
+}
+
+// Estructura del JSON de salida de promedio por mes (agregado sobre los 10 años)
+export interface PromedioAnualPorMesOutput {
+ metadata: {
+ generatedAt: string;
+ periodoInicio: string;
+ periodoFin: string;
+ };
+ data: Record;
+}
+// Genérico de metadata
+export interface Metadata {
+ generatedAt: string;
+ periodoInicio: string;
+ periodoFin: string;
+}
+
+// Estructura del JSON de salida para base de datos (sobre los últimos 10 años)
+interface MonthlyTenYearsAgo {
+ año: number;
+ mes: number;
+ promedio_agua_actual: number;
+}
+export interface HistoricalTenYearsAgo {
+ embalse: string;
+ meses: MonthlyTenYearsAgo[];
+ metadata: Metadata;
+}
+export const createEmptyHistoricalTenYearsAgo = (): HistoricalTenYearsAgo[] => {
+ return [
+ {
+ embalse: "",
+ meses: [
+ {
+ año: new Date().getFullYear(),
+ mes: new Date().getMonth() + 1,
+ promedio_agua_actual: 0,
+ },
+ ],
+ metadata: {
+ generatedAt: "",
+ periodoInicio: "",
+ periodoFin: "",
+ },
+ },
+ ];
+};
+
+// Estructura del JSON de salida para base de datos (del año anterior)
+
+export interface HistoricalLastYear {
+ embalse: string;
+ meses: PromedioAnualPorMesEntry[];
+ metadata: Metadata;
+}
+
+export const createEmptyHistoricalLastYear = (): HistoricalLastYear[] => {
+ return [
+ {
+ embalse: "",
+ meses: [
+ {
+ mes: new Date().getMonth() + 1,
+ promedio_agua_actual: 0,
+ },
+ ],
+ metadata: {
+ generatedAt: "",
+ periodoInicio: "",
+ periodoFin: "",
+ },
+ },
+ ];
+};
diff --git a/packages/db/src/dals/historical/historical.context.ts b/packages/db/src/dals/historical/historical.context.ts
new file mode 100644
index 0000000..1b0f494
--- /dev/null
+++ b/packages/db/src/dals/historical/historical.context.ts
@@ -0,0 +1,15 @@
+import { dbServer } from "#core/servers/index.js";
+import {
+ HistoricalLastYear,
+ HistoricalTenYearsAgo,
+} from "#console-runners/historical-seed/types.js";
+
+export const getHistoricalTenYearsAgoContext = () =>
+ dbServer.db?.collection(
+ "embalsesPromedioHistoricoDiezAnios",
+ );
+
+export const getHistoricalLastYearContext = () =>
+ dbServer.db?.collection(
+ "embalsesPromedioHistoricoPorMeses",
+ );
diff --git a/packages/db/src/dals/historical/historical.repository.ts b/packages/db/src/dals/historical/historical.repository.ts
new file mode 100644
index 0000000..83c832c
--- /dev/null
+++ b/packages/db/src/dals/historical/historical.repository.ts
@@ -0,0 +1,41 @@
+import {
+ HistoricalLastYear,
+ HistoricalTenYearsAgo,
+} from "#console-runners/historical-seed/types.js";
+import {
+ getHistoricalLastYearContext,
+ getHistoricalTenYearsAgoContext,
+} from "./historical.context.js";
+
+export const historicalRepository = {
+ updateHistoricalTenYearsAgo: async (
+ reservoirs: HistoricalTenYearsAgo[],
+ ): Promise => {
+ const { ok } = await getHistoricalTenYearsAgoContext().bulkWrite(
+ reservoirs.map((reservoir) => ({
+ updateOne: {
+ filter: { embalse: reservoir.embalse },
+ update: { $set: reservoir },
+ upsert: true,
+ },
+ })),
+ );
+
+ return ok === 1;
+ },
+ updateHistoricalLastYear: async (
+ reservoirs: HistoricalLastYear[],
+ ): Promise => {
+ const { ok } = await getHistoricalLastYearContext().bulkWrite(
+ reservoirs.map((reservoir) => ({
+ updateOne: {
+ filter: { embalse: reservoir.embalse },
+ update: { $set: reservoir },
+ upsert: true,
+ },
+ })),
+ );
+
+ return ok === 1;
+ },
+};
diff --git a/packages/db/src/dals/historical/index.ts b/packages/db/src/dals/historical/index.ts
new file mode 100644
index 0000000..1a5e59e
--- /dev/null
+++ b/packages/db/src/dals/historical/index.ts
@@ -0,0 +1,2 @@
+export * from "./historical.context.js";
+export * from "./historical.repository.js";
diff --git a/packages/db/src/dals/index.ts b/packages/db/src/dals/index.ts
index 832838a..daca6d7 100644
--- a/packages/db/src/dals/index.ts
+++ b/packages/db/src/dals/index.ts
@@ -1,3 +1,4 @@
export * from "./cuencas/index.js";
export * from "./embalses/index.js";
export * from "./metadatos/index.js";
+export * from "./historical/index.js";