panels-origin/api/budget_test.ts
2026-08-21 16:55:01 -05:00

98 lines
4.6 KiB
TypeScript

import { assertEquals, assertStringIncludes } from "jsr:@std/assert@1";
import * as XLSX from "xlsx";
import { buildBudgetTemplate, parseBudgetRows, parseMoney, previewBudgetExcel } from "./budget.ts";
Deno.test("parsea partidas estilo CLAVE DESCRIPCION UNIDAD", () => {
const parsed = parseBudgetRows([
["Obra:", "Barda perimetral"],
["BARDAS PERIMETRALES", "", "", "", "", ""],
["CLAVE", "DESCRIPCION", "UNIDAD", "CANTIDAD", "PRECIO U", "IMPORTE"],
["PRE01", "TRAZO Y NIVELACION", "ml", 92, 10, 920],
["EXC-001", "EXCAVACION", "M3", 8, 850, 6800],
["", "", "", "", "SUB TOTAL", 398545.97],
["", "", "", "", "16% IVA", 63767.36],
["", "", "", "", "TOTAL", 462313.33],
]);
assertEquals(parsed.items.length, 2);
assertEquals(parsed.items[0].code, "PRE01");
assertEquals(parsed.items[0].quantity, 92);
assertEquals(parsed.items[0].amount, 920);
assertEquals(parsed.items[0].chapter, "BARDAS PERIMETRALES");
assertEquals(parsed.items[1].code, "EXC-001");
});
Deno.test("parsea plantilla con columna CAPITULO", () => {
const parsed = parseBudgetRows([
["CAPITULO", "CLAVE", "DESCRIPCION", "UNIDAD", "CANTIDAD", "PRECIO", "IMPORTE"],
["Acabados", "ACA01", "Aplanado", "M2", 10, 20, 200],
["Cimentacion", "CIM01", "Zapata", "PZA", 2, 100, 200],
]);
assertEquals(parsed.items.map((item) => item.chapter), ["Acabados", "Cimentacion"]);
assertEquals(parsed.items[0].amount, 200);
});
Deno.test("lee importes con coma o signo", () => {
assertEquals(parseMoney("$ 1,500.00"), 1500);
assertEquals(parseMoney("201.47"), 201.47);
});
Deno.test("parsea Opus/Neodata: agrupadores, continuaciones y totales ignorados", () => {
const parsed = parseBudgetRows([
["Código", "Concepto", "Unidad", "Cantidad", "P. Unitario", "Importe", "%"],
["A", "LOCAL L224", "", "", "", "", ""],
["A001", "PRELIMINARES Y ALBAÑILERÍA", "", "", "", "", ""],
["10301-001", "Trazo y nivelación manual para establecer ejes, banco", "M2", 61.9, 15.53, 961.31, 0.000847],
["", "de nivel y referencias, incluye: materiales, mano de", "", 0, 0, 0, 0],
["", "obra, equipo y herramienta.", "", 0, 0, 0, 0],
["A001", "TOTAL PRELIMINARES Y ALBAÑILERÍA", "", "", "", 123219.19, 0.1],
["", "IVA 16.00%", "", "", "", 181433.44, ""],
["", "TOTAL DEL PRESUPUESTO MOSTRADO:", "", "", "", 1315392.47, ""],
]);
assertEquals(parsed.items.length, 1);
assertEquals(parsed.groups.map((group) => group.code), ["A", "A001"]);
assertEquals(parsed.groups[0].kind, "lote");
assertEquals(parsed.groups[1].parentWbs, parsed.groups[0].wbs);
assertEquals(parsed.items[0].code, "10301-001");
assertEquals(parsed.items[0].chapter, "PRELIMINARES Y ALBAÑILERÍA");
assertEquals(parsed.items[0].parentWbs, parsed.groups[1].wbs);
assertStringIncludes(parsed.items[0].description, "Trazo y nivelación manual");
assertStringIncludes(parsed.items[0].description, "banco de nivel y referencias");
assertStringIncludes(parsed.items[0].description, "equipo y herramienta");
});
Deno.test("parsea plantilla WBS lote-capitulo-concepto", () => {
const parsed = parseBudgetRows([
["WBS", "TIPO", "CODIGO", "DESCRIPCION", "UNIDAD", "CANTIDAD", "PRECIO"],
["1", "lote", "A", "LOCAL L224", "", "", ""],
["1.01", "capitulo", "A001", "Preliminares y albañilería", "", "", ""],
["1.01.01", "concepto", "10301-001", "Trazo y nivelación manual", "M2", 61.9, 15.53],
["1.02", "capitulo", "A002", "Instalación hidrosanitaria", "", "", ""],
["1.02.01", "concepto", "11402-001", "Inodoro", "PZA", 1, 100],
]);
assertEquals(parsed.groups.length, 3);
assertEquals(parsed.groups[0].kind, "lote");
assertEquals(parsed.groups[1].parentWbs, "1");
assertEquals(parsed.items.length, 2);
assertEquals(parsed.items[0].parentWbs, "1.01");
assertEquals(parsed.items[0].chapter, "Preliminares y albañilería");
assertEquals(parsed.items[1].parentWbs, "1.02");
assertEquals(parsed.items[0].amount, 961.31);
});
Deno.test("la plantilla es un xlsx valido", () => {
const bytes = buildBudgetTemplate();
assertEquals(bytes[0], 0x50);
assertEquals(bytes[1], 0x4b);
const wb = XLSX.read(bytes, { type: "array" });
assertEquals(wb.SheetNames.includes("PRESUPUESTO"), true);
assertEquals(wb.SheetNames.includes("INSTRUCCIONES"), true);
});
Deno.test("preview de plantilla no guarda y arma arbol", () => {
const preview = previewBudgetExcel(buildBudgetTemplate());
assertEquals(preview.format, "wbs");
assertEquals(preview.errors.length, 0);
assertEquals(preview.tree[0]?.code, "A");
assertEquals(preview.tree[0]?.children.length, 2);
assertEquals(preview.totals.item_count, 4);
});