#!/usr/bin/env bash set -Eeuo pipefail ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}" API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}" DUMMY_UUID="00000000-0000-4000-8000-000000000000" TMP_MIGRATION="/tmp/dhv2-d53721-accept-migration-$$.log" TMP_ROUTE="/tmp/dhv2-d5372-route-$$.txt" trap 'rm -f "$TMP_MIGRATION" "$TMP_ROUTE"' EXIT cd "$ROOT" echo "========== D5.3.7.2.1 · VERSIONES ==========" [[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-7.2.1" ]] [[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-7.2.1" ]] grep -Fq "API_PHASE = 'D5.3.7.2.1'" api-v3/src/version.ts echo "API: 0.19.3-7.2.1" echo "WEB: 0.19.3-7.2.1" echo echo "========== D5.3.7.2.1 · DISCO Y DB ==========" FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')" (( FREE_KB >= 2 * 1024 * 1024 )) [[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]] docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"' echo "Libre: $(( FREE_KB / 1024 / 1024 )) GB · PostgreSQL healthy" echo echo "========== D5.3.7.2.1 · MIGRACIONES ==========" docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION" grep -Fq 'Pending migrations: no' "$TMP_MIGRATION" echo echo "========== D5.3.7.2.1 · HEALTH ==========" HEALTH="$(curl -fsS "$API_URL/health")" echo "$HEALTH" grep -Fq '"version":"0.19.3-7.2.1"' <<<"$HEALTH" grep -Fq '"phase":"D5.3.7.2.1"' <<<"$HEALTH" grep -Fq '"database":"ok"' <<<"$HEALTH" echo echo "========== D5.3.7.2.1 · RUTA DE DETALLE PROTEGIDA ==========" CODE="$(curl -sS -o "$TMP_ROUTE" -w '%{http_code}' "$API_URL/asset-imports/$DUMMY_UUID/plan/items")" [[ "$CODE" == "401" ]] echo "/asset-imports/:id/plan/items sin sesión: 401 OK" echo echo "========== D5.3.7.2.1 · MODELO RELACIONAL ==========" DB_CHECK="$( cat <<'SQL' | docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER"' SELECT CASE WHEN to_regclass('public.administrative_departments') IS NOT NULL AND to_regclass('public.area_department_relations') IS NOT NULL THEN 'RELATIONAL_TABLES_OK' ELSE 'RELATIONAL_TABLES_BAD' END; SELECT CASE WHEN has_table_privilege(:'app_user','administrative_departments','SELECT') AND has_table_privilege(:'app_user','administrative_departments','INSERT') AND has_table_privilege(:'app_user','administrative_departments','UPDATE') AND NOT has_table_privilege(:'app_user','administrative_departments','DELETE') AND has_table_privilege(:'app_user','area_department_relations','SELECT') AND has_table_privilege(:'app_user','area_department_relations','INSERT') AND has_table_privilege(:'app_user','area_department_relations','UPDATE') AND NOT has_table_privilege(:'app_user','area_department_relations','DELETE') THEN 'RELATIONAL_PRIVILEGES_OK' ELSE 'RELATIONAL_PRIVILEGES_BAD' END; SELECT CASE WHEN EXISTS (SELECT 1 FROM typeorm_migrations WHERE name='PhaseD5372RelationalTerritoryPlan1787767200000') THEN 'MIGRATION_OK' ELSE 'MIGRATION_BAD' END; SELECT CASE WHEN EXISTS ( SELECT 1 FROM pg_constraint WHERE conname='chk_asset_import_plan_item_kind' AND pg_get_constraintdef(oid) LIKE '%DEPARTMENT%' AND pg_get_constraintdef(oid) LIKE '%AREA_DEPARTMENT_RELATION%' AND pg_get_constraintdef(oid) LIKE '%LEGAL_RIGHT%' AND pg_get_constraintdef(oid) LIKE '%LEGAL_RIGHT_ORGANIZATION%' ) THEN 'PLAN_KINDS_OK' ELSE 'PLAN_KINDS_BAD' END; SQL )" echo "$DB_CHECK" grep -Fq 'RELATIONAL_TABLES_OK' <<<"$DB_CHECK" grep -Fq 'RELATIONAL_PRIVILEGES_OK' <<<"$DB_CHECK" grep -Fq 'MIGRATION_OK' <<<"$DB_CHECK" grep -Fq 'PLAN_KINDS_OK' <<<"$DB_CHECK" echo echo "========== D5.3.7.2.1 · NORMALIZACIÓN Y DEDUPLICACIÓN ==========" grep -Fq 'organizationImportKey' api-v3/src/asset-imports/asset-import-plan.ts grep -Fq 'normalizedLegalRightType' api-v3/src/asset-imports/asset-import-plan.ts grep -Fq "entityKind: 'DEPARTMENT'" api-v3/src/asset-imports/asset-imports.service.ts grep -Fq "entityKind:'AREA_DEPARTMENT_RELATION'" api-v3/src/asset-imports/asset-imports.service.ts || grep -Fq "entityKind: 'AREA_DEPARTMENT_RELATION'" api-v3/src/asset-imports/asset-imports.service.ts grep -Fq "entityKind:'LEGAL_RIGHT'" api-v3/src/asset-imports/asset-imports.service.ts || grep -Fq "entityKind: 'LEGAL_RIGHT'" api-v3/src/asset-imports/asset-imports.service.ts grep -Fq "entityKind:'LEGAL_RIGHT_ORGANIZATION'" api-v3/src/asset-imports/asset-imports.service.ts || grep -Fq "entityKind: 'LEGAL_RIGHT_ORGANIZATION'" api-v3/src/asset-imports/asset-imports.service.ts grep -Fq "operatorUnassigned ? '' : organizationImportKey(operator)" api-v3/src/asset-imports/asset-imports.service.ts grep -Fq "organizationImportKey\(operator\)" api-v3/test/unit/asset-imports-d5-3-7.test.ts || grep -Fq "organizationImportKey(operator)" api-v3/test/unit/asset-imports-d5-3-7.test.ts if grep -Fq "add('departamento'" api-v3/src/asset-imports/asset-imports.service.ts; then echo "ERROR: Departamento no debe persistirse como atributo textual duplicado"; exit 1 fi grep -Fq "status:'PENDING'" api-v3/src/asset-imports/asset-imports.service.ts || grep -Fq "status: 'PENDING'" api-v3/src/asset-imports/asset-imports.service.ts grep -Fq 'no se infiere titularidad' api-v3/src/asset-imports/asset-imports.service.ts echo "Departamentos únicos, siglas de organizaciones, derechos normalizados y relaciones explícitas: OK" echo echo "========== D5.3.7.2.1 · APLICACIÓN Y ROLLBACK ==========" grep -Fq 'INSERT INTO administrative_departments' api-v3/src/asset-imports/asset-imports.service.ts grep -Fq 'INSERT INTO area_department_relations' api-v3/src/asset-imports/asset-imports.service.ts grep -Fq 'INSERT INTO area_legal_rights' api-v3/src/asset-imports/asset-imports.service.ts grep -Fq 'INSERT INTO area_legal_right_organizations' api-v3/src/asset-imports/asset-imports.service.ts grep -Fq "area_department_relations SET valid_until=CURRENT_DATE" api-v3/src/asset-imports/asset-imports.service.ts grep -Fq "area_legal_rights SET status='REVOKED'" api-v3/src/asset-imports/asset-imports.service.ts grep -Fq "administrative_departments SET is_active=false" api-v3/src/asset-imports/asset-imports.service.ts if grep -Eq 'DELETE FROM (assets|administrative_departments|area_department_relations|area_legal_rights)' api-v3/src/asset-imports/asset-imports.service.ts; then echo "ERROR: rollback relacional no debe hacer DELETE físico"; exit 1 fi echo "Aplicación transaccional y reversión lógica relacional: OK" echo echo "========== D5.3.7.2.1 · FRONTEND ==========" grep -Fq 'Plan relacional por entidad' web-v2/src/pages/AssetImportsPage.tsx grep -Fq 'Ver detalle' web-v2/src/pages/AssetImportsPage.tsx grep -Fq 'Departamentos' web-v2/src/pages/AssetImportsPage.tsx grep -Fq 'Marco jurídico' web-v2/src/pages/AssetImportsPage.tsx grep -Fq 'listAssetImportPlanItems' web-v2/src/lib/api.ts echo "Desglose por tipo y detalle paginado: OK" echo echo "========== D5.3.7.2.1 · CONTENEDORES ==========" docker compose ps -a [[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]] [[ "$(docker inspect dhv2-api --format '{{.State.Status}}')" == "running" ]] [[ "$(docker inspect dhv2-web --format '{{.State.Status}}')" == "running" ]] echo echo "ACEPTACIÓN DE FASE D5.3.7.2.1: OK"