diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7fe93e6..e3f10d2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -47,6 +47,8 @@ jobs:
cache-dependency-path: web-v2/package-lock.json
- run: npm ci
- run: npm run typecheck
+ - name: F3.1 WEB contract
+ run: bash ../scripts/check-f3-1-web-contract.sh
- run: npm run build
contract:
@@ -62,5 +64,15 @@ jobs:
done < <(find scripts -type f -name '*.sh' -print0)
- name: Validate Compose
run: docker compose --env-file .env.example config >/dev/null
+ - name: VPS-equivalent isolated API preflight
+ run: |
+ set -Eeuo pipefail
+ image="dhv2-api:ci-vps-preflight-${GITHUB_SHA::12}"
+ docker build --target builder -t "$image" api-v3
+ docker run --rm \
+ -v "$PWD/api-v3/test:/app/test:ro" \
+ -v "$PWD/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
+ "$image" npm test
+ docker image rm "$image" >/dev/null 2>&1 || true
- name: Build production images
run: docker compose --env-file .env.example build api migrate web
diff --git a/api-v3/test/unit/f3-1-inventory-structure.test.ts b/api-v3/test/unit/f3-1-inventory-structure.test.ts
index ca63f5e..9761435 100644
--- a/api-v3/test/unit/f3-1-inventory-structure.test.ts
+++ b/api-v3/test/unit/f3-1-inventory-structure.test.ts
@@ -52,15 +52,3 @@ test('F3.1 fuerza Área → Yacimiento → Instalación → Subinstalación en s
assert.match(service, /SUBINSTALACION: 'instalacion'/);
assert.match(service, /INVENTORY_SUBINSTALLATION_FAMILY_PARENT_INVALID/);
});
-
-test('F3.1 retira Relevamientos del dashboard y usa alta guiada de cuatro niveles', () => {
- const layout = readFileSync('../web-v2/src/layout/AppLayout.tsx', 'utf8');
- const app = readFileSync('../web-v2/src/app/App.tsx', 'utf8');
- const page = readFileSync('../web-v2/src/pages/InventoryCreatePage.tsx', 'utf8');
- assert.doesNotMatch(layout, /label: 'Relevamientos'/);
- assert.match(app, /path="\/relevamiento\/\*" element={}/);
- assert.match(app, /path="\/inventarios\/nuevo" element={}/);
- for (const kind of ['AREA', 'YACIMIENTO', 'INSTALACION', 'SUBINSTALACION']) assert.match(page, new RegExp(`kind: '${kind}'`));
- assert.doesNotMatch(page, /kind: 'EQUIPO'/);
- assert.match(page, /getInventoryFamilyFindings/);
-});
diff --git a/scripts/check-f3-1-web-contract.sh b/scripts/check-f3-1-web-contract.sh
new file mode 100644
index 0000000..83acb70
--- /dev/null
+++ b/scripts/check-f3-1-web-contract.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+set -Eeuo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+LAYOUT="$ROOT/web-v2/src/layout/AppLayout.tsx"
+APP="$ROOT/web-v2/src/app/App.tsx"
+PAGE="$ROOT/web-v2/src/pages/InventoryCreatePage.tsx"
+
+for file in "$LAYOUT" "$APP" "$PAGE"; do
+ test -f "$file" || {
+ echo "F3.1 WEB contract: falta $file"
+ exit 1
+ }
+done
+
+if grep -Fq "label: 'Relevamientos'" "$LAYOUT"; then
+ echo "F3.1 WEB contract: Relevamientos no debe volver al menú principal"
+ exit 1
+fi
+
+grep -Fq 'path="/relevamiento/*"' "$APP"
+grep -Fq 'Navigate to="/inventarios" replace' "$APP"
+grep -Fq 'path="/inventarios/nuevo"' "$APP"
+grep -Fq 'InventoryCreatePage' "$APP"
+
+for kind in AREA YACIMIENTO INSTALACION SUBINSTALACION; do
+ grep -Fq "kind: '$kind'" "$PAGE"
+done
+
+if grep -Fq "kind: 'EQUIPO'" "$PAGE"; then
+ echo "F3.1 WEB contract: el alta guiada no debe restaurar EQUIPO como quinto nivel estructural"
+ exit 1
+fi
+
+grep -Fq 'getInventoryFamilyFindings' "$PAGE"
+
+echo 'F3.1 WEB contract: OK'