chore: import DH V2 D5.6.4 production baseline

This commit is contained in:
DH V2
2026-09-05 10:12:35 -03:00
commit 82213e72f5
757 changed files with 84218 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-${1:-/var/www/dhv2.korexlabs.com}}"
BACKUP_ROOT="${BACKUP_ROOT:-/root/DH_V2_BACKUPS}"
STAMP="${STAMP:-$(date +%Y%m%d_%H%M%S)}"
BACKUP_DIR="${BACKUP_DIR:-$BACKUP_ROOT/INVENTARIO_A_REVISAR_$STAMP}"
MARKER="$BACKUP_DIR/INVENTARIO A REVISAR.txt"
MIN_FREE_KB=$((12 * 1024 * 1024))
fail() { echo "ERROR BACKUP INVENTARIO A REVISAR: $*" >&2; exit 1; }
cd "$ROOT"
[[ -f docker-compose.yml ]] || fail "No existe docker-compose.yml en $ROOT"
[[ -f api-v3/package.json ]] || fail "No se encontró api-v3/package.json"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
[[ "$FREE_KB" =~ ^[0-9]+$ ]] || fail "No se pudo determinar espacio libre"
(( FREE_KB >= MIN_FREE_KB )) || fail "Se requieren al menos 12 GB libres para el backup fuerte; hay $(( FREE_KB / 1024 / 1024 )) GB"
DB_HEALTH="$(docker inspect dhv2-db --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' 2>/dev/null || true)"
[[ "$DB_HEALTH" == "healthy" ]] || fail "PostgreSQL no está healthy (estado: $DB_HEALTH)"
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"' >/dev/null
umask 077
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
API_VERSION="$(node -p "require('./api-v3/package.json').version")"
WEB_VERSION="$(node -p "require('./web-v2/package.json').version")"
PHASE="$(sed -n "s/.*API_PHASE = '\([^']*\)'.*/\1/p" api-v3/src/version.ts | head -n1)"
cat > "$MARKER" <<TXT
INVENTARIO A REVISAR
Backup fuerte previo al cambio conceptual de inventarios.
Objetivo: poder volver al estado exacto anterior si la definición cambia después de la reunión.
Fecha: $(date --iso-8601=seconds)
Servidor: $(hostname)
Proyecto: $ROOT
API: $API_VERSION
WEB: $WEB_VERSION
Fase: $PHASE
Este backup contiene:
- dump PostgreSQL en formato custom;
- código fuente del proyecto;
- volumen completo de asset-media/importaciones;
- imágenes Docker actuales de api/web/migrate;
- docker-compose y .env (si existe, permisos 600);
- estado de contenedores, migraciones y hashes SHA-256.
TXT
chmod 600 "$MARKER"
printf '%s\n' "[1/8] PostgreSQL"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$BACKUP_DIR/database.dump"
[[ -s "$BACKUP_DIR/database.dump" ]] || fail "database.dump quedó vacío"
docker compose exec -T db sh -lc 'pg_restore -l' < "$BACKUP_DIR/database.dump" > "$BACKUP_DIR/database-list.txt"
[[ -s "$BACKUP_DIR/database-list.txt" ]] || fail "No se pudo validar database.dump"
cat <<'SQL' | docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1' > "$BACKUP_DIR/inventory-state.txt"
\echo '========== INVENTARIO A REVISAR · ESTADO DEL MAESTRO =========='
SELECT count(*) AS assets_total FROM assets;
SELECT type.code,type.name,count(asset.id) AS cantidad FROM asset_types type LEFT JOIN assets asset ON asset.asset_type_id=type.id AND asset.information_status<>'INACTIVE' GROUP BY type.id,type.code,type.name ORDER BY type.code;
\echo ''
\echo '========== IMPORTACIONES =========='
SELECT batch.id,batch.original_name,batch.status,plan.revision,plan.status AS plan_status,plan.applied_at FROM asset_import_batches batch LEFT JOIN LATERAL (SELECT revision,status,applied_at FROM asset_import_plans p WHERE p.batch_id=batch.id ORDER BY revision DESC LIMIT 1) plan ON true ORDER BY batch.created_at DESC;
SQL
printf '%s\n' "[2/8] Source"
tar \
--exclude='api-v3/node_modules' \
--exclude='api-v3/dist' \
--exclude='web-v2/node_modules' \
--exclude='web-v2/dist' \
--exclude='.git' \
--exclude='DH_V2_FASE_*.zip' \
-czf "$BACKUP_DIR/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
[[ -s "$BACKUP_DIR/source.tar.gz" ]] || fail "source.tar.gz quedó vacío"
tar -tzf "$BACKUP_DIR/source.tar.gz" >/dev/null
printf '%s\n' "[3/8] Configuración privada"
cp -a docker-compose.yml "$BACKUP_DIR/docker-compose.yml"
if [[ -f .env ]]; then
cp -a .env "$BACKUP_DIR/.env"
chmod 600 "$BACKUP_DIR/.env"
fi
printf '%s\n' "[4/8] Asset media / archivos importados"
docker volume inspect dhv2_asset_media >/dev/null
# nginx:alpine ya forma parte del stack web y aporta tar/gzip BusyBox sin instalar nada en el host.
docker run --rm \
-v dhv2_asset_media:/source:ro \
-v "$BACKUP_DIR:/backup" \
--entrypoint sh nginx:alpine \
-c 'cd /source && tar -czf /backup/asset-media.tar.gz .'
[[ -s "$BACKUP_DIR/asset-media.tar.gz" ]] || fail "asset-media.tar.gz quedó vacío"
tar -tzf "$BACKUP_DIR/asset-media.tar.gz" >/dev/null
printf '%s\n' "[5/8] Imágenes Docker exactas"
for image in dhv2-api:latest dhv2-web:latest dhv2-migrate:latest; do
docker image inspect "$image" >/dev/null || fail "Falta la imagen $image"
done
docker image save dhv2-api:latest dhv2-web:latest dhv2-migrate:latest | gzip -1 > "$BACKUP_DIR/docker-images.tar.gz"
[[ -s "$BACKUP_DIR/docker-images.tar.gz" ]] || fail "docker-images.tar.gz quedó vacío"
gzip -t "$BACKUP_DIR/docker-images.tar.gz"
printf '%s\n' "[6/8] Estado operativo"
docker compose ps -a > "$BACKUP_DIR/docker-status.txt"
for image in dhv2-api:latest dhv2-web:latest dhv2-migrate:latest; do docker image inspect "$image" --format '{{.RepoTags}} {{.Id}} {{.Created}}'; done > "$BACKUP_DIR/docker-images.txt"
docker compose --profile tools run --rm migrate npm run migration:show > "$BACKUP_DIR/migrations.txt" 2>&1
grep -Fq 'Pending migrations: no' "$BACKUP_DIR/migrations.txt" || fail "Hay migraciones pendientes; backup no se marca como aceptado"
curl -fsS http://127.0.0.1:3101/api/v3/health > "$BACKUP_DIR/health.json"
printf '%s\n' "[7/8] Runbook de restauración"
cat > "$BACKUP_DIR/RESTORE_INVENTARIO_A_REVISAR.txt" <<'TXT'
RESTAURACIÓN — INVENTARIO A REVISAR
No ejecutar estas acciones durante operación normal. Este paquete es un punto de retorno.
Contenido principal:
- database.dump PostgreSQL completo
- source.tar.gz código fuente previo
- asset-media.tar.gz volumen de archivos/importaciones
- docker-images.tar.gz imágenes exactas api/web/migrate
- .env secretos/configuración previa (si existía)
- docker-compose.yml compose previo
Orden recomendado de recuperación:
1) detener api/web/migrate y mantener una copia del estado actual;
2) restaurar source.tar.gz y .env/docker-compose.yml;
3) cargar docker-images.tar.gz con: gzip -dc docker-images.tar.gz | docker image load;
4) restaurar PostgreSQL desde database.dump con el usuario owner;
5) restaurar dhv2_asset_media desde asset-media.tar.gz;
6) recrear api/web y verificar /api/v3/health y migration:show.
La restauración destructiva debe hacerse sólo después de revisar el incidente y conservar un backup del estado que se reemplazará.
TXT
chmod 600 "$BACKUP_DIR/RESTORE_INVENTARIO_A_REVISAR.txt"
printf '%s\n' "[8/8] Integridad SHA-256"
cat > "$BACKUP_DIR/BACKUP_OK.txt" <<TXT
INVENTARIO A REVISAR — BACKUP OK
Fecha: $(date --iso-8601=seconds)
API: $API_VERSION
WEB: $WEB_VERSION
Fase: $PHASE
DB: validada con pg_restore -l
Asset media: tar validado
Docker images: gzip validado
SHA-256: OK
TXT
chmod 600 "$BACKUP_DIR/BACKUP_OK.txt"
(
cd "$BACKUP_DIR"
find . -maxdepth 1 -type f ! -name 'SHA256SUMS.txt' -printf '%P\0' \
| sort -z \
| xargs -0 sha256sum
) > "$BACKUP_DIR/SHA256SUMS.txt"
chmod 600 "$BACKUP_DIR/SHA256SUMS.txt"
(
cd "$BACKUP_DIR"
sha256sum -c SHA256SUMS.txt >/dev/null
) || fail "Falló la validación SHA-256"
printf '\nBACKUP FUERTE INVENTARIO A REVISAR: OK\n'
printf 'Ruta: %s\n' "$BACKUP_DIR"
du -sh "$BACKUP_DIR"
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_project="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
dhv2_label="${1:-manual}"
dhv2_stamp="$(date +%Y%m%d_%H%M%S)"
if [[ ! "$dhv2_label" =~ ^[a-zA-Z0-9_-]{1,40}$ ]]; then
echo "Etiqueta de backup inválida" >&2
exit 1
fi
dhv2_backup="/var/www/dh-backup/dhv2-${dhv2_label}-${dhv2_stamp}"
install -d -m 700 "$dhv2_backup"
cd "$dhv2_project"
test -f .env || { echo "No se encontró $dhv2_project/.env" >&2; exit 1; }
docker compose ps --status running >/dev/null
tar \
--exclude='*.zip' \
--exclude='.env' \
--exclude='*/node_modules' \
--exclude='*/dist' \
--exclude='*.tsbuildinfo' \
-czf "$dhv2_backup/source.tar.gz" .
install -m 600 .env "$dhv2_backup/.env"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$dhv2_backup/database.dump"
dhv2_media_file=""
if docker volume inspect dhv2_asset_media >/dev/null 2>&1; then
dhv2_media_file="asset-media.tar.gz"
docker compose exec -T api sh -lc \
'test -d "$ASSET_MEDIA_ROOT" && tar -czf - -C "$ASSET_MEDIA_ROOT" .' \
> "$dhv2_backup/$dhv2_media_file"
chmod 600 "$dhv2_backup/$dhv2_media_file"
fi
dhv2_nginx_file=""
for dhv2_candidate in \
/etc/nginx/sites-available/dhv2.korexlabs.com \
/etc/nginx/sites-available/dhv2.korexlabs.com.conf \
/etc/nginx/sites-enabled/dhv2.korexlabs.com \
/etc/nginx/sites-enabled/dhv2.korexlabs.com.conf; do
if [ -f "$dhv2_candidate" ]; then
dhv2_nginx_file="nginx-site.conf"
install -m 600 "$dhv2_candidate" "$dhv2_backup/$dhv2_nginx_file"
break
fi
done
if [ -z "$dhv2_nginx_file" ]; then
dhv2_nginx_file="NGINX_CONFIG_NOT_FOUND.txt"
printf '%s\n' 'No se encontró una configuración Nginx en las rutas conocidas.' > "$dhv2_backup/$dhv2_nginx_file"
chmod 600 "$dhv2_backup/$dhv2_nginx_file"
fi
{
echo "Proyecto: DH Inspección V2"
echo "Fase: D5.2"
echo "Versión: 0.19.2"
echo "Fecha UTC: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "Origen: $dhv2_project"
echo "Base: dump PostgreSQL custom format"
if [ -n "$dhv2_media_file" ]; then
echo "Archivos protegidos de activos e inspecciones: $dhv2_media_file"
else
echo "Archivos protegidos: volumen todavía inexistente"
fi
echo
echo "Contenedores al generar el backup:"
docker compose ps
} > "$dhv2_backup/MANIFEST.txt"
chmod 600 "$dhv2_backup/MANIFEST.txt"
(
cd "$dhv2_backup"
dhv2_checksum_files=(source.tar.gz database.dump .env "$dhv2_nginx_file" MANIFEST.txt)
if [ -n "$dhv2_media_file" ]; then
dhv2_checksum_files+=("$dhv2_media_file")
fi
sha256sum "${dhv2_checksum_files[@]}" > SHA256SUMS
)
chmod 600 "$dhv2_backup/SHA256SUMS"
echo "$dhv2_backup"
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
cd "$ROOT"
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.6-3" ]]
grep -Fq "APP_PHASE = 'Fase D5.6.3 · Navegación contextual'" web-v2/src/config/version.ts
grep -Fq 'OperationalContextBar' web-v2/src/layout/AppLayout.tsx
grep -Fq 'OperationalContextProvider' web-v2/src/app/App.tsx
grep -Fq 'path="/planificacion"' web-v2/src/app/App.tsx
! grep -Fq "label: 'Planificación'" web-v2/src/layout/AppLayout.tsx
grep -Fq 'Planificar inspección' web-v2/src/pages/InspectionVisitsPage.tsx
grep -Fq 'inspection-status-tabs' web-v2/src/pages/InspectionVisitsPage.tsx
grep -Fq 'useOperationalContext' web-v2/src/pages/AssetsPage.tsx
grep -Fq 'useOperationalContext' web-v2/src/pages/ActsPage.tsx
grep -Fq 'useOperationalContext' web-v2/src/pages/ReportsPage.tsx
grep -Fq 'useOperationalContext' web-v2/src/pages/FindingsPage.tsx
grep -Fq 'operationalContext.areaId' web-v2/src/pages/InspectionVisitEditorPage.tsx
echo 'D5.6.3 dashboard checks: 13/13 OK'
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
cd "$ROOT"
checks=0
ok() { checks=$((checks+1)); echo "OK $1"; }
fail() { echo "FAIL $1"; exit 1; }
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.6-4" ]] || fail 'API 0.19.6-4'
ok 'API 0.19.6-4'
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.6-4" ]] || fail 'WEB 0.19.6-4'
ok 'WEB 0.19.6-4'
grep -Fq "API_PHASE = 'D5.6.4'" api-v3/src/version.ts || fail 'fase API'
ok 'fase API D5.6.4'
grep -Fq "Fase D5.6.4 · Inspecciones simples y combobox buscables" web-v2/src/config/version.ts || fail 'fase WEB'
ok 'fase WEB D5.6.4'
DTO='api-v3/src/inspection-visits/dto/create-inspection-visit.dto.ts'
grep -Fq 'operationalAreaId!' "$DTO" && grep -Fq 'operatorCompanyId!' "$DTO" && grep -Fq 'plannedStartAt!' "$DTO" && grep -Fq 'leadInspectorUserId!' "$DTO" || fail 'DTO mínimo'
! grep -Eq 'code!|title!|plannedEndAt' "$DTO" || fail 'DTO conserva campos eliminados'
ok 'creación mínima Área + Operadora + inicio + Inspector'
grep -Fq 'pg_advisory_xact_lock' api-v3/src/inspection-visits/inspection-visit-code.ts || fail 'lock código'
grep -Fq 'America/Argentina/Mendoza' api-v3/src/inspection-visits/inspection-visit-code.ts || fail 'zona código'
grep -Fq 'INS-${year}-${String(sequence).padStart(6, '\''0'\'')}' api-v3/src/inspection-visits/inspection-visit-code.ts || fail 'formato código'
ok 'código INS anual automático y transaccional'
grep -Fq 'title: code' api-v3/src/inspection-visits/inspection-visits.service.ts || fail 'título interno'
grep -Fq 'plannedEndAt: null' api-v3/src/inspection-visits/inspection-visits.service.ts || fail 'fin null'
ok 'sin título editable ni fin planificado nuevo'
grep -Fq 'leadInspectorUserId: dto.leadInspectorUserId' api-v3/src/inspection-visits/inspection-visits.service.ts || fail 'lead create'
grep -Fq 'await this.replaceMemberLinks(' api-v3/src/inspection-visits/inspection-visits.service.ts || fail 'team create'
grep -Fq 'generateChecklistInternal(manager, visit, principal.userId)' api-v3/src/inspection-visits/inspection-visits.service.ts || fail 'checklist create'
ok 'inspector + equipo + checklist en creación'
grep -Fq "@RequirePermissions('inspections.manage', 'inspections.assign')" api-v3/src/inspection-visits/inspection-visits.controller.ts || fail 'permisos create'
ok 'creación requiere planificación y asignación'
! grep -Fq 'plannedEndAt' api-v3/src/inspection-verifications/dto/plan-verification-visit.dto.ts || fail 'fin verificación DTO'
grep -Fq 'nextInspectionVisitCode(manager, plannedStartAt)' api-v3/src/inspection-verifications/inspection-verifications.service.ts || fail 'código verificación'
ok 'verificaciones usan misma numeración y sólo inicio'
grep -Fq 'CREACIÓN RÁPIDA' web-v2/src/pages/InspectionVisitEditorPage.tsx || fail 'creación rápida UI'
grep -Fq 'Fecha y hora de inicio' web-v2/src/pages/InspectionVisitEditorPage.tsx || fail 'inicio UI'
grep -Fq 'Buscar Inspector…' web-v2/src/pages/InspectionVisitEditorPage.tsx || fail 'inspector buscable'
! grep -Fq 'Fin previsto' web-v2/src/pages/InspectionVisitEditorPage.tsx || fail 'fin UI'
! grep -Fq '<span>Título</span>' web-v2/src/pages/InspectionVisitEditorPage.tsx || fail 'título UI'
ok 'formulario rápido de Inspección'
grep -Fq 'searchable-select-search' web-v2/src/styles.css || fail 'estilos combobox'
test -f web-v2/src/components/SearchableSelect.tsx || fail 'componente combobox'
ok 'componente de combobox buscable'
if grep -R '<select' web-v2/src --include='*.tsx' | grep -v 'components/SearchableSelect.tsx' >/dev/null; then
echo 'Quedaron selects nativos:'
grep -R '<select' web-v2/src --include='*.tsx' | grep -v 'components/SearchableSelect.tsx'
fail 'selects nativos'
fi
ok 'todos los combobox del dashboard usan buscador'
COUNT="$(grep -R '<SearchableSelect' web-v2/src --include='*.tsx' | wc -l | tr -d ' ')"
(( COUNT >= 59 )) || fail 'cantidad combobox migrados'
ok "$COUNT usos de SearchableSelect"
if find api-v3/src/database/migrations -maxdepth 1 -type f -iname '*d5-6-4*' | grep -q .; then fail 'migración inesperada'; fi
ok 'sin migración D5.6.4'
echo
echo "D5.6.4 checks: $checks/$checks OK"
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_PATCH_DASHBOARD_D5_6_3.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-dashboard-d563-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_6_3_DASH_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_6_3_DASH_OK_$STAMP"
TEST_IMAGE="dhv2-web:d563-test"
cleanup() {
rm -rf "$STAGE"
docker image rm "$TEST_IMAGE" >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.6.3 dashboard en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_WEB" == "0.19.6-2" ]] || { echo "ERROR: este patch requiere WEB 0.19.6-2. Actual: $CURRENT_WEB"; exit 1; }
echo '[1/7] Staging del patch'
mkdir -p "$STAGE"
cp -a web-v2 "$STAGE/web-v2"
unzip -oq "$ZIP" 'web-v2/*' -d "$STAGE"
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.6-3" ]]
grep -Fq "APP_PHASE = 'Fase D5.6.3 · Navegación contextual'" "$STAGE/web-v2/src/config/version.ts"
echo '[2/7] Build web en staging'
docker build -t "$TEST_IMAGE" "$STAGE/web-v2"
echo '[3/7] Validaciones de navegación'
grep -Fq 'OperationalContextBar' "$STAGE/web-v2/src/layout/AppLayout.tsx"
grep -Fq 'OperationalContextProvider' "$STAGE/web-v2/src/app/App.tsx"
grep -Fq 'path="/planificacion"' "$STAGE/web-v2/src/app/App.tsx"
! grep -Fq "label: 'Planificación'" "$STAGE/web-v2/src/layout/AppLayout.tsx"
grep -Fq 'Planificar inspección' "$STAGE/web-v2/src/pages/InspectionVisitsPage.tsx"
grep -Fq 'inspection-status-tabs' "$STAGE/web-v2/src/pages/InspectionVisitsPage.tsx"
grep -Fq 'useOperationalContext' "$STAGE/web-v2/src/pages/AssetsPage.tsx"
grep -Fq 'useOperationalContext' "$STAGE/web-v2/src/pages/ActsPage.tsx"
grep -Fq 'useOperationalContext' "$STAGE/web-v2/src/pages/ReportsPage.tsx"
grep -Fq 'useOperationalContext' "$STAGE/web-v2/src/pages/FindingsPage.tsx"
grep -Fq 'operationalContext.areaId' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
echo '[4/7] Backup PRE del dashboard'
mkdir -p "$PRE_BACKUP"; chmod 700 "$PRE_BACKUP"
tar -czf "$PRE_BACKUP/web-v2-source.tar.gz" --exclude='web-v2/node_modules' --exclude='web-v2/dist' web-v2
printf 'WEB previa=%s\nAPI sin cambios=%s\n' "$CURRENT_WEB" "$(node -p "require('./api-v3/package.json').version")" > "$PRE_BACKUP/BASE_VERSION.txt"
( cd "$PRE_BACKUP" && sha256sum web-v2-source.tar.gz BASE_VERSION.txt > SHA256SUMS.txt && sha256sum -c SHA256SUMS.txt )
echo '[5/7] Aplicando patch incremental'
unzip -oq "$ZIP" 'web-v2/*' 'scripts/check-dashboard-d5-6-3.sh' 'scripts/deploy-dashboard-d5-6-3.sh' -d "$ROOT"
chmod +x scripts/check-dashboard-d5-6-3.sh scripts/deploy-dashboard-d5-6-3.sh
bash scripts/check-dashboard-d5-6-3.sh
echo '[6/7] Build y recreación del dashboard'
docker compose build web
docker compose up -d --force-recreate web
for i in $(seq 1 30); do
WEB_ADDR="$(docker compose port web 80 2>/dev/null | tail -1)"
WEB_PORT="${WEB_ADDR##*:}"
if [[ "$WEB_PORT" =~ ^[0-9]+$ ]] && curl -fsS "http://127.0.0.1:$WEB_PORT/" >/dev/null 2>&1; then break; fi
if [[ "$i" == "30" ]]; then docker compose logs --tail=160 web; exit 1; fi
sleep 1
done
echo '[7/7] Backup POST'
mkdir -p "$POST_BACKUP"; chmod 700 "$POST_BACKUP"
tar -czf "$POST_BACKUP/web-v2-source.tar.gz" --exclude='web-v2/node_modules' --exclude='web-v2/dist' web-v2
cp -a "$ZIP" "$POST_BACKUP/"
printf 'D5.6.3 DASHBOARD OK\nWEB final=0.19.6-3\nAPI sin cambios=%s\n' "$(node -p "require('./api-v3/package.json').version")" > "$POST_BACKUP/BACKUP_OK.txt"
( cd "$POST_BACKUP" && sha256sum web-v2-source.tar.gz "$(basename "$ZIP")" BACKUP_OK.txt > SHA256SUMS.txt && sha256sum -c SHA256SUMS.txt )
echo
echo 'D5.6.3 DASHBOARD: OK'
echo 'WEB: 0.19.6-3'
echo "API: $(node -p "require('./api-v3/package.json').version") (sin cambios)"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d531-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_1_OK_$STAMP"
cleanup() { rm -rf "$STAGE"; }
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/10] Preparando staging"
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d531-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d531-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d531-test "$STAGE/web-v2"
echo "[5/10] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
--exclude='.git' --exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
echo "[6/10] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/phase-d5-3-1-acceptance.sh" "$ROOT/scripts/deploy-phase-d5-3-1.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando que no haya migraciones nuevas"
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -q 'Pending migrations: no' <<<"$MIGRATION_OUTPUT"
echo "[9/10] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >/tmp/dhv2-d531-health.json 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat /tmp/dhv2-d531-health.json; echo
echo "[10/10] Aceptación y backup POST"
bash "$ROOT/scripts/phase-d5-3-1-acceptance.sh"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.1 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_10_2_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53102-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_10_2_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_10_2_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53102-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53102-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53102-test dhv2-web:d53102-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.10.2 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-10.2" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-10.2" ]]
grep -Fq "API_PHASE = 'D5.3.10.2'" "$STAGE/api-v3/src/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d53102-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53102-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d53102-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.10.2"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.10.2"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-10-2.sh" "$ROOT/scripts/phase-d5-3-10-2-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando que D5.3.10.2 no tenga migraciones pendientes"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-10.2"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.10.2"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-10-2-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.10.2: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.10.2 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_11_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53111-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_11_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_11_1_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53111-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53111-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53111-test dhv2-web:d53111-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.11.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-11" ]]
[[ "$CURRENT_WEB" == "0.19.3-11" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-11.1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-11.1" ]]
grep -Fq "API_PHASE = 'D5.3.11.1'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.11.1 · Navegación de importados'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d53111-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53111-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d53111-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.11.1"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.11.1"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-11-1.sh" "$ROOT/scripts/phase-d5-3-11-1-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando que D5.3.11.1 no tenga migraciones pendientes"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-11.1"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.11.1"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-11-1-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.11.1: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.11.1 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_11_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5311-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_11_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_11_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5311-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5311-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5311-test dhv2-web:d5311-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.11 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-10.2" ]]
[[ "$CURRENT_WEB" == "0.19.3-10.2" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-11" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-11" ]]
grep -Fq "API_PHASE = 'D5.3.11'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.11 · Importación parcial y revisiones'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5311-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5311-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5311-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.11"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.11"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-11.sh" "$ROOT/scripts/phase-d5-3-11-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando que D5.3.11 no tenga migraciones pendientes"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-11"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.11"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-11-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.11: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.11 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_12_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5312-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_12_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_12_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5312-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5312-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5312-test dhv2-web:d5312-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.12 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-11.1" ]]
[[ "$CURRENT_WEB" == "0.19.3-11.1" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-12" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-12" ]]
grep -Fq "API_PHASE = 'D5.3.12'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.12 · Inventarios por empresa'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5312-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5312-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5312-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.12"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.12"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-12.sh" "$ROOT/scripts/phase-d5-3-12-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando que D5.3.12 no tenga migraciones pendientes"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-12"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.12"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-12-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.12: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.12 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_13_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5313-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_13_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_13_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5313-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5313-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5313-test dhv2-web:d5313-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.13 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-12" ]]
[[ "$CURRENT_WEB" == "0.19.3-12" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-13" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-13" ]]
grep -Fq "API_PHASE = 'D5.3.13'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.13 · Seguimiento de hallazgos'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5313-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5313-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5313-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.13"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.13"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-13.sh" "$ROOT/scripts/phase-d5-3-13-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando que D5.3.13 no tenga migraciones pendientes"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-13"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.13"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-13-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.13: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.13 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_14_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5314-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_14_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_14_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5314-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5314-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5314-test dhv2-web:d5314-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.14 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-13" ]]
[[ "$CURRENT_WEB" == "0.19.3-13" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-14" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-14" ]]
grep -Fq "API_PHASE = 'D5.3.14'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.14 · Expediente técnico'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5314-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5314-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5314-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.14"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.14"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-14.sh" "$ROOT/scripts/phase-d5-3-14-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando que D5.3.14 no tenga migraciones pendientes"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-14"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.14"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-14-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.14: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.14 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_15_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53151-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_15_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_15_1_OK_$STAMP"
MIGRATION_RUN_LOG="/tmp/dhv2-d53151-migration-run-$STAMP.log"
MIGRATION_SHOW_LOG="/tmp/dhv2-d53151-migration-show-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53151-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_RUN_LOG" "$MIGRATION_SHOW_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53151-test dhv2-web:d53151-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.15.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-14" ]]
[[ "$CURRENT_WEB" == "0.19.3-14" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-15.1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-15.1" ]]
grep -Fq "API_PHASE = 'D5.3.15.1'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.15.1 · Centro documental'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d53151-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53151-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d53151-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.15.1"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.15.1"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-15-1.sh" "$ROOT/scripts/phase-d5-3-15-1-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.15.1"
docker compose --profile tools run --rm migrate npm run migration:run 2>&1 | tee "$MIGRATION_RUN_LOG"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_SHOW_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_SHOW_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-15.1"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.15.1"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-15-1-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.15.1: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.15.1 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_16_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5316-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_16_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_16_OK_$STAMP"
MIGRATION_RUN_LOG="/tmp/dhv2-d5316-migration-run-$STAMP.log"
MIGRATION_SHOW_LOG="/tmp/dhv2-d5316-migration-show-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5316-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_RUN_LOG" "$MIGRATION_SHOW_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5316-test dhv2-web:d5316-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.16 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-15.1" ]]
[[ "$CURRENT_WEB" == "0.19.3-15.1" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-16" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-16" ]]
grep -Fq "API_PHASE = 'D5.3.16'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.16 · Planificación de verificaciones'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5316-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5316-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5316-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.16"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.16"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-16.sh" "$ROOT/scripts/phase-d5-3-16-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.16"
docker compose --profile tools run --rm migrate npm run migration:run 2>&1 | tee "$MIGRATION_RUN_LOG"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_SHOW_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_SHOW_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-16"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.16"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-16-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.16: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.16 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_17_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53171-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_17_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_17_1_OK_$STAMP"
MIGRATION_RUN_LOG="/tmp/dhv2-d53171-migration-run-$STAMP.log"
MIGRATION_SHOW_LOG="/tmp/dhv2-d53171-migration-show-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53171-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_RUN_LOG" "$MIGRATION_SHOW_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53171-test dhv2-web:d53171-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.17.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-16" ]]
[[ "$CURRENT_WEB" == "0.19.3-16" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-17.1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-17.1" ]]
grep -Fq "API_PHASE = 'D5.3.17.1'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.17.1 · Verificación de campo'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d53171-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53171-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d53171-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.17.1"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.17.1"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-17-1.sh" "$ROOT/scripts/phase-d5-3-17-1-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.17.1"
docker compose --profile tools run --rm migrate npm run migration:run 2>&1 | tee "$MIGRATION_RUN_LOG"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_SHOW_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_SHOW_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-17.1"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.17.1"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-17-1-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.17.1: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.17.1 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_17_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5317-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_17_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_17_OK_$STAMP"
MIGRATION_RUN_LOG="/tmp/dhv2-d5317-migration-run-$STAMP.log"
MIGRATION_SHOW_LOG="/tmp/dhv2-d5317-migration-show-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5317-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_RUN_LOG" "$MIGRATION_SHOW_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5317-test dhv2-web:d5317-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.17 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-16" ]]
[[ "$CURRENT_WEB" == "0.19.3-16" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-17" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-17" ]]
grep -Fq "API_PHASE = 'D5.3.17'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.17 · Verificación de campo'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5317-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5317-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5317-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.17"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.17"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-17.sh" "$ROOT/scripts/phase-d5-3-17-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.17"
docker compose --profile tools run --rm migrate npm run migration:run 2>&1 | tee "$MIGRATION_RUN_LOG"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_SHOW_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_SHOW_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-17"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.17"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-16-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.17: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.17 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_18_2_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53182-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_18_2_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_18_2_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53182-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53182-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53182-test dhv2-web:d53182-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.18.2 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-18" ]]
[[ "$CURRENT_WEB" == "0.19.3-18" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-18.2" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-18.2" ]]
grep -Fq "API_PHASE = 'D5.3.18.2'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.18.2 · Expediente operativo'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d53182-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53182-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d53182-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.18.2"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.18.2"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-18-2.sh" "$ROOT/scripts/phase-d5-3-18-2-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.18.2"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-18.2"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.18.2"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-18-2-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.18.2: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.18.2 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_18_3_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53183-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_18_3_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_18_3_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53183-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53183-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53183-test dhv2-web:d53183-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.18.3 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-18" ]]
[[ "$CURRENT_WEB" == "0.19.3-18" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-18.3" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-18.3" ]]
grep -Fq "API_PHASE = 'D5.3.18.3'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.18.3 · Expediente operativo'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d53183-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53183-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d53183-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.18.3"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.18.3"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-18-3.sh" "$ROOT/scripts/phase-d5-3-18-3-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.18.3"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-18.3"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.18.3"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-18-3-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.18.3: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.18.3 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_18_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5318-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_18_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_18_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5318-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5318-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5318-test dhv2-web:d5318-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.18 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-17.1" ]]
[[ "$CURRENT_WEB" == "0.19.3-17.1" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-18" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-18" ]]
grep -Fq "API_PHASE = 'D5.3.18'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.18 · Expediente operativo'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5318-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5318-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5318-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.18"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.18"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-18.sh" "$ROOT/scripts/phase-d5-3-18-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando que D5.3.18 no tenga migraciones pendientes"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-18"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.18"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-18-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.18: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.18 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_19_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53191-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_19_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_19_1_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53191-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53191-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53191-test dhv2-web:d53191-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.19.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-18.3" ]]
[[ "$CURRENT_WEB" == "0.19.3-18.3" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-19.1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-19.1" ]]
grep -Fq "API_PHASE = 'D5.3.19.1'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.19.1 · Inventarios y filtros operativos'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d53191-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53191-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d53191-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.19.1"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.19.1"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-19-1.sh" "$ROOT/scripts/phase-d5-3-19-1-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.19.1"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-19.1"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.19.1"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-19-1-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.19.1: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.19.1 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_2_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5321-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_2_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_2_1_OK_$STAMP"
cleanup() { rm -rf "$STAGE"; }
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.2.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/10] Preparando staging"
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5321-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5321-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5321-test "$STAGE/web-v2"
echo "[5/10] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
--exclude='.git' --exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
echo "[6/10] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x \
"$ROOT/scripts/deploy-phase-d5-3-2-1.sh" \
"$ROOT/scripts/phase-d5-3-2-1-acceptance.sh" \
"$ROOT/scripts/dev-seed-mendoza-demo.sh" \
"$ROOT/scripts/dev-clean-demo.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando migraciones"
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -q 'Pending migrations: no' <<<"$MIGRATION_OUTPUT"
echo "[9/10] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >/tmp/dhv2-d5321-health.json 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat /tmp/dhv2-d5321-health.json; echo
echo "[10/10] Aceptación y backup POST"
bash "$ROOT/scripts/phase-d5-3-2-1-acceptance.sh"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.2.1 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_2_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d532-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_2_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_2_OK_$STAMP"
cleanup() { rm -rf "$STAGE"; }
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.2 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/10] Preparando staging"
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d532-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d532-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d532-test "$STAGE/web-v2"
echo "[5/10] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
--exclude='.git' --exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
echo "[6/10] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x \
"$ROOT/scripts/deploy-phase-d5-3-2.sh" \
"$ROOT/scripts/phase-d5-3-2-acceptance.sh" \
"$ROOT/scripts/dev-seed-mendoza-demo.sh" \
"$ROOT/scripts/dev-clean-demo.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando migraciones"
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -q 'Pending migrations: no' <<<"$MIGRATION_OUTPUT"
echo "[9/10] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >/tmp/dhv2-d532-health.json 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat /tmp/dhv2-d532-health.json; echo
echo "[10/10] Aceptación y backup POST"
bash "$ROOT/scripts/phase-d5-3-2-acceptance.sh"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.2 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_20_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5320-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_20_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_20_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5320-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5320-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5320-test dhv2-web:d5320-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.20 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-19.1" ]]
[[ "$CURRENT_WEB" == "0.19.3-19.1" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-20" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-20" ]]
grep -Fq "API_PHASE = 'D5.3.20'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.20 · Informe Word automático'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5320-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5320-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5320-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.20"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.20"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-20.sh" "$ROOT/scripts/phase-d5-3-20-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.20"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-20"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.20"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-20-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.20: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.20 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_21_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53211-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_21_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_21_1_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53211-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53211-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53211-test dhv2-web:d53211-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.21.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-20" ]]
[[ "$CURRENT_WEB" == "0.19.3-20" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-21.1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-21.1" ]]
grep -Fq "API_PHASE = 'D5.3.21.1'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.21.1 · Entrega documental'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d53211-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53211-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d53211-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.21.1"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.21.1"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-21-1.sh" "$ROOT/scripts/phase-d5-3-21-1-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.21.1"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-21.1"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.21.1"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-21-1-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.21.1: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.21.1 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_22_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5322-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_22_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_22_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5322-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5322-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5322-test dhv2-web:d5322-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.22 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-21.1" ]]
[[ "$CURRENT_WEB" == "0.19.3-21.1" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-22" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-22" ]]
grep -Fq "API_PHASE = 'D5.3.22'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.22 · Revisión directiva de Informes'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5322-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5322-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5322-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.22"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.22"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-22.sh" "$ROOT/scripts/phase-d5-3-22-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.22"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-22"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.22"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-22-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.22: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.22 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_23_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53231-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_23_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_23_1_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53231-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53231-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53231-test dhv2-web:d53231-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.23.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-22" ]]
[[ "$CURRENT_WEB" == "0.19.3-22" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-23.1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-23.1" ]]
grep -Fq "API_PHASE = 'D5.3.23.1'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.23.1 · Altas de campo y revisión de Inventarios'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d53231-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53231-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d53231-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.23.1"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.23.1"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-23-1.sh" "$ROOT/scripts/phase-d5-3-23-1-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.23.1"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-23.1"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.23.1"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-23-1-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.23.1: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.23.1 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_24_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5324-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_24_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_24_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5324-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5324-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5324-test dhv2-web:d5324-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.24 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-23.1" ]]
[[ "$CURRENT_WEB" == "0.19.3-23.1" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-24" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-24" ]]
grep -Fq "API_PHASE = 'D5.3.24'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.24 · Historial de contexto operativo'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5324-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5324-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5324-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.24"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.24"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-24.sh" "$ROOT/scripts/phase-d5-3-24-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.24"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-24"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.24"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-24-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.24: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.24 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_25_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5325-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_25_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_25_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5325-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5325-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5325-test dhv2-web:d5325-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.25 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-24" ]]
[[ "$CURRENT_WEB" == "0.19.3-24" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-25" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-25" ]]
grep -Fq "API_PHASE = 'D5.3.25'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.3.25 · Historial de verificaciones'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d5325-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5325-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d5325-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.25"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.25"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-25.sh" "$ROOT/scripts/phase-d5-3-25-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.3.25"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-25"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.25"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-25-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.25: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.25 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_3_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d533-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_3_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_3_OK_$STAMP"
cleanup() { rm -rf "$STAGE"; }
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.3 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preparando staging"
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d533-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d533-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d533-test "$STAGE/web-v2"
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
--exclude='.git' --exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x \
"$ROOT/scripts/deploy-phase-d5-3-3.sh" \
"$ROOT/scripts/phase-d5-3-3-acceptance.sh" \
"$ROOT/scripts/dev-seed-mendoza-demo.sh" \
"$ROOT/scripts/dev-clean-demo.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migración si corresponde"
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
if grep -q 'Pending migrations: yes' <<<"$MIGRATION_OUTPUT"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
MIGRATION_AFTER="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_AFTER"
grep -q 'Pending migrations: no' <<<"$MIGRATION_AFTER"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >/tmp/dhv2-d533-health.json 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat /tmp/dhv2-d533-health.json; echo
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-3-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.3 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_4_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d534-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_4_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_4_OK_$STAMP"
cleanup() { rm -rf "$STAGE"; }
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.4 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preparando staging"
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d534-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d534-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d534-test "$STAGE/web-v2"
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
--exclude='.git' --exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x \
"$ROOT/scripts/deploy-phase-d5-3-4.sh" \
"$ROOT/scripts/phase-d5-3-4-acceptance.sh" \
"$ROOT/scripts/dev-seed-mendoza-demo.sh" \
"$ROOT/scripts/dev-clean-demo.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migraciones pendientes si corresponde"
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
if grep -q 'Pending migrations: yes' <<<"$MIGRATION_OUTPUT"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
MIGRATION_AFTER="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_AFTER"
grep -q 'Pending migrations: no' <<<"$MIGRATION_AFTER"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >/tmp/dhv2-d534-health.json 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat /tmp/dhv2-d534-health.json; echo
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-4-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.4 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_5_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5351-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_5_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_5_1_OK_$STAMP"
cleanup() { rm -rf "$STAGE"; }
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.5.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preparando staging"
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-5.1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-5.1" ]]
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d5351-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5351-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d5351-test "$STAGE/web-v2"
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
--exclude='.git' --exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x \
"$ROOT/scripts/deploy-phase-d5-3-5-1.sh" \
"$ROOT/scripts/phase-d5-3-5-1-acceptance.sh" \
"$ROOT/scripts/dev-seed-mendoza-demo.sh" \
"$ROOT/scripts/dev-clean-demo.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migraciones pendientes"
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
if grep -q 'Pending migrations: yes' <<<"$MIGRATION_OUTPUT"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
MIGRATION_AFTER="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_AFTER"
grep -q 'Pending migrations: no' <<<"$MIGRATION_AFTER"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >/tmp/dhv2-d5351-health.json 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat /tmp/dhv2-d5351-health.json; echo
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-5-1-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.5.1 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_5_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d535-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_5_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_5_OK_$STAMP"
cleanup() { rm -rf "$STAGE"; }
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.5 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preparando staging"
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-5" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-5" ]]
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d535-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d535-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d535-test "$STAGE/web-v2"
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
--exclude='.git' --exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x \
"$ROOT/scripts/deploy-phase-d5-3-5.sh" \
"$ROOT/scripts/phase-d5-3-5-acceptance.sh" \
"$ROOT/scripts/dev-seed-mendoza-demo.sh" \
"$ROOT/scripts/dev-clean-demo.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migraciones pendientes"
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
if grep -q 'Pending migrations: yes' <<<"$MIGRATION_OUTPUT"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
MIGRATION_AFTER="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_AFTER"
grep -q 'Pending migrations: no' <<<"$MIGRATION_AFTER"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >/tmp/dhv2-d535-health.json 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat /tmp/dhv2-d535-health.json; echo
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-5-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.5 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_6_3_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5363-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_6_3_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_6_3_OK_$STAMP"
cleanup(){ rm -rf "$STAGE"; }
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.6.3 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preparando staging"
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-6.3" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-6.3" ]]
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d5363-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm -v "$STAGE/api-v3/test:/app/test:ro" -v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" dhv2-api:d5363-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d5363-test "$STAGE/web-v2"
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar --exclude='api-v3/node_modules' --exclude='api-v3/dist' --exclude='web-v2/node_modules' --exclude='web-v2/dist' --exclude='.git' --exclude='DH_V2_FASE_*.zip' -czf "$PRE_BACKUP/source.tar.gz" api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-6-3.sh" "$ROOT/scripts/phase-d5-3-6-3-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Verificando migraciones"
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
if grep -Fq 'Pending migrations: yes' <<<"$MIGRATION_OUTPUT"; then docker compose --profile tools run --rm migrate npm run migration:run; fi
MIGRATION_AFTER="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_AFTER"
grep -Fq 'Pending migrations: no' <<<"$MIGRATION_AFTER"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >/tmp/dhv2-d5363-health.json 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat /tmp/dhv2-d5363-health.json; echo
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-6-3-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.6.3 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_6_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d536-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_6_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_6_OK_$STAMP"
cleanup(){ rm -rf "$STAGE"; }
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.6 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preparando staging"
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-6" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-6" ]]
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d536-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm -v "$STAGE/api-v3/test:/app/test:ro" -v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" dhv2-api:d536-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d536-test "$STAGE/web-v2"
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar --exclude='api-v3/node_modules' --exclude='api-v3/dist' --exclude='web-v2/node_modules' --exclude='web-v2/dist' --exclude='.git' --exclude='DH_V2_FASE_*.zip' -czf "$PRE_BACKUP/source.tar.gz" api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-6.sh" "$ROOT/scripts/phase-d5-3-6-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Verificando migraciones"
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
if grep -Fq 'Pending migrations: yes' <<<"$MIGRATION_OUTPUT"; then docker compose --profile tools run --rm migrate npm run migration:run; fi
MIGRATION_AFTER="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_AFTER"
grep -Fq 'Pending migrations: no' <<<"$MIGRATION_AFTER"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >/tmp/dhv2-d536-health.json 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat /tmp/dhv2-d536-health.json; echo
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-6-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.6 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_7_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5371-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_7_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_7_1_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5371-migration-$STAMP.log"
MIGRATION_AFTER_LOG="/tmp/dhv2-d5371-migration-after-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5371-health-$STAMP.json"
MIN_FREE_KB=$((12 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$MIGRATION_AFTER_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5371-test dhv2-web:d5371-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.7.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente para un deploy con builds Docker."
echo "Libre: $(( FREE_KB / 1024 )) MB · requerido: $(( MIN_FREE_KB / 1024 )) MB"
echo "Revisá logs y caché. Una opción segura para caché de builds es: docker builder prune -af"
exit 1
fi
DB_HEALTH="$(docker inspect dhv2-db --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' 2>/dev/null || true)"
if [[ "$DB_HEALTH" != "healthy" ]]; then
echo "ERROR: PostgreSQL no está healthy (estado: $DB_HEALTH). No se inicia el deploy."
docker compose ps -a || true
docker logs --tail 80 dhv2-db || true
exit 1
fi
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-7.1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-7.1" ]]
grep -Fq "API_PHASE = 'D5.3.7.1'" "$STAGE/api-v3/src/version.ts"
echo "Espacio libre antes de builds: $(( FREE_KB / 1024 / 1024 )) GB"
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d5371-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5371-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d5371-test "$STAGE/web-v2"
FREE_AFTER_BUILD_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_AFTER_BUILD_KB" =~ ^[0-9]+$ ]] || (( FREE_AFTER_BUILD_KB < 4 * 1024 * 1024 )); then
echo "ERROR: los builds dejaron menos de 4 GB libres. Producción no será modificada."
df -h "$ROOT"
exit 1
fi
if [[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" != "healthy" ]]; then
echo "ERROR: PostgreSQL dejó de estar healthy durante staging. Producción no será modificada."
docker logs --tail 80 dhv2-db || true
exit 1
fi
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' \
--exclude='api-v3/dist' \
--exclude='web-v2/node_modules' \
--exclude='web-v2/dist' \
--exclude='.git' \
--exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then
cp -a .env "$PRE_BACKUP/.env"
chmod 600 "$PRE_BACKUP/.env"
fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-7-1.sh" "$ROOT/scripts/phase-d5-3-7-1-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migraciones pendientes"
# Se muestra la salida real; con pipefail un fallo del contenedor no queda oculto por tee.
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
if grep -Fq 'Pending migrations: yes' "$MIGRATION_LOG"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_AFTER_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_AFTER_LOG"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-7.1"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.7.1"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-7-1-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then
cp -a .env "$POST_BACKUP/.env"
chmod 600 "$POST_BACKUP/.env"
fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.7.1 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_7_2_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53721-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_7_2_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_7_2_1_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53721-migration-$STAMP.log"
MIGRATION_AFTER_LOG="/tmp/dhv2-d53721-migration-after-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53721-health-$STAMP.json"
MIN_FREE_KB=$((12 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$MIGRATION_AFTER_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53721-test dhv2-web:d53721-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.7.2.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente para un deploy con builds Docker."
echo "Libre: $(( FREE_KB / 1024 )) MB · requerido: $(( MIN_FREE_KB / 1024 )) MB"
echo "Revisá logs y caché. Una opción segura para caché de builds es: docker builder prune -af"
exit 1
fi
DB_HEALTH="$(docker inspect dhv2-db --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' 2>/dev/null || true)"
if [[ "$DB_HEALTH" != "healthy" ]]; then
echo "ERROR: PostgreSQL no está healthy (estado: $DB_HEALTH). No se inicia el deploy."
docker compose ps -a || true
docker logs --tail 80 dhv2-db || true
exit 1
fi
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-7.2.1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-7.2.1" ]]
grep -Fq "API_PHASE = 'D5.3.7.2.1'" "$STAGE/api-v3/src/version.ts"
echo "Espacio libre antes de builds: $(( FREE_KB / 1024 / 1024 )) GB"
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d53721-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53721-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d53721-test "$STAGE/web-v2"
FREE_AFTER_BUILD_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_AFTER_BUILD_KB" =~ ^[0-9]+$ ]] || (( FREE_AFTER_BUILD_KB < 4 * 1024 * 1024 )); then
echo "ERROR: los builds dejaron menos de 4 GB libres. Producción no será modificada."
df -h "$ROOT"
exit 1
fi
if [[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" != "healthy" ]]; then
echo "ERROR: PostgreSQL dejó de estar healthy durante staging. Producción no será modificada."
docker logs --tail 80 dhv2-db || true
exit 1
fi
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' \
--exclude='api-v3/dist' \
--exclude='web-v2/node_modules' \
--exclude='web-v2/dist' \
--exclude='.git' \
--exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then
cp -a .env "$PRE_BACKUP/.env"
chmod 600 "$PRE_BACKUP/.env"
fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-7-2-1.sh" "$ROOT/scripts/phase-d5-3-7-2-1-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migraciones pendientes"
# Se muestra la salida real; con pipefail un fallo del contenedor no queda oculto por tee.
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
if grep -Fq 'Pending migrations: yes' "$MIGRATION_LOG"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_AFTER_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_AFTER_LOG"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-7.2.1"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.7.2.1"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-7-2-1-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then
cp -a .env "$POST_BACKUP/.env"
chmod 600 "$POST_BACKUP/.env"
fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.7.2.1 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_7_2_2_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53722-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_7_2_2_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_7_2_2_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53722-migration-$STAMP.log"
MIGRATION_AFTER_LOG="/tmp/dhv2-d53722-migration-after-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53722-health-$STAMP.json"
MIN_FREE_KB=$((12 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$MIGRATION_AFTER_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53722-test dhv2-web:d53722-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.7.2.2 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente para un deploy con builds Docker."
echo "Libre: $(( FREE_KB / 1024 )) MB · requerido: $(( MIN_FREE_KB / 1024 )) MB"
echo "Revisá logs y caché. Una opción segura para caché de builds es: docker builder prune -af"
exit 1
fi
DB_HEALTH="$(docker inspect dhv2-db --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' 2>/dev/null || true)"
if [[ "$DB_HEALTH" != "healthy" ]]; then
echo "ERROR: PostgreSQL no está healthy (estado: $DB_HEALTH). No se inicia el deploy."
docker compose ps -a || true
docker logs --tail 80 dhv2-db || true
exit 1
fi
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-7.2.2" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-7.2.2" ]]
grep -Fq "API_PHASE = 'D5.3.7.2.2'" "$STAGE/api-v3/src/version.ts"
echo "Espacio libre antes de builds: $(( FREE_KB / 1024 / 1024 )) GB"
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d53722-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53722-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d53722-test "$STAGE/web-v2"
FREE_AFTER_BUILD_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_AFTER_BUILD_KB" =~ ^[0-9]+$ ]] || (( FREE_AFTER_BUILD_KB < 4 * 1024 * 1024 )); then
echo "ERROR: los builds dejaron menos de 4 GB libres. Producción no será modificada."
df -h "$ROOT"
exit 1
fi
if [[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" != "healthy" ]]; then
echo "ERROR: PostgreSQL dejó de estar healthy durante staging. Producción no será modificada."
docker logs --tail 80 dhv2-db || true
exit 1
fi
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' \
--exclude='api-v3/dist' \
--exclude='web-v2/node_modules' \
--exclude='web-v2/dist' \
--exclude='.git' \
--exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then
cp -a .env "$PRE_BACKUP/.env"
chmod 600 "$PRE_BACKUP/.env"
fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-7-2-2.sh" "$ROOT/scripts/phase-d5-3-7-2-2-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migraciones pendientes"
# Se muestra la salida real; con pipefail un fallo del contenedor no queda oculto por tee.
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
if grep -Fq 'Pending migrations: yes' "$MIGRATION_LOG"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_AFTER_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_AFTER_LOG"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-7.2.2"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.7.2.2"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-7-2-2-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then
cp -a .env "$POST_BACKUP/.env"
chmod 600 "$POST_BACKUP/.env"
fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.7.2.2 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_7_2_3_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53723-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_7_2_3_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_7_2_3_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53723-migration-$STAMP.log"
MIGRATION_AFTER_LOG="/tmp/dhv2-d53723-migration-after-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53723-health-$STAMP.json"
MIN_FREE_KB=$((12 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$MIGRATION_AFTER_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53723-test dhv2-web:d53723-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.7.2.3 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente para un deploy con builds Docker."
echo "Libre: $(( FREE_KB / 1024 )) MB · requerido: $(( MIN_FREE_KB / 1024 )) MB"
echo "Revisá logs y caché. Una opción segura para caché de builds es: docker builder prune -af"
exit 1
fi
DB_HEALTH="$(docker inspect dhv2-db --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' 2>/dev/null || true)"
if [[ "$DB_HEALTH" != "healthy" ]]; then
echo "ERROR: PostgreSQL no está healthy (estado: $DB_HEALTH). No se inicia el deploy."
docker compose ps -a || true
docker logs --tail 80 dhv2-db || true
exit 1
fi
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-7.2.3" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-7.2.3" ]]
grep -Fq "API_PHASE = 'D5.3.7.2.3'" "$STAGE/api-v3/src/version.ts"
echo "Espacio libre antes de builds: $(( FREE_KB / 1024 / 1024 )) GB"
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d53723-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53723-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d53723-test "$STAGE/web-v2"
FREE_AFTER_BUILD_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_AFTER_BUILD_KB" =~ ^[0-9]+$ ]] || (( FREE_AFTER_BUILD_KB < 4 * 1024 * 1024 )); then
echo "ERROR: los builds dejaron menos de 4 GB libres. Producción no será modificada."
df -h "$ROOT"
exit 1
fi
if [[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" != "healthy" ]]; then
echo "ERROR: PostgreSQL dejó de estar healthy durante staging. Producción no será modificada."
docker logs --tail 80 dhv2-db || true
exit 1
fi
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' \
--exclude='api-v3/dist' \
--exclude='web-v2/node_modules' \
--exclude='web-v2/dist' \
--exclude='.git' \
--exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then
cp -a .env "$PRE_BACKUP/.env"
chmod 600 "$PRE_BACKUP/.env"
fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-7-2-3.sh" "$ROOT/scripts/phase-d5-3-7-2-3-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migraciones pendientes"
# Se muestra la salida real; con pipefail un fallo del contenedor no queda oculto por tee.
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
if grep -Fq 'Pending migrations: yes' "$MIGRATION_LOG"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_AFTER_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_AFTER_LOG"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-7.2.3"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.7.2.3"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-7-2-3-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then
cp -a .env "$POST_BACKUP/.env"
chmod 600 "$POST_BACKUP/.env"
fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.7.2.3 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_7_2_4_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53724-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_7_2_4_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_7_2_4_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53724-migration-$STAMP.log"
MIGRATION_AFTER_LOG="/tmp/dhv2-d53724-migration-after-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53724-health-$STAMP.json"
MIN_FREE_KB=$((12 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$MIGRATION_AFTER_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53724-test dhv2-web:d53724-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.7.2.4 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente para un deploy con builds Docker."
echo "Libre: $(( FREE_KB / 1024 )) MB · requerido: $(( MIN_FREE_KB / 1024 )) MB"
echo "Revisá logs y caché. Una opción segura para caché de builds es: docker builder prune -af"
exit 1
fi
DB_HEALTH="$(docker inspect dhv2-db --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' 2>/dev/null || true)"
if [[ "$DB_HEALTH" != "healthy" ]]; then
echo "ERROR: PostgreSQL no está healthy (estado: $DB_HEALTH). No se inicia el deploy."
docker compose ps -a || true
docker logs --tail 80 dhv2-db || true
exit 1
fi
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-7.2.4" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-7.2.4" ]]
grep -Fq "API_PHASE = 'D5.3.7.2.4'" "$STAGE/api-v3/src/version.ts"
echo "Espacio libre antes de builds: $(( FREE_KB / 1024 / 1024 )) GB"
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d53724-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53724-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d53724-test "$STAGE/web-v2"
FREE_AFTER_BUILD_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_AFTER_BUILD_KB" =~ ^[0-9]+$ ]] || (( FREE_AFTER_BUILD_KB < 4 * 1024 * 1024 )); then
echo "ERROR: los builds dejaron menos de 4 GB libres. Producción no será modificada."
df -h "$ROOT"
exit 1
fi
if [[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" != "healthy" ]]; then
echo "ERROR: PostgreSQL dejó de estar healthy durante staging. Producción no será modificada."
docker logs --tail 80 dhv2-db || true
exit 1
fi
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' \
--exclude='api-v3/dist' \
--exclude='web-v2/node_modules' \
--exclude='web-v2/dist' \
--exclude='.git' \
--exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then
cp -a .env "$PRE_BACKUP/.env"
chmod 600 "$PRE_BACKUP/.env"
fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-7-2-4.sh" "$ROOT/scripts/phase-d5-3-7-2-4-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migraciones pendientes"
# Se muestra la salida real; con pipefail un fallo del contenedor no queda oculto por tee.
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
if grep -Fq 'Pending migrations: yes' "$MIGRATION_LOG"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_AFTER_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_AFTER_LOG"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-7.2.4"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.7.2.4"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-7-2-4-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then
cp -a .env "$POST_BACKUP/.env"
chmod 600 "$POST_BACKUP/.env"
fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.7.2.4 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_7_2_5_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d53725-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_7_2_5_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_7_2_5_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d53725-migration-$STAMP.log"
MIGRATION_AFTER_LOG="/tmp/dhv2-d53725-migration-after-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d53725-health-$STAMP.json"
MIN_FREE_KB=$((12 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$MIGRATION_AFTER_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d53725-test dhv2-web:d53725-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.7.2.5 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente para un deploy con builds Docker."
echo "Libre: $(( FREE_KB / 1024 )) MB · requerido: $(( MIN_FREE_KB / 1024 )) MB"
echo "Revisá logs y caché. Una opción segura para caché de builds es: docker builder prune -af"
exit 1
fi
DB_HEALTH="$(docker inspect dhv2-db --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' 2>/dev/null || true)"
if [[ "$DB_HEALTH" != "healthy" ]]; then
echo "ERROR: PostgreSQL no está healthy (estado: $DB_HEALTH). No se inicia el deploy."
docker compose ps -a || true
docker logs --tail 80 dhv2-db || true
exit 1
fi
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-7.2.5" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-7.2.5" ]]
grep -Fq "API_PHASE = 'D5.3.7.2.5'" "$STAGE/api-v3/src/version.ts"
echo "Espacio libre antes de builds: $(( FREE_KB / 1024 / 1024 )) GB"
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d53725-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d53725-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d53725-test "$STAGE/web-v2"
FREE_AFTER_BUILD_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_AFTER_BUILD_KB" =~ ^[0-9]+$ ]] || (( FREE_AFTER_BUILD_KB < 4 * 1024 * 1024 )); then
echo "ERROR: los builds dejaron menos de 4 GB libres. Producción no será modificada."
df -h "$ROOT"
exit 1
fi
if [[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" != "healthy" ]]; then
echo "ERROR: PostgreSQL dejó de estar healthy durante staging. Producción no será modificada."
docker logs --tail 80 dhv2-db || true
exit 1
fi
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' \
--exclude='api-v3/dist' \
--exclude='web-v2/node_modules' \
--exclude='web-v2/dist' \
--exclude='.git' \
--exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then
cp -a .env "$PRE_BACKUP/.env"
chmod 600 "$PRE_BACKUP/.env"
fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-7-2-5.sh" "$ROOT/scripts/phase-d5-3-7-2-5-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migraciones pendientes"
# Se muestra la salida real; con pipefail un fallo del contenedor no queda oculto por tee.
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
if grep -Fq 'Pending migrations: yes' "$MIGRATION_LOG"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_AFTER_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_AFTER_LOG"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-7.2.5"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.7.2.5"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-7-2-5-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then
cp -a .env "$POST_BACKUP/.env"
chmod 600 "$POST_BACKUP/.env"
fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.7.2.5 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_7_2_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d5372-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_7_2_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_7_2_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d5372-migration-$STAMP.log"
MIGRATION_AFTER_LOG="/tmp/dhv2-d5372-migration-after-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d5372-health-$STAMP.json"
MIN_FREE_KB=$((12 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$MIGRATION_AFTER_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d5372-test dhv2-web:d5372-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.7.2 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente para un deploy con builds Docker."
echo "Libre: $(( FREE_KB / 1024 )) MB · requerido: $(( MIN_FREE_KB / 1024 )) MB"
echo "Revisá logs y caché. Una opción segura para caché de builds es: docker builder prune -af"
exit 1
fi
DB_HEALTH="$(docker inspect dhv2-db --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' 2>/dev/null || true)"
if [[ "$DB_HEALTH" != "healthy" ]]; then
echo "ERROR: PostgreSQL no está healthy (estado: $DB_HEALTH). No se inicia el deploy."
docker compose ps -a || true
docker logs --tail 80 dhv2-db || true
exit 1
fi
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-7.2" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-7.2" ]]
grep -Fq "API_PHASE = 'D5.3.7.2'" "$STAGE/api-v3/src/version.ts"
echo "Espacio libre antes de builds: $(( FREE_KB / 1024 / 1024 )) GB"
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d5372-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d5372-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d5372-test "$STAGE/web-v2"
FREE_AFTER_BUILD_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_AFTER_BUILD_KB" =~ ^[0-9]+$ ]] || (( FREE_AFTER_BUILD_KB < 4 * 1024 * 1024 )); then
echo "ERROR: los builds dejaron menos de 4 GB libres. Producción no será modificada."
df -h "$ROOT"
exit 1
fi
if [[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" != "healthy" ]]; then
echo "ERROR: PostgreSQL dejó de estar healthy durante staging. Producción no será modificada."
docker logs --tail 80 dhv2-db || true
exit 1
fi
echo "[5/11] Backup PRE"
mkdir -p "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar \
--exclude='api-v3/node_modules' \
--exclude='api-v3/dist' \
--exclude='web-v2/node_modules' \
--exclude='web-v2/dist' \
--exclude='.git' \
--exclude='DH_V2_FASE_*.zip' \
-czf "$PRE_BACKUP/source.tar.gz" \
api-v3 web-v2 docs scripts infra docker-compose.yml README.md .env.example
if [[ -f .env ]]; then
cp -a .env "$PRE_BACKUP/.env"
chmod 600 "$PRE_BACKUP/.env"
fi
echo "[6/11] Instalando source"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-7-2.sh" "$ROOT/scripts/phase-d5-3-7-2-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migraciones pendientes"
# Se muestra la salida real; con pipefail un fallo del contenedor no queda oculto por tee.
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
if grep -Fq 'Pending migrations: yes' "$MIGRATION_LOG"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_AFTER_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_AFTER_LOG"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-7.2"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.7.2"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
echo "[10/11] Aceptación automática"
bash "$ROOT/scripts/phase-d5-3-7-2-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then
cp -a .env "$POST_BACKUP/.env"
chmod 600 "$POST_BACKUP/.env"
fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
echo
echo "D5.3.7.2 desplegada correctamente"
echo "Backup PRE : $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_8_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d538-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
STRONG_BACKUP="$BACKUP_ROOT/INVENTARIO_A_REVISAR_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_8_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d538-migration-$STAMP.log"
MIGRATION_AFTER_LOG="/tmp/dhv2-d538-migration-after-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d538-health-$STAMP.json"
MIN_FREE_KB=$((18 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$MIGRATION_AFTER_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d538-test dhv2-web:d538-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.8 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/11] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente para staging + backup fuerte + build final."
echo "Libre: $(( FREE_KB / 1024 )) MB · requerido: $(( MIN_FREE_KB / 1024 )) MB"
echo "No se modificó producción. Revisá logs/caché antes de continuar."
exit 1
fi
DB_HEALTH="$(docker inspect dhv2-db --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' 2>/dev/null || true)"
if [[ "$DB_HEALTH" != "healthy" ]]; then
echo "ERROR: PostgreSQL no está healthy (estado: $DB_HEALTH). No se inicia el deploy."
docker compose ps -a || true
docker logs --tail 80 dhv2-db || true
exit 1
fi
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-8" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-8" ]]
grep -Fq "API_PHASE = 'D5.3.8'" "$STAGE/api-v3/src/version.ts"
grep -Fq 'INVENTARIO A REVISAR' "$STAGE/scripts/backup-inventario-a-revisar.sh"
echo "Espacio libre antes de builds: $(( FREE_KB / 1024 / 1024 )) GB"
echo "[2/11] Compilando API en staging"
docker build --target builder -t dhv2-api:d538-test "$STAGE/api-v3"
echo "[3/11] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d538-test npm test
echo "[4/11] Compilando web en staging"
docker build -t dhv2-web:d538-test "$STAGE/web-v2"
FREE_AFTER_BUILD_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_AFTER_BUILD_KB" =~ ^[0-9]+$ ]] || (( FREE_AFTER_BUILD_KB < 12 * 1024 * 1024 )); then
echo "ERROR: staging dejó menos de 12 GB libres para el backup fuerte. Producción no será modificada."
df -h "$ROOT"
exit 1
fi
if [[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" != "healthy" ]]; then
echo "ERROR: PostgreSQL dejó de estar healthy durante staging. Producción no será modificada."
docker logs --tail 80 dhv2-db || true
exit 1
fi
echo "[5/11] BACKUP FUERTE · INVENTARIO A REVISAR"
BACKUP_DIR="$STRONG_BACKUP" ROOT="$ROOT" bash "$STAGE/scripts/backup-inventario-a-revisar.sh"
test -s "$STRONG_BACKUP/database.dump"
test -s "$STRONG_BACKUP/source.tar.gz"
test -s "$STRONG_BACKUP/asset-media.tar.gz"
test -s "$STRONG_BACKUP/docker-images.tar.gz"
test -s "$STRONG_BACKUP/BACKUP_OK.txt"
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "[6/11] Instalando source D5.3.8"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x \
"$ROOT/scripts/deploy-phase-d5-3-8.sh" \
"$ROOT/scripts/phase-d5-3-8-acceptance.sh" \
"$ROOT/scripts/backup-inventario-a-revisar.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/11] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/11] Aplicando migración D5.3.8"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
if grep -Fq 'Pending migrations: yes' "$MIGRATION_LOG"; then
docker compose --profile tools run --rm migrate npm run migration:run
fi
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_AFTER_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_AFTER_LOG"
echo "[9/11] Recreando API y web"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then
break
fi
if [[ "$i" == "40" ]]; then
docker compose logs --tail=200 api
exit 1
fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-8"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.8"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
echo "[10/11] Aceptación automática"
STRONG_BACKUP="$STRONG_BACKUP" bash "$ROOT/scripts/phase-d5-3-8-acceptance.sh"
echo "[11/11] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then
cp -a .env "$POST_BACKUP/.env"
chmod 600 "$POST_BACKUP/.env"
fi
sha256sum "$POST_BACKUP/$(basename "$ZIP")" > "$POST_BACKUP/SHA256SUMS.txt"
cat > "$POST_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt" <<TXT
El punto de retorno previo al cambio conceptual D5.3.8 está en:
$STRONG_BACKUP
Palabra clave: INVENTARIO A REVISAR
TXT
chmod 600 "$POST_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
echo
echo "D5.3.8 desplegada correctamente"
echo "BACKUP FUERTE INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_3_9_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d539-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_3_9_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_3_9_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d539-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d539-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d539-test dhv2-web:d539-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.3.9 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.3-9" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.3-9" ]]
grep -Fq "API_PHASE = 'D5.3.9'" "$STAGE/api-v3/src/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d539-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d539-test npm test
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d539-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.3.9"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.3.9"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-3-9.sh" "$ROOT/scripts/phase-d5-3-9-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando que D5.3.9 no tenga migraciones pendientes"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.3-9"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.3.9"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-3-9-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.3.9: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.3.9 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_4_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d541-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_4_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_4_1_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d541-migration-$STAMP.log"
TEST_LOG="/tmp/dhv2-d541-tests-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d541-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$TEST_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d541-test dhv2-web:d541-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.4.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.3-25" ]]
[[ "$CURRENT_WEB" == "0.19.3-25" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.4-1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.4-1" ]]
grep -Fq "API_PHASE = 'D5.4.1'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.4.1 · Hallazgos aplicables al Inventario'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d541-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d541-test npm test 2>&1 | tee "$TEST_LOG"
grep -Eq 'tests[[:space:]]+246' "$TEST_LOG"
grep -Eq 'pass[[:space:]]+246' "$TEST_LOG"
grep -Eq 'fail[[:space:]]+0' "$TEST_LOG"
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d541-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.4.1"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.4.1"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-4-1.sh" "$ROOT/scripts/phase-d5-4-1-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.4"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.4-1"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.4.1"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-4-1-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.4.1: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.4.1 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_5_1_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d551-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_5_1_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_5_1_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d551-migration-$STAMP.log"
TEST_LOG="/tmp/dhv2-d551-tests-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d551-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$TEST_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d551-test dhv2-web:d551-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.5.1 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.4-1" ]]
[[ "$CURRENT_WEB" == "0.19.4-1" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.5-1" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.5-1" ]]
grep -Fq "API_PHASE = 'D5.5.1'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.5.1 · Corrección de staging de planificación'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d551-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d551-test npm test 2>&1 | tee "$TEST_LOG"
grep -Eq 'tests[[:space:]]+252' "$TEST_LOG"
grep -Eq 'pass[[:space:]]+252' "$TEST_LOG"
grep -Eq 'fail[[:space:]]+0' "$TEST_LOG"
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d551-test "$STAGE/web-v2"
grep -Fq 'Área → Operadora → fecha' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
grep -Fq 'CHECKLIST AUTOMÁTICO' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
grep -Fq 'Agregar registro sin pendiente previo' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
grep -Fq 'Motivo de exclusión' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
grep -Fq 'debe iniciar la visita desde la APK' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
if grep -Fq 'startInspectionVisit' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"; then exit 1; fi
grep -Fq 'generateInspectionVisitChecklist' "$STAGE/web-v2/src/lib/api.ts"
grep -Fq 'excludeInspectionVisitAsset' "$STAGE/web-v2/src/lib/api.ts"
echo "[5/10] Backup PRE D5.5.1"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore README.md
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.5.1"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-5-1.sh" "$ROOT/scripts/phase-d5-5-1-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.5"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.5-1"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.5.1"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-5-1-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.5.1: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.5.1 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_5_2_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d552-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_5_2_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_5_2_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d552-migration-$STAMP.log"
TEST_LOG="/tmp/dhv2-d552-tests-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d552-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$TEST_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d552-test dhv2-web:d552-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.5.2 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.5-1" ]]
[[ "$CURRENT_WEB" == "0.19.5-1" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.5-2" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.5-2" ]]
grep -Fq "API_PHASE = 'D5.5.2'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.5.2 · Planificación web y checklist'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d552-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d552-test npm test 2>&1 | tee "$TEST_LOG"
grep -Eq 'tests[[:space:]]+252' "$TEST_LOG"
grep -Eq 'pass[[:space:]]+252' "$TEST_LOG"
grep -Eq 'fail[[:space:]]+0' "$TEST_LOG"
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d552-test "$STAGE/web-v2"
grep -Fq 'Área → Operadora → fecha' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
grep -Fq 'CHECKLIST AUTOMÁTICO' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
grep -Fq 'Agregar registro sin pendiente previo' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
grep -Fq 'Motivo de exclusión' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
grep -Fq 'debe iniciar la visita desde la APK' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
if grep -Fq 'startInspectionVisit' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"; then exit 1; fi
grep -Fq 'generateInspectionVisitChecklist' "$STAGE/web-v2/src/lib/api.ts"
grep -Fq 'excludeInspectionVisitAsset' "$STAGE/web-v2/src/lib/api.ts"
echo "[5/10] Backup PRE D5.5.2"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore README.md
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.5.2"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-5-2.sh" "$ROOT/scripts/phase-d5-5-2-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando migración D5.5 ya aplicada"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.5-2"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.5.2"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-5-2-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.5.2: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.5.2 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_5_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d55-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_5_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_5_OK_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d55-migration-$STAMP.log"
TEST_LOG="/tmp/dhv2-d55-tests-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d55-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$TEST_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d55-test dhv2-web:d55-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.5 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.4-1" ]]
[[ "$CURRENT_WEB" == "0.19.4-1" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.5" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.5" ]]
grep -Fq "API_PHASE = 'D5.5'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.5 · Planificación web y checklist'" "$STAGE/web-v2/src/config/version.ts"
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d55-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d55-test npm test 2>&1 | tee "$TEST_LOG"
grep -Eq 'tests[[:space:]]+252' "$TEST_LOG"
grep -Eq 'pass[[:space:]]+252' "$TEST_LOG"
grep -Eq 'fail[[:space:]]+0' "$TEST_LOG"
echo "[4/10] Compilando web en staging"
docker build -t dhv2-web:d55-test "$STAGE/web-v2"
echo "[5/10] Backup PRE D5.5"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore README.md
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.5"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-5.sh" "$ROOT/scripts/phase-d5-5-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Aplicando migración D5.5"
docker compose --profile tools run --rm migrate npm run migration:run
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.5"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.5"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-5-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP"
chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.5: %s\nINVENTARIO A REVISAR: %s\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump "$(basename "$ZIP")" BACKUP_REFERENCES.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
)
echo
echo "D5.5 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup fuerte INVENTARIO A REVISAR: $STRONG_BACKUP"
echo "Backup POST: $POST_BACKUP"
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_6_2_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d562-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_6_2_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_6_2_OK_$STAMP"
CLOSURE_BACKUP="$BACKUP_ROOT/FASE_D_CERRADA_D5_6_2_$STAMP"
TEST_LOG="/tmp/dhv2-d562-tests-$STAMP.log"
MIGRATION_LOG="/tmp/dhv2-d562-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d562-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$TEST_LOG" "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d562-test dhv2-web:d562-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.6.2 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
case "$CURRENT_API/$CURRENT_WEB" in
0.19.5-2/0.19.5-2|0.19.6/0.19.6|0.19.6-1/0.19.6-1) ;;
*) echo "ERROR: base no soportada. API=$CURRENT_API WEB=$CURRENT_WEB"; exit 1 ;;
esac
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.6-2" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.6-2" ]]
grep -Fq "API_PHASE = 'D5.6.2'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.6.2 · Accesos y compatibilidad Android'" "$STAGE/web-v2/src/config/version.ts"
if find "$STAGE/api-v3/src/database/migrations" -maxdepth 1 -type f -iname '*d5-6-2*' | grep -q .; then
echo "ERROR: D5.6.2 no debe agregar migración"
exit 1
fi
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d562-test "$STAGE/api-v3"
echo "[3/10] Ejecutando tests API en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d562-test npm test 2>&1 | tee "$TEST_LOG"
grep -Eq 'tests[[:space:]]+262' "$TEST_LOG"
grep -Eq 'pass[[:space:]]+262' "$TEST_LOG"
grep -Eq 'fail[[:space:]]+0' "$TEST_LOG"
echo "[4/10] Compilando web y validando administración"
docker build -t dhv2-web:d562-test "$STAGE/web-v2"
grep -Fq 'Restablecer contraseña' "$STAGE/web-v2/src/pages/UserDetailPage.tsx"
grep -Fq 'resetUserPassword' "$STAGE/web-v2/src/lib/api.ts"
grep -Fq 'Contraseña de usuario restablecida' "$STAGE/web-v2/src/lib/format.ts"
echo "[5/10] Backup PRE D5.6.2"
mkdir -p "$PRE_BACKUP"; chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore README.md
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
printf 'Base previa API=%s WEB=%s\n' "$CURRENT_API" "$CURRENT_WEB" > "$PRE_BACKUP/BASE_VERSION.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz BASE_VERSION.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.6.2"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-6-2.sh" "$ROOT/scripts/phase-d5-6-2-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando migraciones"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.6-2"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.6.2"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-6-2-acceptance.sh"
echo "[10/10] Backup POST"
mkdir -p "$POST_BACKUP" "$CLOSURE_BACKUP"; chmod 700 "$POST_BACKUP" "$CLOSURE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
tar -czf "$POST_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore README.md
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
printf 'D5.6.2 OK\nBase previa API=%s WEB=%s\nVersión final 0.19.6-2\nFASE D CERRADA + hotfix de acceso Android\n' "$CURRENT_API" "$CURRENT_WEB" > "$POST_BACKUP/BACKUP_OK.txt"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
(
cd "$POST_BACKUP"
sha256sum database.dump source.tar.gz "$(basename "$ZIP")" BACKUP_OK.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
ln "$POST_BACKUP/database.dump" "$CLOSURE_BACKUP/database.dump"
ln "$POST_BACKUP/source.tar.gz" "$CLOSURE_BACKUP/source.tar.gz"
ln "$POST_BACKUP/$(basename "$ZIP")" "$CLOSURE_BACKUP/$(basename "$ZIP")"
printf 'FASE D CERRADA\nHotfix D5.6.2 aplicado\nBackup canónico: %s\nVersión: 0.19.6-2\n' "$POST_BACKUP" > "$CLOSURE_BACKUP/BACKUP_OK.txt"
echo
echo "ACEPTACIÓN DE FASE D5.6.2: OK"
echo "D5.6.2 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
echo "Backup fuerte FASE D CERRADA: $CLOSURE_BACKUP"
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_PATCH_D5_6_4.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d564-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_6_4_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_6_4_OK_$STAMP"
API_TEST_IMAGE='dhv2-api:d564-test'
WEB_TEST_IMAGE='dhv2-web:d564-test'
TEST_LOG="/tmp/dhv2-d564-tests-$STAMP.log"
MIGRATION_LOG="/tmp/dhv2-d564-migration-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d564-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$TEST_LOG" "$MIGRATION_LOG" "$HEALTH_FILE"
docker image rm "$API_TEST_IMAGE" "$WEB_TEST_IMAGE" >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.6.4 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == '0.19.6-2' ]] || { echo "ERROR: D5.6.4 requiere API 0.19.6-2. Actual: $CURRENT_API"; exit 1; }
[[ "$CURRENT_WEB" == '0.19.6-3' ]] || { echo "ERROR: D5.6.4 requiere WEB 0.19.6-3. Actual: $CURRENT_WEB"; exit 1; }
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == 'healthy' ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
echo '[1/10] Staging incremental'
mkdir -p "$STAGE"
cp -a api-v3 "$STAGE/api-v3"
cp -a web-v2 "$STAGE/web-v2"
mkdir -p "$STAGE/scripts"
unzip -oq "$ZIP" 'api-v3/*' 'web-v2/*' 'scripts/*' -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == '0.19.6-4' ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == '0.19.6-4' ]]
bash "$STAGE/scripts/check-phase-d5-6-4.sh" "$STAGE"
echo '[2/10] Build API en staging'
docker build --target builder -t "$API_TEST_IMAGE" "$STAGE/api-v3"
echo '[3/10] Tests API en staging'
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
"$API_TEST_IMAGE" npm test 2>&1 | tee "$TEST_LOG"
grep -Eq 'tests[[:space:]]+266' "$TEST_LOG"
grep -Eq 'pass[[:space:]]+266' "$TEST_LOG"
grep -Eq 'fail[[:space:]]+0' "$TEST_LOG"
echo '[4/10] Build WEB en staging'
docker build -t "$WEB_TEST_IMAGE" "$STAGE/web-v2"
echo '[5/10] Backup PRE'
mkdir -p "$PRE_BACKUP"; chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore README.md
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
printf 'Base previa API=%s WEB=%s\n' "$CURRENT_API" "$CURRENT_WEB" > "$PRE_BACKUP/BASE_VERSION.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz BASE_VERSION.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo '[6/10] Aplicando patch incremental'
unzip -oq "$ZIP" 'api-v3/*' 'web-v2/*' 'scripts/*' -d "$ROOT"
chmod +x scripts/check-phase-d5-6-4.sh scripts/phase-d5-6-4-acceptance.sh scripts/deploy-phase-d5-6-4.sh
bash scripts/check-phase-d5-6-4.sh "$ROOT"
echo '[7/10] Construyendo imágenes finales'
docker compose build api migrate web
echo '[8/10] Verificando migraciones'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo '[9/10] Recreando API/web y aceptación'
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == '40' ]]; then docker compose logs --tail=200 api web; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.6-4"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.6.4"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash scripts/phase-d5-6-4-acceptance.sh
echo '[10/10] Backup POST'
mkdir -p "$POST_BACKUP"; chmod 700 "$POST_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
tar -czf "$POST_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore README.md
cp -a "$ZIP" "$POST_BACKUP/"
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
printf 'D5.6.4 OK\nBase previa API=%s WEB=%s\nVersión final API/WEB=0.19.6-4\n' "$CURRENT_API" "$CURRENT_WEB" > "$POST_BACKUP/BACKUP_OK.txt"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
(
cd "$POST_BACKUP"
sha256sum database.dump source.tar.gz "$(basename "$ZIP")" BACKUP_OK.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo
echo 'ACEPTACIÓN DE FASE D5.6.4: OK'
echo 'D5.6.4 desplegada correctamente'
echo 'API: 0.19.6-4'
echo 'WEB: 0.19.6-4'
echo "Backup PRE: $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
ZIP="${ZIP:-$ROOT/DH_V2_FASE_D5_6_SOURCE.zip}"
STAMP="$(date +%Y%m%d_%H%M%S)"
STAGE="/root/dhv2-d56-stage-$STAMP"
BACKUP_ROOT="/root/DH_V2_BACKUPS"
PRE_BACKUP="$BACKUP_ROOT/D5_6_PRE_$STAMP"
POST_BACKUP="$BACKUP_ROOT/D5_6_OK_$STAMP"
CLOSURE_BACKUP="$BACKUP_ROOT/FASE_D_CERRADA_$STAMP"
MIGRATION_LOG="/tmp/dhv2-d56-migration-$STAMP.log"
TEST_LOG="/tmp/dhv2-d56-tests-$STAMP.log"
HEALTH_FILE="/tmp/dhv2-d56-health-$STAMP.json"
MIN_FREE_KB=$((4 * 1024 * 1024))
cleanup() {
rm -rf "$STAGE"
rm -f "$MIGRATION_LOG" "$TEST_LOG" "$HEALTH_FILE"
docker image rm dhv2-api:d56-test dhv2-web:d56-test >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'echo; echo "ERROR D5.6 en línea $LINENO: $BASH_COMMAND"' ERR
cd "$ROOT"
test -f "$ZIP"
CURRENT_API="$(node -p "require('./api-v3/package.json').version")"
CURRENT_WEB="$(node -p "require('./web-v2/package.json').version")"
[[ "$CURRENT_API" == "0.19.5-2" ]]
[[ "$CURRENT_WEB" == "0.19.5-2" ]]
echo "[1/10] Preflight y staging"
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
if [[ ! "$FREE_KB" =~ ^[0-9]+$ ]] || (( FREE_KB < MIN_FREE_KB )); then
echo "ERROR: espacio libre insuficiente. Libre: $(( FREE_KB / 1024 )) MB"
exit 1
fi
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}' 2>/dev/null || true)" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
mkdir -p "$STAGE"
unzip -q "$ZIP" -d "$STAGE"
[[ "$(node -p "require('$STAGE/api-v3/package.json').version")" == "0.19.6" ]]
[[ "$(node -p "require('$STAGE/web-v2/package.json').version")" == "0.19.6" ]]
grep -Fq "API_PHASE = 'D5.6'" "$STAGE/api-v3/src/version.ts"
grep -Fq "APP_PHASE = 'Fase D5.6 · Cierre integral de Fase D'" "$STAGE/web-v2/src/config/version.ts"
if find "$STAGE/api-v3/src/database/migrations" -maxdepth 1 -type f -iname '*d5-6*' | grep -q .; then
echo "ERROR: D5.6 no debe agregar una migración"
exit 1
fi
echo "[2/10] Compilando API en staging"
docker build --target builder -t dhv2-api:d56-test "$STAGE/api-v3"
echo "[3/10] Ejecutando suite integral en staging"
docker run --rm \
-v "$STAGE/api-v3/test:/app/test:ro" \
-v "$STAGE/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
dhv2-api:d56-test npm test 2>&1 | tee "$TEST_LOG"
grep -Eq 'tests[[:space:]]+260' "$TEST_LOG"
grep -Eq 'pass[[:space:]]+260' "$TEST_LOG"
grep -Eq 'fail[[:space:]]+0' "$TEST_LOG"
echo "[4/10] Compilando web y auditando terminología"
docker build -t dhv2-web:d56-test "$STAGE/web-v2"
grep -Fq 'ARCHIVOS DEL INVENTARIO' "$STAGE/web-v2/src/features/assets/AssetMediaPanel.tsx"
grep -Fq 'INVENTARIO DEL ACTA' "$STAGE/web-v2/src/pages/InspectionActEditorPage.tsx"
if grep -RnhE --include='*.tsx' 'ARCHIVOS DEL ACTIVO|ACTIVOS DEL ACTA' "$STAGE/web-v2/src"; then exit 1; fi
grep -Fq 'debe iniciar la visita desde la APK' "$STAGE/web-v2/src/pages/InspectionVisitEditorPage.tsx"
echo "[5/10] Backup PRE D5.6"
mkdir -p "$PRE_BACKUP"
chmod 700 "$PRE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$PRE_BACKUP/database.dump"
tar -czf "$PRE_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore README.md
if [[ -f .env ]]; then cp -a .env "$PRE_BACKUP/.env"; chmod 600 "$PRE_BACKUP/.env"; fi
STRONG_BACKUP="$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
printf 'Backup fuerte conceptual previo: %s\nPalabra clave: INVENTARIO A REVISAR\n' "$STRONG_BACKUP" > "$PRE_BACKUP/INVENTARIO_A_REVISAR_REFERENCE.txt"
(
cd "$PRE_BACKUP"
sha256sum database.dump source.tar.gz INVENTARIO_A_REVISAR_REFERENCE.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
echo "[6/10] Instalando source D5.6"
unzip -oq "$ZIP" -d "$ROOT"
chmod +x "$ROOT/scripts/deploy-phase-d5-6.sh" "$ROOT/scripts/phase-d5-6-acceptance.sh"
echo "API source: $(node -p "require('./api-v3/package.json').version")"
echo "WEB source: $(node -p "require('./web-v2/package.json').version")"
echo "[7/10] Construyendo imágenes finales"
docker compose build api migrate web
echo "[8/10] Verificando que D5.6 no requiere migración"
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$MIGRATION_LOG"
grep -Fq 'Pending migrations: no' "$MIGRATION_LOG"
echo "[9/10] Recreando API/web y aceptación integral"
docker compose up -d --force-recreate api web
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:3101/api/v3/health >"$HEALTH_FILE" 2>/dev/null; then break; fi
if [[ "$i" == "40" ]]; then docker compose logs --tail=200 api; exit 1; fi
sleep 2
done
cat "$HEALTH_FILE"; echo
grep -Fq '"version":"0.19.6"' "$HEALTH_FILE"
grep -Fq '"phase":"D5.6"' "$HEALTH_FILE"
grep -Fq '"database":"ok"' "$HEALTH_FILE"
bash "$ROOT/scripts/phase-d5-6-acceptance.sh"
echo "[10/10] Backup POST y cierre formal de Fase D"
mkdir -p "$POST_BACKUP" "$CLOSURE_BACKUP"
chmod 700 "$POST_BACKUP" "$CLOSURE_BACKUP"
docker compose exec -T db sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$POST_BACKUP/database.dump"
tar -czf "$POST_BACKUP/source.tar.gz" \
--exclude='api-v3/node_modules' --exclude='api-v3/dist' \
--exclude='web-v2/node_modules' --exclude='web-v2/dist' \
api-v3 web-v2 scripts docs docker-compose.yml .env.example .gitignore README.md
docker compose ps -a > "$POST_BACKUP/docker-status.txt"
cp -a "$ZIP" "$POST_BACKUP/"
if [[ -f .env ]]; then cp -a .env "$POST_BACKUP/.env"; chmod 600 "$POST_BACKUP/.env"; fi
printf 'PRE D5.6: %s\nINVENTARIO A REVISAR: %s\nFASE D: CERRADA\n' "$PRE_BACKUP" "$STRONG_BACKUP" > "$POST_BACKUP/BACKUP_REFERENCES.txt"
printf 'FASE D CERRADA\nVersión API/WEB: 0.19.6\nBackup fuente: %s\nFecha: %s\n' "$POST_BACKUP" "$(date -Is)" > "$POST_BACKUP/FASE_D_CERRADA.txt"
(
cd "$POST_BACKUP"
sha256sum database.dump source.tar.gz "$(basename "$ZIP")" BACKUP_REFERENCES.txt FASE_D_CERRADA.txt > SHA256SUMS.txt
[[ ! -f .env ]] || sha256sum .env >> SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
)
# Hard links preserve a second strong closure label without duplicating large files.
ln "$POST_BACKUP/database.dump" "$CLOSURE_BACKUP/database.dump"
ln "$POST_BACKUP/source.tar.gz" "$CLOSURE_BACKUP/source.tar.gz"
ln "$POST_BACKUP/$(basename "$ZIP")" "$CLOSURE_BACKUP/$(basename "$ZIP")"
cp "$POST_BACKUP/SHA256SUMS.txt" "$CLOSURE_BACKUP/SHA256SUMS.txt"
printf 'FASE D CERRADA\nBackup canónico: %s\nVersión: 0.19.6\n' "$POST_BACKUP" > "$CLOSURE_BACKUP/BACKUP_OK.txt"
echo
echo "ACEPTACIÓN INTEGRAL DE FASE D: OK"
echo "FASE D: CERRADA"
echo "D5.6 desplegada correctamente"
echo "Backup PRE: $PRE_BACKUP"
echo "Backup POST: $POST_BACKUP"
echo "Backup fuerte FASE D CERRADA: $CLOSURE_BACKUP"
+276
View File
@@ -0,0 +1,276 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
TOKEN="DH-DEV-DEMO"
APPLY=false
if [[ "${1:-}" == "--apply" && "${2:-}" == "$TOKEN" ]]; then APPLY=true; fi
cd "$ROOT"
DB_SERVICE="${DB_SERVICE:-db}"
psql_owner() {
docker compose exec -T "$DB_SERVICE" sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -v ON_ERROR_STOP=1' <<<"$1"
}
DEMO_CTE="WITH demo_assets AS (
SELECT id FROM assets
WHERE code LIKE 'DEV-DEMO-%' OR source_reference LIKE 'DH-DEV-DEMO:%'
), demo_documents AS (
SELECT id FROM source_documents WHERE external_reference LIKE 'DH-DEV-DEMO:%' OR document_number LIKE '%DH-DEV-DEMO%'
), demo_visits AS (
SELECT DISTINCT v.id
FROM inspection_visits v
LEFT JOIN inspection_visit_assets va ON va.visit_id = v.id
WHERE v.scope_asset_id IN (SELECT id FROM demo_assets)
OR va.asset_id IN (SELECT id FROM demo_assets)
), demo_acts AS (
SELECT id FROM inspection_acts WHERE visit_id IN (SELECT id FROM demo_visits)
), demo_campaigns AS (
SELECT DISTINCT c.id
FROM survey_campaigns c
LEFT JOIN survey_campaign_targets t ON t.campaign_id = c.id
WHERE c.scope_asset_id IN (SELECT id FROM demo_assets)
OR t.asset_id IN (SELECT id FROM demo_assets)
)"
asset_count="$(psql_owner "$DEMO_CTE SELECT COUNT(*) FROM demo_assets;")"
visit_count="$(psql_owner "$DEMO_CTE SELECT COUNT(*) FROM demo_visits;")"
act_count="$(psql_owner "$DEMO_CTE SELECT COUNT(*) FROM demo_acts;")"
campaign_count="$(psql_owner "$DEMO_CTE SELECT COUNT(*) FROM demo_campaigns;")"
document_count="$(psql_owner "$DEMO_CTE SELECT COUNT(*) FROM demo_documents;")"
echo "========== DH · LIMPIEZA DE DATOS DE PRUEBA =========="
echo "Activos DEV-DEMO detectados : $asset_count"
echo "Visitas relacionadas : $visit_count"
echo "Actas relacionadas : $act_count"
echo "Relevamientos relacionados : $campaign_count"
echo "Documentos DEV-DEMO : $document_count"
echo
if [[ "$asset_count" == "0" ]]; then
echo "No hay datos DEV-DEMO para limpiar."
exit 0
fi
# Nunca ampliamos implícitamente el conjunto a registros no marcados como prueba.
# Si un dato no demo depende de uno demo, o un flujo mezcla ambos entornos, se aborta.
MIXED_SQL="$DEMO_CTE
SELECT
(SELECT COUNT(*) FROM assets a
WHERE a.id NOT IN (SELECT id FROM demo_assets)
AND (a.parent_id IN (SELECT id FROM demo_assets)
OR a.operational_area_id IN (SELECT id FROM demo_assets)
OR a.operator_company_id IN (SELECT id FROM demo_assets)))
+ (SELECT COUNT(*) FROM area_company_relations r
WHERE (r.area_id IN (SELECT id FROM demo_assets)) <> (r.company_id IN (SELECT id FROM demo_assets)))
+ (SELECT COUNT(*) FROM inspection_visits v
WHERE v.id IN (SELECT id FROM demo_visits)
AND v.scope_asset_id IS NOT NULL
AND v.scope_asset_id NOT IN (SELECT id FROM demo_assets))
+ (SELECT COUNT(*) FROM inspection_visit_assets va
WHERE va.visit_id IN (SELECT id FROM demo_visits)
AND va.asset_id NOT IN (SELECT id FROM demo_assets))
+ (SELECT COUNT(*) FROM inspection_act_assets aa
WHERE aa.act_id IN (SELECT id FROM demo_acts)
AND aa.asset_id NOT IN (SELECT id FROM demo_assets))
+ (SELECT COUNT(*) FROM inspection_findings f
WHERE f.act_id IN (SELECT id FROM demo_acts)
AND f.asset_id NOT IN (SELECT id FROM demo_assets))
+ (SELECT COUNT(*) FROM inspection_findings f
WHERE f.asset_id IN (SELECT id FROM demo_assets)
AND f.act_id NOT IN (SELECT id FROM demo_acts))
+ (SELECT COUNT(*) FROM survey_campaigns c
WHERE c.id IN (SELECT id FROM demo_campaigns)
AND c.scope_asset_id IS NOT NULL
AND c.scope_asset_id NOT IN (SELECT id FROM demo_assets))
+ (SELECT COUNT(*) FROM survey_campaign_targets t
WHERE t.campaign_id IN (SELECT id FROM demo_campaigns)
AND t.asset_id NOT IN (SELECT id FROM demo_assets))
+ (SELECT COUNT(*) FROM organization_memberships m
WHERE (m.parent_organization_id IN (SELECT id FROM demo_assets)) <> (m.member_organization_id IN (SELECT id FROM demo_assets)))
+ (SELECT COUNT(*) FROM asset_source_documents l
WHERE l.document_id IN (SELECT id FROM demo_documents) AND l.asset_id NOT IN (SELECT id FROM demo_assets))
+ (SELECT COUNT(*) FROM area_legal_right_organizations o
JOIN area_legal_rights r ON r.id=o.right_id
WHERE (r.area_id IN (SELECT id FROM demo_assets)) <> (o.organization_id IN (SELECT id FROM demo_assets)));"
mixed="$(psql_owner "$MIXED_SQL")"
if [[ "$mixed" != "0" ]]; then
echo "ABORTADO: se detectaron $mixed vínculo(s) entre DEV-DEMO y datos no marcados como prueba."
echo "El limpiador no borrará registros ambiguos. Revisá esas relaciones manualmente."
exit 2
fi
if [[ "$APPLY" != true ]]; then
echo "MODO SIMULACIÓN: no se modificó nada."
echo "La limpieza real queda reservada para reiniciar el demo o preparar la futura entrega."
echo "Comando protegido:"
echo " bash scripts/dev-clean-demo.sh --apply DH-DEV-DEMO"
exit 0
fi
STAMP="$(date +%Y%m%d_%H%M%S)"
BACKUP="/root/DH_V2_BACKUPS/DEV_CLEAN_PRE_${STAMP}"
mkdir -p "$BACKUP"
echo "Creando backup obligatorio en $BACKUP ..."
docker compose exec -T "$DB_SERVICE" sh -lc \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$BACKUP/database.dump"
ASSET_FILES="$BACKUP/asset-files.txt"
EVIDENCE_FILES="$BACKUP/evidence-files.txt"
SIGNATURE_FILES="$BACKUP/signature-files.txt"
psql_owner "$DEMO_CTE SELECT stored_name FROM asset_media WHERE asset_id IN (SELECT id FROM demo_assets);" > "$ASSET_FILES"
psql_owner "$DEMO_CTE
SELECT e.stored_name
FROM inspection_finding_evidence e
JOIN inspection_findings f ON f.id=e.finding_id
WHERE f.act_id IN (SELECT id FROM demo_acts) OR f.asset_id IN (SELECT id FROM demo_assets);" > "$EVIDENCE_FILES"
psql_owner "$DEMO_CTE
SELECT s.stored_name
FROM inspection_act_signatures s
WHERE s.act_id IN (SELECT id FROM demo_acts) AND s.stored_name IS NOT NULL;" > "$SIGNATURE_FILES"
cat > "$BACKUP/cleanup.sql" <<'SQL'
BEGIN;
-- Sólo el usuario dueño de la base ejecuta este script. Replica desactiva temporalmente
-- los triggers append-only para poder retirar datos de desarrollo conservando backup previo.
SET LOCAL session_replication_role = replica;
CREATE TEMP TABLE demo_assets(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_assets
SELECT id FROM assets
WHERE code LIKE 'DEV-DEMO-%' OR source_reference LIKE 'DH-DEV-DEMO:%';
CREATE TEMP TABLE demo_documents(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_documents SELECT id FROM source_documents WHERE external_reference LIKE 'DH-DEV-DEMO:%' OR document_number LIKE '%DH-DEV-DEMO%';
CREATE TEMP TABLE demo_visits(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_visits
SELECT DISTINCT v.id
FROM inspection_visits v
LEFT JOIN inspection_visit_assets va ON va.visit_id=v.id
WHERE v.scope_asset_id IN (SELECT id FROM demo_assets)
OR va.asset_id IN (SELECT id FROM demo_assets);
CREATE TEMP TABLE demo_acts(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_acts SELECT id FROM inspection_acts WHERE visit_id IN (SELECT id FROM demo_visits);
CREATE TEMP TABLE demo_findings(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_findings
SELECT id FROM inspection_findings
WHERE act_id IN (SELECT id FROM demo_acts) OR asset_id IN (SELECT id FROM demo_assets);
CREATE TEMP TABLE demo_evidence(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_evidence SELECT id FROM inspection_finding_evidence WHERE finding_id IN (SELECT id FROM demo_findings);
CREATE TEMP TABLE demo_communications(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_communications SELECT id FROM inspection_finding_communications WHERE finding_id IN (SELECT id FROM demo_findings);
CREATE TEMP TABLE demo_signatures(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_signatures SELECT id FROM inspection_act_signatures WHERE act_id IN (SELECT id FROM demo_acts);
CREATE TEMP TABLE demo_campaigns(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_campaigns
SELECT DISTINCT c.id
FROM survey_campaigns c
LEFT JOIN survey_campaign_targets t ON t.campaign_id=c.id
WHERE c.scope_asset_id IN (SELECT id FROM demo_assets)
OR t.asset_id IN (SELECT id FROM demo_assets);
CREATE TEMP TABLE demo_targets(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_targets SELECT id FROM survey_campaign_targets WHERE campaign_id IN (SELECT id FROM demo_campaigns);
CREATE TEMP TABLE demo_reports(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_reports SELECT id FROM survey_target_reports WHERE target_id IN (SELECT id FROM demo_targets);
CREATE TEMP TABLE demo_relations(id uuid PRIMARY KEY) ON COMMIT DROP;
INSERT INTO demo_relations
SELECT id FROM area_company_relations
WHERE area_id IN (SELECT id FROM demo_assets) AND company_id IN (SELECT id FROM demo_assets);
DELETE FROM survey_target_report_media WHERE report_id IN (SELECT id FROM demo_reports);
DELETE FROM survey_target_report_versions WHERE report_id IN (SELECT id FROM demo_reports);
DELETE FROM survey_target_reports WHERE id IN (SELECT id FROM demo_reports);
DELETE FROM survey_campaign_targets WHERE id IN (SELECT id FROM demo_targets);
DELETE FROM survey_campaigns WHERE id IN (SELECT id FROM demo_campaigns);
DELETE FROM inspection_finding_evidence WHERE id IN (SELECT id FROM demo_evidence);
DELETE FROM inspection_finding_communications WHERE id IN (SELECT id FROM demo_communications);
DELETE FROM inspection_finding_versions WHERE finding_id IN (SELECT id FROM demo_findings);
DELETE FROM inspection_findings WHERE id IN (SELECT id FROM demo_findings);
DELETE FROM inspection_act_signatures WHERE id IN (SELECT id FROM demo_signatures);
DELETE FROM inspection_act_closures WHERE act_id IN (SELECT id FROM demo_acts);
DELETE FROM inspection_act_responsibles WHERE act_id IN (SELECT id FROM demo_acts);
DELETE FROM inspection_act_versions WHERE act_id IN (SELECT id FROM demo_acts);
DELETE FROM inspection_act_assets WHERE act_id IN (SELECT id FROM demo_acts);
DELETE FROM inspection_acts WHERE id IN (SELECT id FROM demo_acts);
DELETE FROM inspection_visit_members WHERE visit_id IN (SELECT id FROM demo_visits);
DELETE FROM inspection_visit_assets WHERE visit_id IN (SELECT id FROM demo_visits);
DELETE FROM inspection_visits WHERE id IN (SELECT id FROM demo_visits);
DELETE FROM area_legal_right_organizations WHERE organization_id IN (SELECT id FROM demo_assets) OR right_id IN (SELECT id FROM area_legal_rights WHERE area_id IN (SELECT id FROM demo_assets));
DELETE FROM area_legal_rights WHERE area_id IN (SELECT id FROM demo_assets);
DELETE FROM organization_memberships WHERE parent_organization_id IN (SELECT id FROM demo_assets) OR member_organization_id IN (SELECT id FROM demo_assets);
DELETE FROM asset_external_identifiers WHERE asset_id IN (SELECT id FROM demo_assets);
DELETE FROM asset_source_documents WHERE asset_id IN (SELECT id FROM demo_assets) OR document_id IN (SELECT id FROM demo_documents);
DELETE FROM area_company_relations WHERE id IN (SELECT id FROM demo_relations);
DELETE FROM organization_profiles WHERE asset_id IN (SELECT id FROM demo_assets);
DELETE FROM asset_media WHERE asset_id IN (SELECT id FROM demo_assets);
DELETE FROM asset_geometries WHERE asset_id IN (SELECT id FROM demo_assets);
DELETE FROM asset_attribute_values WHERE asset_id IN (SELECT id FROM demo_assets);
DELETE FROM asset_versions WHERE asset_id IN (SELECT id FROM demo_assets);
DELETE FROM audit_events
WHERE request_id LIKE 'dev-seed-%'
OR entity_id IN (
SELECT id::text FROM demo_assets
UNION SELECT id::text FROM demo_visits
UNION SELECT id::text FROM demo_acts
UNION SELECT id::text FROM demo_findings
UNION SELECT id::text FROM demo_evidence
UNION SELECT id::text FROM demo_communications
UNION SELECT id::text FROM demo_signatures
UNION SELECT id::text FROM demo_campaigns
UNION SELECT id::text FROM demo_targets
UNION SELECT id::text FROM demo_reports
UNION SELECT id::text FROM demo_relations
UNION SELECT id::text FROM demo_documents
);
DELETE FROM assets WHERE id IN (SELECT id FROM demo_assets);
DELETE FROM source_documents d WHERE d.id IN (SELECT id FROM demo_documents)
AND NOT EXISTS (SELECT 1 FROM asset_source_documents l WHERE l.document_id=d.id)
AND NOT EXISTS (SELECT 1 FROM area_company_relations r WHERE r.source_document_id=d.id)
AND NOT EXISTS (SELECT 1 FROM organization_memberships m WHERE m.source_document_id=d.id)
AND NOT EXISTS (SELECT 1 FROM area_legal_rights r WHERE r.source_document_id=d.id)
AND NOT EXISTS (SELECT 1 FROM asset_external_identifiers i WHERE i.source_document_id=d.id);
-- La numeración de actas vuelve al máximo real remanente. Si no queda ninguna acta
-- de un año, se elimina la secuencia para que ese año pueda volver a comenzar en 1.
DELETE FROM document_annual_sequences s
WHERE s.document_type='ACT'
AND NOT EXISTS (SELECT 1 FROM inspection_acts a WHERE a.act_year=s.year);
UPDATE document_annual_sequences s
SET last_number=q.max_number, updated_at=CURRENT_TIMESTAMP
FROM (
SELECT act_year, MAX(act_number) AS max_number
FROM inspection_acts
GROUP BY act_year
) q
WHERE s.document_type='ACT' AND s.year=q.act_year;
COMMIT;
SQL
docker compose exec -T "$DB_SERVICE" sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1' < "$BACKUP/cleanup.sql"
while IFS= read -r f; do [[ -z "$f" ]] || docker compose exec -T api rm -f -- "/app/storage/asset-media/$f"; done < "$ASSET_FILES"
while IFS= read -r f; do [[ -z "$f" ]] || docker compose exec -T api rm -f -- "/app/storage/asset-media/inspection-findings/$f"; done < "$EVIDENCE_FILES"
while IFS= read -r f; do [[ -z "$f" ]] || docker compose exec -T api rm -f -- "/app/storage/asset-media/inspection-signatures/$f"; done < "$SIGNATURE_FILES"
echo "Limpieza DEV-DEMO finalizada."
echo "Se conservaron tipos, atributos, jerarquías, usuarios, roles, permisos y catálogo de hallazgos."
echo "Backup previo: $BACKUP/database.dump"
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd /var/www/dhv2.korexlabs.com
echo "Creando caso de prueba DH DEV DEMO..."
docker compose exec -T api npm run dev:seed:mendoza-demo
+159
View File
@@ -0,0 +1,159 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_tmp="$(mktemp -d)"
umask 077
cleanup() {
rm -rf "$dhv2_tmp"
unset dhv2_username dhv2_password dhv2_csrf
}
trap cleanup EXIT
fail() {
echo "ERROR: $*" >&2
exit 1
}
command -v curl >/dev/null || fail "curl no está instalado"
command -v python3 >/dev/null || fail "python3 no está instalado"
echo "========== A7 · PRUEBAS SIN SESIÓN =========="
for dhv2_path in dashboard/summary users roles audit; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o "$dhv2_tmp/public.json" -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" || fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
echo
echo "========== A7 · LOGIN INVÁLIDO GENÉRICO =========="
DHV2_PAYLOAD_PATH="$dhv2_tmp/invalid-payload.json" python3 - <<'PY'
import json
import os
from pathlib import Path
Path(os.environ['DHV2_PAYLOAD_PATH']).write_text(json.dumps({
'identifier': 'a7_usuario_inexistente',
'password': 'A7-invalida-2026',
'deviceLabel': 'Prueba A7',
}), encoding='utf-8')
PY
dhv2_invalid_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o "$dhv2_tmp/invalid.json" -w '%{http_code}' \
-H 'Content-Type: application/json' \
--data-binary "@$dhv2_tmp/invalid-payload.json" \
"$dhv2_base_url/api/v3/auth/login")"
test "$dhv2_invalid_code" = "401" || fail "El login inválido devolvió HTTP $dhv2_invalid_code; se esperaba 401"
grep -q '"code":"INVALID_CREDENTIALS"' "$dhv2_tmp/invalid.json" || fail "El login inválido no devolvió el error genérico esperado"
echo "Login inválido genérico: 401 OK"
echo
echo "========== A7 · LOGIN ADMINISTRATIVO =========="
dhv2_username="${DHV2_TEST_USERNAME:-}"
dhv2_password="${DHV2_TEST_PASSWORD:-}"
if [ -z "$dhv2_username" ]; then
read -r -p "Usuario administrador: " dhv2_username
fi
if [ -z "$dhv2_password" ]; then
read -r -s -p "Contraseña: " dhv2_password
echo
fi
test -n "$dhv2_username" || fail "El usuario es obligatorio"
test -n "$dhv2_password" || fail "La contraseña es obligatoria"
DHV2_USERNAME="$dhv2_username" \
DHV2_PASSWORD="$dhv2_password" \
DHV2_PAYLOAD_PATH="$dhv2_tmp/login-payload.json" \
python3 - <<'PY'
import json
import os
from pathlib import Path
Path(os.environ['DHV2_PAYLOAD_PATH']).write_text(json.dumps({
'identifier': os.environ['DHV2_USERNAME'],
'password': os.environ['DHV2_PASSWORD'],
'deviceLabel': 'Aceptación Fase A7',
}), encoding='utf-8')
PY
dhv2_login_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-D "$dhv2_tmp/login.headers" \
-c "$dhv2_tmp/cookies.txt" \
-o "$dhv2_tmp/login.json" \
-w '%{http_code}' \
-H 'Content-Type: application/json' \
--data-binary "@$dhv2_tmp/login-payload.json" \
"$dhv2_base_url/api/v3/auth/login")"
rm -f "$dhv2_tmp/login-payload.json"
unset dhv2_password
test "$dhv2_login_code" = "200" || fail "El login administrativo devolvió HTTP $dhv2_login_code"
grep -q '"mustChangePassword":false' "$dhv2_tmp/login.json" || fail "La cuenta todavía exige cambiar la contraseña desde el panel"
dhv2_csrf="$(DHV2_RESPONSE_PATH="$dhv2_tmp/login.json" python3 - <<'PY'
import json
import os
from pathlib import Path
data = json.loads(Path(os.environ['DHV2_RESPONSE_PATH']).read_text(encoding='utf-8'))
print(data.get('csrfToken', ''))
PY
)"
test -n "$dhv2_csrf" || fail "El login no devolvió un token CSRF"
dhv2_secure_cookie_count="$(grep -Ei '^set-cookie:' "$dhv2_tmp/login.headers" | grep -Eic 'Secure.*SameSite=Strict' || true)"
dhv2_http_only_count="$(grep -Ei '^set-cookie:' "$dhv2_tmp/login.headers" | grep -Eic 'HttpOnly.*Secure.*SameSite=Strict' || true)"
test "$dhv2_secure_cookie_count" -ge 3 || fail "No se detectaron las tres cookies Secure + SameSite=Strict"
test "$dhv2_http_only_count" -ge 2 || fail "Las cookies de acceso y refresh no tienen todos los atributos requeridos"
echo "Login y atributos de cookies: OK"
echo
echo "========== A7 · ENDPOINTS AUTENTICADOS =========="
for dhv2_path in auth/me dashboard/summary 'users?page=1&pageSize=1' roles 'audit?page=1&pageSize=1'; do
dhv2_safe_name="${dhv2_path//[^a-zA-Z0-9]/_}"
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-b "$dhv2_tmp/cookies.txt" \
-o "$dhv2_tmp/$dhv2_safe_name.json" \
-w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "200" || fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 200"
echo "/api/v3/$dhv2_path autenticado: 200 OK"
done
echo
echo "========== A7 · CSRF Y LOGOUT =========="
dhv2_csrf_failure="$(curl -sS --connect-timeout 10 --max-time 30 \
-b "$dhv2_tmp/cookies.txt" \
-o "$dhv2_tmp/csrf-failure.json" \
-w '%{http_code}' \
-X POST "$dhv2_base_url/api/v3/auth/logout")"
test "$dhv2_csrf_failure" = "403" || fail "Una escritura sin CSRF devolvió HTTP $dhv2_csrf_failure; se esperaba 403"
grep -q '"code":"CSRF_INVALID"' "$dhv2_tmp/csrf-failure.json" || fail "No se devolvió CSRF_INVALID"
echo "Escritura sin CSRF: 403 OK"
dhv2_logout_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-b "$dhv2_tmp/cookies.txt" \
-c "$dhv2_tmp/cookies.txt" \
-o "$dhv2_tmp/logout.json" \
-w '%{http_code}' \
-H "x-csrf-token: $dhv2_csrf" \
-X POST "$dhv2_base_url/api/v3/auth/logout")"
test "$dhv2_logout_code" = "200" || fail "Logout devolvió HTTP $dhv2_logout_code"
dhv2_after_logout="$(curl -sS --connect-timeout 10 --max-time 30 \
-b "$dhv2_tmp/cookies.txt" \
-o "$dhv2_tmp/after-logout.json" \
-w '%{http_code}' \
"$dhv2_base_url/api/v3/auth/me")"
test "$dhv2_after_logout" = "401" || fail "La sesión siguió activa después del logout"
echo "Logout y revocación de sesión: OK"
echo
echo "ACEPTACIÓN AUTENTICADA DE FASE A: OK"
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== B1 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== B1 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.8.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.8.0"
grep -q '"phase":"B1"' <<<"$dhv2_internal" || fail "Health interno no informa B1"
grep -q '"version":"0.8.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.8.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== B1 · RUTAS PROTEGIDAS =========="
for dhv2_path in assets asset-types; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-b1-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" || fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-b1-response.json
echo
echo "========== B1 · ESQUEMA Y PERMISOS =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -At' <<'SQL'
SELECT 'tables', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN (
'asset_types',
'asset_type_parent_rules',
'asset_attribute_definitions',
'assets',
'asset_attribute_values'
);
SELECT 'permissions', COUNT(*)
FROM permissions
WHERE code IN (
'asset_types.read',
'asset_types.manage',
'assets.read',
'assets.create',
'assets.update',
'assets.change_status'
);
SELECT 'admin_permissions', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code = 'admin'
AND permission.code IN (
'asset_types.read',
'asset_types.manage',
'assets.read',
'assets.create',
'assets.update',
'assets.change_status'
);
SQL
)"
grep -q 'tables|5' <<<"$dhv2_db_check" || fail "No se encontraron las cinco tablas de B1"
grep -q 'permissions|6' <<<"$dhv2_db_check" || fail "No se encontraron los seis permisos de B1"
grep -q 'admin_permissions|6' <<<"$dhv2_db_check" || fail "El rol admin no recibió todos los permisos de B1"
echo "$dhv2_db_check"
echo
echo "========== B1 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.8.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Base del Maestro de Activos" /usr/share/nginx/html/assets >/dev/null'
echo "Frontend y footer v0.8.0: OK"
echo
echo "ACEPTACIÓN DE FASE B1: OK"
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== B2 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== B2 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.9.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.9.0"
grep -q '"phase":"B2"' <<<"$dhv2_internal" || fail "Health interno no informa B2"
grep -q '"version":"0.9.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.9.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== B2 · RUTAS PROTEGIDAS =========="
for dhv2_path in map/assets assets; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-b2-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" || fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-b2-response.json
echo
echo "========== B2 · POSTGIS Y PERMISOS =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -At' <<'SQL'
SELECT 'postgis', COUNT(*)
FROM pg_extension
WHERE extname = 'postgis';
SELECT 'geometry_table', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'asset_geometries';
SELECT 'geometry_column', COUNT(*)
FROM geometry_columns
WHERE f_table_schema = 'public'
AND f_table_name = 'asset_geometries'
AND f_geometry_column = 'geometry'
AND srid = 4326;
SELECT 'gist_index', COUNT(*)
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'asset_geometries'
AND indexname = 'idx_asset_geometries_geometry';
SELECT 'permission', COUNT(*)
FROM permissions
WHERE code = 'assets.update_geometry';
SELECT 'admin_permission', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code = 'admin'
AND permission.code = 'assets.update_geometry';
SQL
)"
grep -q 'postgis|1' <<<"$dhv2_db_check" || fail "PostGIS no está disponible"
grep -q 'geometry_table|1' <<<"$dhv2_db_check" || fail "No se creó asset_geometries"
grep -q 'geometry_column|1' <<<"$dhv2_db_check" || fail "La geometría EPSG:4326 no fue registrada"
grep -q 'gist_index|1' <<<"$dhv2_db_check" || fail "No se encontró el índice espacial GIST"
grep -q 'permission|1' <<<"$dhv2_db_check" || fail "No se creó assets.update_geometry"
grep -q 'admin_permission|1' <<<"$dhv2_db_check" || fail "Admin no recibió assets.update_geometry"
echo "$dhv2_db_check"
echo
echo "========== B2 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.9.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Geometrías PostGIS y mapa operativo" /usr/share/nginx/html/assets >/dev/null && grep -R "Mapa de activos" /usr/share/nginx/html/assets >/dev/null'
echo "Mapa y footer v0.9.0: OK"
echo
echo "ACEPTACIÓN DE FASE B2: OK"
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== B3 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== B3 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.10.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.10.0"
grep -q '"phase":"B3"' <<<"$dhv2_internal" || fail "Health interno no informa B3"
grep -q '"version":"0.10.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.10.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== B3 · RUTAS PROTEGIDAS =========="
for dhv2_path in asset-versions assets; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-b3-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" || fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-b3-response.json
echo
echo "========== B3 · VERSIONADO Y PERMISOS =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'versions_table', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'asset_versions';
SELECT 'current_version_column', COUNT(*)
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'assets'
AND column_name = 'current_version';
SELECT 'history_index', COUNT(*)
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'asset_versions'
AND indexname = 'idx_asset_versions_asset_number';
SELECT 'backfill_mismatches', COUNT(*)
FROM assets asset
WHERE asset.current_version < 1
OR NOT EXISTS (
SELECT 1 FROM asset_versions version
WHERE version.asset_id = asset.id
AND version.version_number = asset.current_version
);
SELECT 'permission', COUNT(*)
FROM permissions
WHERE code = 'assets.read_history';
SELECT 'role_permissions', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'assets.read_history';
SELECT 'app_select', has_table_privilege(:'app_user', 'asset_versions', 'SELECT');
SELECT 'app_insert', has_table_privilege(:'app_user', 'asset_versions', 'INSERT');
SELECT 'app_update', has_table_privilege(:'app_user', 'asset_versions', 'UPDATE');
SELECT 'app_delete', has_table_privilege(:'app_user', 'asset_versions', 'DELETE');
SQL
)"
grep -q 'versions_table|1' <<<"$dhv2_db_check" || fail "No se creó asset_versions"
grep -q 'current_version_column|1' <<<"$dhv2_db_check" || fail "assets no tiene current_version"
grep -q 'history_index|1' <<<"$dhv2_db_check" || fail "No se encontró el índice temporal"
grep -q 'backfill_mismatches|0' <<<"$dhv2_db_check" || fail "Hay activos sin versión vigente"
grep -q 'permission|1' <<<"$dhv2_db_check" || fail "No se creó assets.read_history"
grep -q 'role_permissions|5' <<<"$dhv2_db_check" || fail "Los cinco roles base no recibieron lectura histórica"
grep -q 'app_select|t' <<<"$dhv2_db_check" || fail "La API no puede consultar versiones"
grep -q 'app_insert|t' <<<"$dhv2_db_check" || fail "La API no puede crear versiones"
grep -q 'app_update|f' <<<"$dhv2_db_check" || fail "La API no debe poder modificar versiones"
grep -q 'app_delete|f' <<<"$dhv2_db_check" || fail "La API no debe poder borrar versiones"
echo "$dhv2_db_check"
echo
echo "========== B3 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.10.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Historial y versiones de activos" /usr/share/nginx/html/assets >/dev/null && grep -R "Historial de activos" /usr/share/nginx/html/assets >/dev/null && grep -R "Snapshot técnico exacto" /usr/share/nginx/html/assets >/dev/null'
echo "Historial y footer v0.10.0: OK"
echo
echo "ACEPTACIÓN DE FASE B3: OK"
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== B4 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== B4 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.11.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.11.0"
grep -q '"phase":"B4"' <<<"$dhv2_internal" || fail "Health interno no informa B4"
grep -q '"version":"0.11.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.11.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== B4 · RUTAS PROTEGIDAS =========="
for dhv2_path in \
"assets/$dhv2_nil_uuid/media" \
"asset-media/$dhv2_nil_uuid/content"; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-b4-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" || fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-b4-response.json
echo
echo "========== B4 · MODELO Y PERMISOS =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'media_table', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'asset_media';
SELECT 'media_columns', COUNT(*)
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'asset_media'
AND column_name IN (
'id', 'asset_id', 'kind', 'original_name', 'stored_name', 'mime_type',
'size_bytes', 'sha256', 'title', 'description', 'captured_at', 'latitude',
'longitude', 'accuracy_m', 'source', 'uploaded_by', 'created_at',
'updated_at', 'deleted_at', 'deleted_by'
);
SELECT 'media_indexes', COUNT(*)
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'asset_media'
AND indexname IN (
'idx_asset_media_asset_created',
'idx_asset_media_sha256',
'idx_asset_media_active'
);
SELECT 'history_media_changes', COUNT(*)
FROM pg_constraint
WHERE conname = 'chk_asset_versions_change_type'
AND pg_get_constraintdef(oid) LIKE '%MEDIA_UPLOADED%'
AND pg_get_constraintdef(oid) LIKE '%MEDIA_UPDATED%'
AND pg_get_constraintdef(oid) LIKE '%MEDIA_REMOVED%';
SELECT 'permissions', COUNT(*)
FROM permissions
WHERE code IN ('assets.read_media', 'assets.manage_media');
SELECT 'read_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'assets.read_media';
SELECT 'manage_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor', 'inspector')
AND permission.code = 'assets.manage_media';
SELECT 'app_select', has_table_privilege(:'app_user', 'asset_media', 'SELECT');
SELECT 'app_insert', has_table_privilege(:'app_user', 'asset_media', 'INSERT');
SELECT 'app_update', has_table_privilege(:'app_user', 'asset_media', 'UPDATE');
SELECT 'app_delete', has_table_privilege(:'app_user', 'asset_media', 'DELETE');
SQL
)"
grep -q 'media_table|1' <<<"$dhv2_db_check" || fail "No se creó asset_media"
grep -q 'media_columns|20' <<<"$dhv2_db_check" || fail "asset_media no tiene el modelo esperado"
grep -q 'media_indexes|3' <<<"$dhv2_db_check" || fail "Faltan índices de archivos"
grep -q 'history_media_changes|1' <<<"$dhv2_db_check" || fail "El historial no reconoce cambios de archivos"
grep -q 'permissions|2' <<<"$dhv2_db_check" || fail "No se crearon los dos permisos de archivos"
grep -q 'read_roles|5' <<<"$dhv2_db_check" || fail "Los cinco roles base no recibieron lectura de archivos"
grep -q 'manage_roles|3' <<<"$dhv2_db_check" || fail "Los tres roles operativos no recibieron gestión de archivos"
grep -q 'app_select|t' <<<"$dhv2_db_check" || fail "La API no puede consultar archivos"
grep -q 'app_insert|t' <<<"$dhv2_db_check" || fail "La API no puede registrar archivos"
grep -q 'app_update|t' <<<"$dhv2_db_check" || fail "La API no puede retirar archivos"
grep -q 'app_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar físicamente registros de archivos"
echo "$dhv2_db_check"
echo
echo "========== B4 · ALMACENAMIENTO =========="
docker volume inspect dhv2_asset_media >/dev/null \
|| fail "No existe el volumen dhv2_asset_media"
docker compose exec -T api sh -lc \
'test "$ASSET_MEDIA_ROOT" = "/app/storage/asset-media" && test -d "$ASSET_MEDIA_ROOT" && test -w "$ASSET_MEDIA_ROOT"' \
|| fail "El almacenamiento de archivos no está montado y escribible"
echo "Volumen dhv2_asset_media montado y escribible: OK"
echo
echo "========== B4 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.11.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Fotografías y documentos de activos" /usr/share/nginx/html/assets >/dev/null && grep -R "Fotografías y documentos" /usr/share/nginx/html/assets >/dev/null && grep -R "Incorporar archivo" /usr/share/nginx/html/assets >/dev/null'
echo "Galería y footer v0.11.0: OK"
echo
echo "ACEPTACIÓN DE FASE B4: OK"
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
dhv2_now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== B5 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== B5 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.12.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.12.0"
grep -q '"phase":"B5"' <<<"$dhv2_internal" || fail "Health interno no informa B5"
grep -q '"version":"0.12.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.12.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== B5 · RUTAS PROTEGIDAS =========="
for dhv2_path in \
"assets/$dhv2_nil_uuid/provenance" \
"temporal-assets?at=$dhv2_now"; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-b5-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" || fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-b5-response.json
echo
echo "========== B5 · PROCEDENCIA Y TEMPORALIDAD =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'provenance_columns', COUNT(*)
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'assets'
AND column_name IN (
'data_origin', 'source_name', 'source_reference', 'source_observed_at',
'source_notes', 'provenance_verified_at', 'provenance_verified_by',
'provenance_updated_at', 'provenance_updated_by'
);
SELECT 'provenance_indexes', COUNT(*)
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'assets'
AND indexname IN (
'idx_assets_data_origin', 'idx_assets_provenance_verified_at'
);
SELECT 'history_provenance_changes', COUNT(*)
FROM pg_constraint
WHERE conname = 'chk_asset_versions_change_type'
AND pg_get_constraintdef(oid) LIKE '%PROVENANCE_BASELINE%'
AND pg_get_constraintdef(oid) LIKE '%PROVENANCE_UPDATED%'
AND pg_get_constraintdef(oid) LIKE '%PROVENANCE_VERIFIED%';
SELECT 'current_snapshot_mismatches', COUNT(*)
FROM assets asset
WHERE NOT EXISTS (
SELECT 1
FROM asset_versions version
WHERE version.asset_id = asset.id
AND version.version_number = asset.current_version
AND version.snapshot ? 'provenance'
);
SELECT 'permissions', COUNT(*)
FROM permissions
WHERE code IN (
'assets.read_provenance', 'assets.manage_provenance',
'assets.verify_provenance', 'assets.read_temporal'
);
SELECT 'read_provenance_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'assets.read_provenance';
SELECT 'read_temporal_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'assets.read_temporal';
SELECT 'manage_provenance_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor', 'inspector')
AND permission.code = 'assets.manage_provenance';
SELECT 'verify_provenance_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor')
AND permission.code = 'assets.verify_provenance';
SELECT 'app_assets_update', has_table_privilege(:'app_user', 'assets', 'UPDATE');
SELECT 'app_versions_update', has_table_privilege(:'app_user', 'asset_versions', 'UPDATE');
SELECT 'app_versions_delete', has_table_privilege(:'app_user', 'asset_versions', 'DELETE');
SQL
)"
grep -q 'provenance_columns|9' <<<"$dhv2_db_check" || fail "Faltan columnas de procedencia"
grep -q 'provenance_indexes|2' <<<"$dhv2_db_check" || fail "Faltan índices de procedencia"
grep -q 'history_provenance_changes|1' <<<"$dhv2_db_check" || fail "El historial no reconoce cambios de procedencia"
grep -q 'current_snapshot_mismatches|0' <<<"$dhv2_db_check" || fail "Hay activos cuya versión vigente no contiene procedencia"
grep -q 'permissions|4' <<<"$dhv2_db_check" || fail "No se crearon los cuatro permisos B5"
grep -q 'read_provenance_roles|5' <<<"$dhv2_db_check" || fail "Los cinco roles base no recibieron lectura de procedencia"
grep -q 'read_temporal_roles|5' <<<"$dhv2_db_check" || fail "Los cinco roles base no recibieron consulta temporal"
grep -q 'manage_provenance_roles|3' <<<"$dhv2_db_check" || fail "Los tres roles operativos no recibieron gestión de procedencia"
grep -q 'verify_provenance_roles|3' <<<"$dhv2_db_check" || fail "Los tres roles de control no recibieron verificación"
grep -q 'app_assets_update|t' <<<"$dhv2_db_check" || fail "La API no puede actualizar procedencia"
grep -q 'app_versions_update|f' <<<"$dhv2_db_check" || fail "La API no debe modificar versiones históricas"
grep -q 'app_versions_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar versiones históricas"
echo "$dhv2_db_check"
echo
echo "========== B5 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.12.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Procedencia y consulta temporal" /usr/share/nginx/html/assets >/dev/null && grep -R "Procedencia de datos" /usr/share/nginx/html/assets >/dev/null && grep -R "Consulta temporal" /usr/share/nginx/html/assets >/dev/null && grep -R "Reconstruir" /usr/share/nginx/html/assets >/dev/null'
echo "Procedencia, consulta temporal y footer v0.12.0: OK"
echo
echo "ACEPTACIÓN DE FASE B5: OK"
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== C1 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== C1 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.13.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.13.0"
grep -q '"phase":"C1"' <<<"$dhv2_internal" || fail "Health interno no informa C1"
grep -q '"version":"0.13.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.13.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== C1 · RUTAS PROTEGIDAS =========="
for dhv2_path in \
"survey-campaigns" \
"survey-campaigns/$dhv2_nil_uuid" \
"survey-campaigns/assignees"; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-c1-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" || fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-c1-response.json
echo
echo "========== C1 · MODELO Y PERMISOS =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'tables', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('survey_campaigns', 'survey_campaign_targets');
SELECT 'status_checks', COUNT(*)
FROM pg_constraint
WHERE conname IN ('chk_survey_campaigns_status', 'chk_survey_campaign_targets_status');
SELECT 'target_identity', COUNT(*)
FROM pg_constraint
WHERE conname = 'uq_survey_campaign_targets_campaign_asset';
SELECT 'asset_references', COUNT(*)
FROM pg_constraint
WHERE conname IN ('fk_survey_campaigns_scope_asset', 'fk_survey_campaign_targets_asset');
SELECT 'permissions', COUNT(*)
FROM permissions
WHERE code IN ('surveys.read', 'surveys.manage', 'surveys.assign', 'surveys.execute');
SELECT 'read_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'surveys.read';
SELECT 'manage_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor')
AND permission.code = 'surveys.manage';
SELECT 'assign_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor')
AND permission.code = 'surveys.assign';
SELECT 'execute_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor', 'inspector')
AND permission.code = 'surveys.execute';
SELECT 'app_campaigns_select', has_table_privilege(:'app_user', 'survey_campaigns', 'SELECT');
SELECT 'app_campaigns_update', has_table_privilege(:'app_user', 'survey_campaigns', 'UPDATE');
SELECT 'app_campaigns_delete', has_table_privilege(:'app_user', 'survey_campaigns', 'DELETE');
SELECT 'app_targets_update', has_table_privilege(:'app_user', 'survey_campaign_targets', 'UPDATE');
SELECT 'app_targets_delete', has_table_privilege(:'app_user', 'survey_campaign_targets', 'DELETE');
SQL
)"
grep -q 'tables|2' <<<"$dhv2_db_check" || fail "Faltan tablas de planificación"
grep -q 'status_checks|2' <<<"$dhv2_db_check" || fail "Faltan controles de estado"
grep -q 'target_identity|1' <<<"$dhv2_db_check" || fail "No se evita duplicar un activo en la campaña"
grep -q 'asset_references|2' <<<"$dhv2_db_check" || fail "La planificación no referencia correctamente al Maestro"
grep -q 'permissions|4' <<<"$dhv2_db_check" || fail "No se crearon los cuatro permisos C1"
grep -q 'read_roles|5' <<<"$dhv2_db_check" || fail "Los cinco roles base no recibieron lectura"
grep -q 'manage_roles|2' <<<"$dhv2_db_check" || fail "Admin y supervisor no recibieron gestión"
grep -q 'assign_roles|2' <<<"$dhv2_db_check" || fail "Admin y supervisor no recibieron asignación"
grep -q 'execute_roles|3' <<<"$dhv2_db_check" || fail "Faltan roles de ejecución"
grep -q 'app_campaigns_select|t' <<<"$dhv2_db_check" || fail "La API no puede leer campañas"
grep -q 'app_campaigns_update|t' <<<"$dhv2_db_check" || fail "La API no puede actualizar campañas"
grep -q 'app_campaigns_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar campañas físicamente"
grep -q 'app_targets_update|t' <<<"$dhv2_db_check" || fail "La API no puede actualizar objetivos"
grep -q 'app_targets_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar objetivos físicamente"
echo "$dhv2_db_check"
echo
echo "========== C1 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.13.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Planificación de relevamientos" /usr/share/nginx/html/assets >/dev/null && grep -R "Nueva campaña" /usr/share/nginx/html/assets >/dev/null && grep -R "Un único Maestro de Activos" /usr/share/nginx/html/assets >/dev/null'
echo "Campañas, Maestro único y footer v0.13.0: OK"
echo
echo "ACEPTACIÓN DE FASE C1: OK"
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== C2 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== C2 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.14.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.14.0"
grep -q '"phase":"C2"' <<<"$dhv2_internal" || fail "Health interno no informa C2"
grep -q '"version":"0.14.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.14.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== C2 · RUTA PROTEGIDA =========="
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-c2-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/survey-campaign-targets/$dhv2_nil_uuid/report")"
test "$dhv2_code" = "401" \
|| fail "/api/v3/survey-campaign-targets/:id/report devolvió HTTP $dhv2_code; se esperaba 401"
rm -f /tmp/dhv2-c2-response.json
echo "/api/v3/survey-campaign-targets/:id/report sin sesión: 401 OK"
echo
echo "========== C2 · MODELO Y PERMISOS =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'tables', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN (
'survey_target_reports',
'survey_target_report_media',
'survey_target_report_versions'
);
SELECT 'submitted_status', COUNT(*)
FROM pg_constraint
WHERE conname = 'chk_survey_campaign_targets_status'
AND pg_get_constraintdef(oid) LIKE '%SUBMITTED%';
SELECT 'report_checks', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'chk_survey_target_reports_outcome',
'chk_survey_target_reports_status',
'chk_survey_target_reports_coordinates',
'chk_survey_target_reports_accuracy',
'chk_survey_target_report_versions_event'
);
SELECT 'report_per_target', COUNT(*)
FROM pg_constraint
WHERE conname = 'uq_survey_target_reports_target_id';
SELECT 'immutable_version_identity', COUNT(*)
FROM pg_constraint
WHERE conname = 'uq_survey_target_report_versions_number';
SELECT 'permissions', COUNT(*)
FROM permissions
WHERE code IN ('surveys.read_reports', 'surveys.capture', 'surveys.review');
SELECT 'read_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'surveys.read_reports';
SELECT 'capture_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor', 'inspector')
AND permission.code = 'surveys.capture';
SELECT 'review_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor')
AND permission.code = 'surveys.review';
SELECT 'app_reports_select', has_table_privilege(:'app_user', 'survey_target_reports', 'SELECT');
SELECT 'app_reports_update', has_table_privilege(:'app_user', 'survey_target_reports', 'UPDATE');
SELECT 'app_reports_delete', has_table_privilege(:'app_user', 'survey_target_reports', 'DELETE');
SELECT 'app_versions_insert', has_table_privilege(:'app_user', 'survey_target_report_versions', 'INSERT');
SELECT 'app_versions_update', has_table_privilege(:'app_user', 'survey_target_report_versions', 'UPDATE');
SELECT 'app_versions_delete', has_table_privilege(:'app_user', 'survey_target_report_versions', 'DELETE');
SQL
)"
grep -q 'tables|3' <<<"$dhv2_db_check" || fail "Faltan tablas de ejecución"
grep -q 'submitted_status|1' <<<"$dhv2_db_check" || fail "El objetivo no admite el estado de revisión"
grep -q 'report_checks|5' <<<"$dhv2_db_check" || fail "Faltan controles de integridad"
grep -q 'report_per_target|1' <<<"$dhv2_db_check" || fail "No se garantiza un informe actual por objetivo"
grep -q 'immutable_version_identity|1' <<<"$dhv2_db_check" || fail "No se garantiza la identidad de versiones"
grep -q 'permissions|3' <<<"$dhv2_db_check" || fail "No se crearon los tres permisos C2"
grep -q 'read_roles|5' <<<"$dhv2_db_check" || fail "Faltan roles de lectura"
grep -q 'capture_roles|3' <<<"$dhv2_db_check" || fail "Faltan roles de captura"
grep -q 'review_roles|3' <<<"$dhv2_db_check" || fail "Faltan roles de revisión"
grep -q 'app_reports_select|t' <<<"$dhv2_db_check" || fail "La API no puede leer informes"
grep -q 'app_reports_update|t' <<<"$dhv2_db_check" || fail "La API no puede actualizar informes"
grep -q 'app_reports_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar informes físicamente"
grep -q 'app_versions_insert|t' <<<"$dhv2_db_check" || fail "La API no puede congelar versiones"
grep -q 'app_versions_update|f' <<<"$dhv2_db_check" || fail "La API no debe alterar versiones congeladas"
grep -q 'app_versions_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar versiones congeladas"
echo "$dhv2_db_check"
echo
echo "========== C2 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.14.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Ejecución y validación de relevamientos" /usr/share/nginx/html/assets >/dev/null && grep -R "Informe de campo" /usr/share/nginx/html/assets >/dev/null && grep -R "Capturar GPS actual" /usr/share/nginx/html/assets >/dev/null && grep -R "VERSIONES INMUTABLES" /usr/share/nginx/html/assets >/dev/null'
echo "Ejecución, GPS, versiones inmutables y footer v0.14.0: OK"
echo
echo "ACEPTACIÓN DE FASE C2: OK"
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== D1 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== D1 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.15.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.15.0"
grep -q '"phase":"D1"' <<<"$dhv2_internal" || fail "Health interno no informa D1"
grep -q '"version":"0.15.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.15.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== D1 · RUTAS PROTEGIDAS =========="
for dhv2_path in \
"inspection-visits" \
"inspection-visits/$dhv2_nil_uuid" \
"inspection-visits/assignees"; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-d1-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" \
|| fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-d1-response.json
echo
echo "========== D1 · MODELO Y PERMISOS =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'tables', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN (
'inspection_visits',
'inspection_visit_assets',
'inspection_visit_members'
);
SELECT 'visit_checks', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'chk_inspection_visits_code',
'chk_inspection_visits_status',
'chk_inspection_visits_planned_dates',
'chk_inspection_visits_actual_dates',
'chk_inspection_visits_cancellation'
);
SELECT 'closed_reserved', COUNT(*)
FROM pg_constraint
WHERE conname = 'chk_inspection_visits_status'
AND pg_get_constraintdef(oid) LIKE '%CLOSED%';
SELECT 'visit_identity', COUNT(*)
FROM pg_constraint
WHERE conname = 'uq_inspection_visits_code';
SELECT 'asset_references', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'fk_inspection_visits_scope_asset',
'fk_inspection_visit_assets_asset'
);
SELECT 'team_references', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'fk_inspection_visits_lead_inspector',
'fk_inspection_visit_members_user'
);
SELECT 'permissions', COUNT(*)
FROM permissions
WHERE code IN (
'inspections.read',
'inspections.manage',
'inspections.assign',
'inspections.execute'
);
SELECT 'read_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'inspections.read';
SELECT 'manage_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor')
AND permission.code = 'inspections.manage';
SELECT 'assign_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor')
AND permission.code = 'inspections.assign';
SELECT 'execute_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor', 'inspector')
AND permission.code = 'inspections.execute';
SELECT 'app_visits_select', has_table_privilege(:'app_user', 'inspection_visits', 'SELECT');
SELECT 'app_visits_update', has_table_privilege(:'app_user', 'inspection_visits', 'UPDATE');
SELECT 'app_visits_delete', has_table_privilege(:'app_user', 'inspection_visits', 'DELETE');
SELECT 'app_assets_delete', has_table_privilege(:'app_user', 'inspection_visit_assets', 'DELETE');
SELECT 'app_members_delete', has_table_privilege(:'app_user', 'inspection_visit_members', 'DELETE');
SQL
)"
grep -q 'tables|3' <<<"$dhv2_db_check" || fail "Faltan tablas de visitas"
grep -q 'visit_checks|5' <<<"$dhv2_db_check" || fail "Faltan controles de integridad"
grep -q 'closed_reserved|1' <<<"$dhv2_db_check" || fail "No quedó reservado el cierre documental"
grep -q 'visit_identity|1' <<<"$dhv2_db_check" || fail "No se garantiza la identidad de visita"
grep -q 'asset_references|2' <<<"$dhv2_db_check" || fail "La visita no referencia correctamente al Maestro"
grep -q 'team_references|2' <<<"$dhv2_db_check" || fail "El equipo no referencia usuarios existentes"
grep -q 'permissions|4' <<<"$dhv2_db_check" || fail "No se crearon los cuatro permisos D1"
grep -q 'read_roles|5' <<<"$dhv2_db_check" || fail "Faltan roles de lectura"
grep -q 'manage_roles|2' <<<"$dhv2_db_check" || fail "Faltan roles de gestión"
grep -q 'assign_roles|2' <<<"$dhv2_db_check" || fail "Faltan roles de asignación"
grep -q 'execute_roles|3' <<<"$dhv2_db_check" || fail "Faltan roles de ejecución"
grep -q 'app_visits_select|t' <<<"$dhv2_db_check" || fail "La API no puede leer visitas"
grep -q 'app_visits_update|t' <<<"$dhv2_db_check" || fail "La API no puede actualizar visitas"
grep -q 'app_visits_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar visitas físicamente"
grep -q 'app_assets_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar vínculos a activos"
grep -q 'app_members_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar vínculos de equipo"
echo "$dhv2_db_check"
echo
echo "========== D1 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.15.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Planificación y apertura de visitas" /usr/share/nginx/html/assets >/dev/null && grep -R "Una inspección es una visita" /usr/share/nginx/html/assets >/dev/null && grep -R "Nueva visita" /usr/share/nginx/html/assets >/dev/null && grep -R "ACTIVOS A INSPECCIONAR" /usr/share/nginx/html/assets >/dev/null'
echo "Visitas, Maestro único, equipo y footer v0.15.0: OK"
echo
echo "ACEPTACIÓN DE FASE D1: OK"
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== D2.1 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== D2.1 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.16.1"' <<<"$dhv2_internal" || fail "Health interno no informa v0.16.1"
grep -q '"phase":"D2.1"' <<<"$dhv2_internal" || fail "Health interno no informa D2.1"
grep -q '"version":"0.16.1"' <<<"$dhv2_external" || fail "Health externo no informa v0.16.1"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== D2.1 · RUTAS PROTEGIDAS =========="
for dhv2_path in \
"inspection-visits/$dhv2_nil_uuid/acts" \
"inspection-acts/$dhv2_nil_uuid"; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-d2-1-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" \
|| fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-d2-1-response.json
echo
echo "========== D2.1 · UN ACTA POR VISITA =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -At' <<'SQL'
SELECT 'unique_visit', COUNT(*)
FROM pg_constraint
WHERE conname = 'uq_inspection_acts_visit'
AND contype = 'u';
SELECT 'duplicate_visits', COUNT(*)
FROM (
SELECT visit_id
FROM inspection_acts
GROUP BY visit_id
HAVING COUNT(*) > 1
) duplicate;
SELECT 'd2_tables', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN (
'document_annual_sequences',
'inspection_acts',
'inspection_act_assets',
'inspection_act_versions'
);
SELECT 'permissions', COUNT(*)
FROM permissions
WHERE code IN (
'inspection_acts.read',
'inspection_acts.create',
'inspection_acts.update',
'inspection_acts.cancel'
);
SELECT 'read_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'inspection_acts.read';
SELECT 'write_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor', 'inspector')
AND permission.code IN ('inspection_acts.create', 'inspection_acts.update');
SELECT 'cancel_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor')
AND permission.code = 'inspection_acts.cancel';
SQL
)"
grep -q 'unique_visit|1' <<<"$dhv2_db_check" || fail "No se garantiza un acta por visita"
grep -q 'duplicate_visits|0' <<<"$dhv2_db_check" || fail "Existen visitas con más de un acta"
grep -q 'd2_tables|4' <<<"$dhv2_db_check" || fail "Faltan tablas base de actas"
grep -q 'permissions|4' <<<"$dhv2_db_check" || fail "Faltan permisos de actas"
grep -q 'read_roles|5' <<<"$dhv2_db_check" || fail "Faltan roles de lectura"
grep -q 'write_roles|6' <<<"$dhv2_db_check" || fail "Faltan roles de creación o edición"
grep -q 'cancel_roles|2' <<<"$dhv2_db_check" || fail "Faltan roles de cancelación"
echo "$dhv2_db_check"
echo
echo "========== D2.1 · PRIVILEGIOS RUNTIME =========="
dhv2_privileges="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'acts_select', has_table_privilege(:'app_user', 'inspection_acts', 'SELECT');
SELECT 'acts_update', has_table_privilege(:'app_user', 'inspection_acts', 'UPDATE');
SELECT 'acts_delete', has_table_privilege(:'app_user', 'inspection_acts', 'DELETE');
SELECT 'versions_insert', has_table_privilege(:'app_user', 'inspection_act_versions', 'INSERT');
SELECT 'versions_update', has_table_privilege(:'app_user', 'inspection_act_versions', 'UPDATE');
SELECT 'versions_delete', has_table_privilege(:'app_user', 'inspection_act_versions', 'DELETE');
SQL
)"
grep -q 'acts_select|t' <<<"$dhv2_privileges" || fail "La API no puede leer actas"
grep -q 'acts_update|t' <<<"$dhv2_privileges" || fail "La API no puede actualizar actas"
grep -q 'acts_delete|f' <<<"$dhv2_privileges" || fail "La API no debe borrar actas"
grep -q 'versions_insert|t' <<<"$dhv2_privileges" || fail "La API no puede crear versiones"
grep -q 'versions_update|f' <<<"$dhv2_privileges" || fail "La API no debe modificar versiones"
grep -q 'versions_delete|f' <<<"$dhv2_privileges" || fail "La API no debe borrar versiones"
echo "$dhv2_privileges"
echo
echo "========== D2.1 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.16.1" /usr/share/nginx/html/assets >/dev/null && grep -R "Un acta por visita" /usr/share/nginx/html/assets >/dev/null && grep -R "Acta única" /usr/share/nginx/html/assets >/dev/null && grep -R "Crear acta" /usr/share/nginx/html/assets >/dev/null'
echo "Acta única y footer v0.16.1: OK"
echo
echo "ACEPTACIÓN DE FASE D2.1: OK"
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== D2 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== D2 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.16.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.16.0"
grep -q '"phase":"D2"' <<<"$dhv2_internal" || fail "Health interno no informa D2"
grep -q '"version":"0.16.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.16.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== D2 · RUTAS PROTEGIDAS =========="
for dhv2_path in \
"inspection-visits/$dhv2_nil_uuid/acts" \
"inspection-acts/$dhv2_nil_uuid"; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-d2-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" \
|| fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-d2-response.json
echo
echo "========== D2 · MODELO, ROLES Y PRIVILEGIOS =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'tables', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN (
'document_annual_sequences',
'inspection_acts',
'inspection_act_assets',
'inspection_act_versions'
);
SELECT 'act_checks', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'chk_inspection_acts_year',
'chk_inspection_acts_number',
'chk_inspection_acts_code',
'chk_inspection_acts_status',
'chk_inspection_acts_current_version',
'chk_inspection_acts_cancellation'
);
SELECT 'annual_identity', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'uq_inspection_acts_year_number',
'uq_inspection_acts_code'
);
SELECT 'reserved_states', COUNT(*)
FROM pg_constraint
WHERE conname = 'chk_inspection_acts_status'
AND pg_get_constraintdef(oid) LIKE '%READY%'
AND pg_get_constraintdef(oid) LIKE '%CLOSED%'
AND pg_get_constraintdef(oid) LIKE '%RECTIFIED%';
SELECT 'sequence_types', COUNT(*)
FROM pg_constraint
WHERE conname = 'chk_document_annual_sequences_type'
AND pg_get_constraintdef(oid) LIKE '%ACT%'
AND pg_get_constraintdef(oid) LIKE '%REPORT%';
SELECT 'immutable_versions', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'uq_inspection_act_versions_number',
'chk_inspection_act_versions_number',
'chk_inspection_act_versions_event'
);
SELECT 'permissions', COUNT(*)
FROM permissions
WHERE code IN (
'inspection_acts.read',
'inspection_acts.create',
'inspection_acts.update',
'inspection_acts.cancel'
);
SELECT 'read_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'inspection_acts.read';
SELECT 'create_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor', 'inspector')
AND permission.code = 'inspection_acts.create';
SELECT 'update_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor', 'inspector')
AND permission.code = 'inspection_acts.update';
SELECT 'cancel_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor')
AND permission.code = 'inspection_acts.cancel';
SELECT 'app_acts_select', has_table_privilege(:'app_user', 'inspection_acts', 'SELECT');
SELECT 'app_acts_update', has_table_privilege(:'app_user', 'inspection_acts', 'UPDATE');
SELECT 'app_acts_delete', has_table_privilege(:'app_user', 'inspection_acts', 'DELETE');
SELECT 'app_versions_insert', has_table_privilege(:'app_user', 'inspection_act_versions', 'INSERT');
SELECT 'app_versions_update', has_table_privilege(:'app_user', 'inspection_act_versions', 'UPDATE');
SELECT 'app_versions_delete', has_table_privilege(:'app_user', 'inspection_act_versions', 'DELETE');
SELECT 'app_sequence_update', has_table_privilege(:'app_user', 'document_annual_sequences', 'UPDATE');
SELECT 'app_sequence_delete', has_table_privilege(:'app_user', 'document_annual_sequences', 'DELETE');
SQL
)"
grep -q 'tables|4' <<<"$dhv2_db_check" || fail "Faltan tablas D2"
grep -q 'act_checks|6' <<<"$dhv2_db_check" || fail "Faltan controles del acta"
grep -q 'annual_identity|2' <<<"$dhv2_db_check" || fail "No se garantiza la identidad anual"
grep -q 'reserved_states|1' <<<"$dhv2_db_check" || fail "Faltan estados documentales reservados"
grep -q 'sequence_types|1' <<<"$dhv2_db_check" || fail "La secuencia no reserva ACT y REPORT"
grep -q 'immutable_versions|3' <<<"$dhv2_db_check" || fail "Faltan controles de versiones"
grep -q 'permissions|4' <<<"$dhv2_db_check" || fail "No se crearon los cuatro permisos D2"
grep -q 'read_roles|5' <<<"$dhv2_db_check" || fail "Faltan roles de lectura"
grep -q 'create_roles|3' <<<"$dhv2_db_check" || fail "Faltan roles de creación"
grep -q 'update_roles|3' <<<"$dhv2_db_check" || fail "Faltan roles de actualización"
grep -q 'cancel_roles|2' <<<"$dhv2_db_check" || fail "Faltan roles de cancelación"
grep -q 'app_acts_select|t' <<<"$dhv2_db_check" || fail "La API no puede leer actas"
grep -q 'app_acts_update|t' <<<"$dhv2_db_check" || fail "La API no puede actualizar actas"
grep -q 'app_acts_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar actas"
grep -q 'app_versions_insert|t' <<<"$dhv2_db_check" || fail "La API no puede registrar versiones"
grep -q 'app_versions_update|f' <<<"$dhv2_db_check" || fail "La API no debe modificar versiones"
grep -q 'app_versions_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar versiones"
grep -q 'app_sequence_update|t' <<<"$dhv2_db_check" || fail "La API no puede incrementar la secuencia"
grep -q 'app_sequence_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar secuencias"
echo "$dhv2_db_check"
echo
echo "========== D2 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.16.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Múltiples actas por visita" /usr/share/nginx/html/assets >/dev/null && grep -R "Nueva acta" /usr/share/nginx/html/assets >/dev/null && grep -R "VERSIONES INMUTABLES" /usr/share/nginx/html/assets >/dev/null && grep -R "Crear y numerar acta" /usr/share/nginx/html/assets >/dev/null'
echo "Actas múltiples, versiones y footer v0.16.0: OK"
echo
echo "ACEPTACIÓN DE FASE D2: OK"
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== D3 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== D3 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.17.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.17.0"
grep -q '"phase":"D3"' <<<"$dhv2_internal" || fail "Health interno no informa D3"
grep -q '"version":"0.17.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.17.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== D3 · RUTAS PROTEGIDAS =========="
for dhv2_path in \
"finding-catalog" \
"inspection-acts/$dhv2_nil_uuid/findings" \
"inspection-findings/$dhv2_nil_uuid" \
"dashboard/summary"; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-d3-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" \
|| fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-d3-response.json
echo
echo "========== D3 · MODELO Y CATÁLOGO =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -At' <<'SQL'
SELECT 'tables', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN (
'finding_categories',
'finding_catalog_items',
'inspection_findings',
'inspection_finding_versions'
);
SELECT 'categories', COUNT(*) FROM finding_categories WHERE is_active = true;
SELECT 'catalog_items', COUNT(*) FROM finding_catalog_items WHERE is_active = true;
SELECT 'normalized_row', COUNT(*)
FROM finding_catalog_items
WHERE code = 'BOMBAS-003'
AND title = 'PUESTA A TIERRA'
AND import_note IS NOT NULL;
SELECT 'finding_checks', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'chk_inspection_findings_number',
'chk_inspection_findings_code',
'chk_inspection_findings_status',
'chk_inspection_findings_content',
'chk_inspection_findings_response',
'chk_inspection_findings_commitment',
'chk_inspection_findings_closure'
);
SELECT 'open_state', COUNT(*)
FROM pg_constraint
WHERE conname = 'chk_inspection_findings_status'
AND pg_get_constraintdef(oid) LIKE '%OPEN%'
AND pg_get_constraintdef(oid) LIKE '%CLOSED%';
SELECT 'immutable_versions', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'uq_inspection_finding_versions_number',
'chk_inspection_finding_versions_number',
'chk_inspection_finding_versions_event'
);
SELECT 'permissions', COUNT(*)
FROM permissions
WHERE code IN (
'finding_catalog.read',
'finding_catalog.manage',
'inspection_findings.read',
'inspection_findings.create',
'inspection_findings.update',
'inspection_findings.follow_up'
);
SELECT 'catalog_read_roles', COUNT(*)
FROM role_permissions rp
INNER JOIN roles role ON role.id = rp.role_id
INNER JOIN permissions permission ON permission.id = rp.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'finding_catalog.read';
SELECT 'catalog_manage_roles', COUNT(*)
FROM role_permissions rp
INNER JOIN roles role ON role.id = rp.role_id
INNER JOIN permissions permission ON permission.id = rp.permission_id
WHERE role.code IN ('admin', 'supervisor')
AND permission.code = 'finding_catalog.manage';
SELECT 'finding_read_roles', COUNT(*)
FROM role_permissions rp
INNER JOIN roles role ON role.id = rp.role_id
INNER JOIN permissions permission ON permission.id = rp.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'inspection_findings.read';
SELECT 'finding_create_roles', COUNT(*)
FROM role_permissions rp
INNER JOIN roles role ON role.id = rp.role_id
INNER JOIN permissions permission ON permission.id = rp.permission_id
WHERE role.code IN ('admin', 'supervisor', 'inspector')
AND permission.code = 'inspection_findings.create';
SELECT 'finding_update_roles', COUNT(*)
FROM role_permissions rp
INNER JOIN roles role ON role.id = rp.role_id
INNER JOIN permissions permission ON permission.id = rp.permission_id
WHERE role.code IN ('admin', 'supervisor', 'inspector')
AND permission.code = 'inspection_findings.update';
SELECT 'follow_up_roles', COUNT(*)
FROM role_permissions rp
INNER JOIN roles role ON role.id = rp.role_id
INNER JOIN permissions permission ON permission.id = rp.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector')
AND permission.code = 'inspection_findings.follow_up';
SQL
)"
grep -q 'tables|4' <<<"$dhv2_db_check" || fail "Faltan tablas D3"
grep -q 'categories|19' <<<"$dhv2_db_check" || fail "El catálogo no tiene 19 categorías"
grep -q 'catalog_items|170' <<<"$dhv2_db_check" || fail "El catálogo no tiene 170 modelos"
grep -q 'normalized_row|1' <<<"$dhv2_db_check" || fail "No se registró la normalización BOMBAS-003"
grep -q 'finding_checks|7' <<<"$dhv2_db_check" || fail "Faltan restricciones de hallazgos"
grep -q 'open_state|1' <<<"$dhv2_db_check" || fail "No está definido el estado abierto"
grep -q 'immutable_versions|3' <<<"$dhv2_db_check" || fail "Faltan controles de versiones"
grep -q 'permissions|6' <<<"$dhv2_db_check" || fail "Faltan permisos D3"
grep -q 'catalog_read_roles|5' <<<"$dhv2_db_check" || fail "Faltan roles de lectura de catálogo"
grep -q 'catalog_manage_roles|2' <<<"$dhv2_db_check" || fail "Faltan roles de gestión de catálogo"
grep -q 'finding_read_roles|5' <<<"$dhv2_db_check" || fail "Faltan roles de lectura de hallazgos"
grep -q 'finding_create_roles|3' <<<"$dhv2_db_check" || fail "Faltan roles de creación de hallazgos"
grep -q 'finding_update_roles|3' <<<"$dhv2_db_check" || fail "Faltan roles de actualización de hallazgos"
grep -q 'follow_up_roles|4' <<<"$dhv2_db_check" || fail "Faltan roles de seguimiento"
echo "$dhv2_db_check"
echo
echo "========== D3 · PRIVILEGIOS RUNTIME =========="
dhv2_privileges="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'catalog_select', has_table_privilege(:'app_user', 'finding_catalog_items', 'SELECT');
SELECT 'catalog_update', has_table_privilege(:'app_user', 'finding_catalog_items', 'UPDATE');
SELECT 'catalog_delete', has_table_privilege(:'app_user', 'finding_catalog_items', 'DELETE');
SELECT 'findings_insert', has_table_privilege(:'app_user', 'inspection_findings', 'INSERT');
SELECT 'findings_update', has_table_privilege(:'app_user', 'inspection_findings', 'UPDATE');
SELECT 'findings_delete', has_table_privilege(:'app_user', 'inspection_findings', 'DELETE');
SELECT 'versions_insert', has_table_privilege(:'app_user', 'inspection_finding_versions', 'INSERT');
SELECT 'versions_update', has_table_privilege(:'app_user', 'inspection_finding_versions', 'UPDATE');
SELECT 'versions_delete', has_table_privilege(:'app_user', 'inspection_finding_versions', 'DELETE');
SQL
)"
grep -q 'catalog_select|t' <<<"$dhv2_privileges" || fail "La API no puede leer el catálogo"
grep -q 'catalog_update|t' <<<"$dhv2_privileges" || fail "La API no puede administrar el catálogo"
grep -q 'catalog_delete|f' <<<"$dhv2_privileges" || fail "La API no debe borrar el catálogo"
grep -q 'findings_insert|t' <<<"$dhv2_privileges" || fail "La API no puede crear hallazgos"
grep -q 'findings_update|t' <<<"$dhv2_privileges" || fail "La API no puede actualizar hallazgos"
grep -q 'findings_delete|f' <<<"$dhv2_privileges" || fail "La API no debe borrar hallazgos"
grep -q 'versions_insert|t' <<<"$dhv2_privileges" || fail "La API no puede crear versiones"
grep -q 'versions_update|f' <<<"$dhv2_privileges" || fail "La API no debe modificar versiones"
grep -q 'versions_delete|f' <<<"$dhv2_privileges" || fail "La API no debe borrar versiones"
echo "$dhv2_privileges"
echo
echo "========== D3 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.17.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Hallazgos y seguimiento" /usr/share/nginx/html/assets >/dev/null && grep -R "Múltiples hallazgos" /usr/share/nginx/html/assets >/dev/null && grep -R "Respuesta de la empresa" /usr/share/nginx/html/assets >/dev/null && grep -R "Próximos controles de hallazgos" /usr/share/nginx/html/assets >/dev/null'
echo "Hallazgos, seguimiento, dashboard y footer v0.17.0: OK"
echo
echo "ACEPTACIÓN DE FASE D3: OK"
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== D4 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== D4 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.18.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.18.0"
grep -q '"phase":"D4"' <<<"$dhv2_internal" || fail "Health interno no informa D4"
grep -q '"version":"0.18.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.18.0"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== D4 · RUTAS PROTEGIDAS =========="
for dhv2_path in \
"inspection-findings/$dhv2_nil_uuid/evidence" \
"inspection-findings/$dhv2_nil_uuid/communications" \
"inspection-finding-evidence/$dhv2_nil_uuid/content"; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-d4-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" \
|| fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-d4-response.json
echo
echo "========== D4 · MODELO INMUTABLE =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -At' <<'SQL'
SELECT 'tables', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN (
'inspection_finding_evidence',
'inspection_finding_communications'
);
SELECT 'communication_checks', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'chk_inspection_finding_communications_direction',
'chk_inspection_finding_communications_channel',
'chk_inspection_finding_communications_type',
'chk_inspection_finding_communications_subject',
'chk_inspection_finding_company_response_direction'
);
SELECT 'evidence_checks', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'chk_inspection_finding_evidence_kind',
'chk_inspection_finding_evidence_purpose',
'chk_inspection_finding_evidence_source',
'chk_inspection_finding_evidence_mime',
'chk_inspection_finding_evidence_size',
'chk_inspection_finding_evidence_sha256',
'chk_inspection_finding_evidence_coordinates',
'chk_inspection_finding_evidence_accuracy',
'chk_inspection_finding_evidence_kind_mime',
'chk_inspection_finding_company_response_pdf',
'chk_inspection_finding_attachment_link'
);
SELECT 'protected_links', COUNT(*)
FROM pg_constraint
WHERE conname IN (
'uq_inspection_finding_communications_id_finding',
'fk_inspection_finding_evidence_communication',
'uq_inspection_finding_evidence_stored_name'
);
SELECT 'permissions', COUNT(*)
FROM permissions
WHERE code IN (
'inspection_evidence.read',
'inspection_evidence.create',
'inspection_communications.read',
'inspection_communications.create'
);
SELECT 'evidence_read_roles', COUNT(*)
FROM role_permissions rp
INNER JOIN roles role ON role.id = rp.role_id
INNER JOIN permissions permission ON permission.id = rp.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'inspection_evidence.read';
SELECT 'evidence_create_roles', COUNT(*)
FROM role_permissions rp
INNER JOIN roles role ON role.id = rp.role_id
INNER JOIN permissions permission ON permission.id = rp.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector')
AND permission.code = 'inspection_evidence.create';
SELECT 'communication_read_roles', COUNT(*)
FROM role_permissions rp
INNER JOIN roles role ON role.id = rp.role_id
INNER JOIN permissions permission ON permission.id = rp.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
AND permission.code = 'inspection_communications.read';
SELECT 'communication_create_roles', COUNT(*)
FROM role_permissions rp
INNER JOIN roles role ON role.id = rp.role_id
INNER JOIN permissions permission ON permission.id = rp.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector')
AND permission.code = 'inspection_communications.create';
SQL
)"
grep -q 'tables|2' <<<"$dhv2_db_check" || fail "Faltan tablas D4"
grep -q 'communication_checks|5' <<<"$dhv2_db_check" || fail "Faltan controles de comunicaciones"
grep -q 'evidence_checks|11' <<<"$dhv2_db_check" || fail "Faltan controles de evidencias"
grep -q 'protected_links|3' <<<"$dhv2_db_check" || fail "Faltan vínculos protegidos"
grep -q 'permissions|4' <<<"$dhv2_db_check" || fail "Faltan permisos D4"
grep -q 'evidence_read_roles|5' <<<"$dhv2_db_check" || fail "Faltan roles de lectura de evidencias"
grep -q 'evidence_create_roles|4' <<<"$dhv2_db_check" || fail "Faltan roles de carga de evidencias"
grep -q 'communication_read_roles|5' <<<"$dhv2_db_check" || fail "Faltan roles de lectura de comunicaciones"
grep -q 'communication_create_roles|4' <<<"$dhv2_db_check" || fail "Faltan roles de carga de comunicaciones"
echo "$dhv2_db_check"
echo
echo "========== D4 · PRIVILEGIOS RUNTIME =========="
dhv2_privileges="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'evidence_select', has_table_privilege(:'app_user', 'inspection_finding_evidence', 'SELECT');
SELECT 'evidence_insert', has_table_privilege(:'app_user', 'inspection_finding_evidence', 'INSERT');
SELECT 'evidence_update', has_table_privilege(:'app_user', 'inspection_finding_evidence', 'UPDATE');
SELECT 'evidence_delete', has_table_privilege(:'app_user', 'inspection_finding_evidence', 'DELETE');
SELECT 'communications_select', has_table_privilege(:'app_user', 'inspection_finding_communications', 'SELECT');
SELECT 'communications_insert', has_table_privilege(:'app_user', 'inspection_finding_communications', 'INSERT');
SELECT 'communications_update', has_table_privilege(:'app_user', 'inspection_finding_communications', 'UPDATE');
SELECT 'communications_delete', has_table_privilege(:'app_user', 'inspection_finding_communications', 'DELETE');
SQL
)"
grep -q 'evidence_select|t' <<<"$dhv2_privileges" || fail "La API no puede leer evidencias"
grep -q 'evidence_insert|t' <<<"$dhv2_privileges" || fail "La API no puede crear evidencias"
grep -q 'evidence_update|f' <<<"$dhv2_privileges" || fail "La API no debe modificar evidencias"
grep -q 'evidence_delete|f' <<<"$dhv2_privileges" || fail "La API no debe borrar evidencias"
grep -q 'communications_select|t' <<<"$dhv2_privileges" || fail "La API no puede leer comunicaciones"
grep -q 'communications_insert|t' <<<"$dhv2_privileges" || fail "La API no puede crear comunicaciones"
grep -q 'communications_update|f' <<<"$dhv2_privileges" || fail "La API no debe modificar comunicaciones"
grep -q 'communications_delete|f' <<<"$dhv2_privileges" || fail "La API no debe borrar comunicaciones"
echo "$dhv2_privileges"
echo
echo "========== D4 · ALMACENAMIENTO =========="
docker compose exec -T api sh -lc \
'test -d "$ASSET_MEDIA_ROOT" && test -w "$ASSET_MEDIA_ROOT"'
echo "Volumen persistente protegido y escribible: OK"
echo
echo "========== D4 · FRONTEND =========="
docker compose exec -T web sh -lc \
'grep -R "0.18.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Evidencias y comunicaciones" /usr/share/nginx/html/assets >/dev/null && grep -R "PDF de respuesta de la empresa" /usr/share/nginx/html/assets >/dev/null && grep -R "Historial de comunicaciones" /usr/share/nginx/html/assets >/dev/null && grep -R "SHA-256" /usr/share/nginx/html/assets >/dev/null'
echo "Evidencias, comunicaciones y footer v0.18.0: OK"
echo
echo "ACEPTACIÓN DE FASE D4: OK"
+141
View File
@@ -0,0 +1,141 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== D5.1 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|| fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== D5.1 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.19.1"' <<<"$dhv2_internal" || fail "Health interno no informa v0.19.1"
grep -q '"phase":"D5.1"' <<<"$dhv2_internal" || fail "Health interno no informa D5.1"
grep -q '"version":"0.19.1"' <<<"$dhv2_external" || fail "Health externo no informa v0.19.1"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== D5.1 · RUTAS PROTEGIDAS =========="
dhv2_post_paths=(
"inspection-visits/$dhv2_nil_uuid/start"
"inspection-visits/$dhv2_nil_uuid/acts"
"inspection-acts/$dhv2_nil_uuid/findings"
"inspection-acts/$dhv2_nil_uuid/ready"
"inspection-acts/$dhv2_nil_uuid/close"
)
for dhv2_path in "${dhv2_post_paths[@]}"; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-X POST -H 'Content-Type: application/json' -d '{}' \
-o /tmp/dhv2-d5-1-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" \
|| fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
rm -f /tmp/dhv2-d5-1-response.json
echo
echo "========== D5.1 · POLÍTICA DE ROLES =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -At' <<'SQL'
WITH operational(code) AS (VALUES
('inspections.execute'),
('inspection_acts.create'),
('inspection_acts.update'),
('inspection_acts.cancel'),
('inspection_findings.create'),
('inspection_findings.update'),
('inspection_closure.prepare'),
('inspection_closure.sign'),
('inspection_closure.close')
)
SELECT 'operational_permissions', COUNT(*)
FROM permissions permission
INNER JOIN operational ON operational.code = permission.code;
WITH operational(code) AS (VALUES
('inspections.execute'),
('inspection_acts.create'),
('inspection_acts.update'),
('inspection_acts.cancel'),
('inspection_findings.create'),
('inspection_findings.update'),
('inspection_closure.prepare'),
('inspection_closure.sign'),
('inspection_closure.close')
)
SELECT 'inspector_grants', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
INNER JOIN operational ON operational.code = permission.code
WHERE role.code = 'inspector';
WITH operational(code) AS (VALUES
('inspections.execute'),
('inspection_acts.create'),
('inspection_acts.update'),
('inspection_acts.cancel'),
('inspection_findings.create'),
('inspection_findings.update'),
('inspection_closure.prepare'),
('inspection_closure.sign'),
('inspection_closure.close')
)
SELECT 'non_inspector_grants', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
INNER JOIN operational ON operational.code = permission.code
WHERE role.code <> 'inspector';
SELECT 'follow_up_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector')
AND permission.code = 'inspection_findings.follow_up';
SELECT 'migration_applied', COUNT(*)
FROM typeorm_migrations
WHERE name = 'PhaseD51MobileInspectionPolicy1787331600000';
SQL
)"
grep -q 'operational_permissions|9' <<<"$dhv2_db_check" || fail "No se encontraron los nueve permisos operativos"
grep -q 'inspector_grants|9' <<<"$dhv2_db_check" || fail "El inspector no tiene los nueve permisos operativos"
grep -q 'non_inspector_grants|0' <<<"$dhv2_db_check" || fail "Un rol no inspector conserva permisos operativos"
grep -q 'follow_up_roles|4' <<<"$dhv2_db_check" || fail "Se alteró el seguimiento posterior del dashboard"
grep -q 'migration_applied|1' <<<"$dhv2_db_check" || fail "La migración D5.1 no figura aplicada"
echo "$dhv2_db_check"
echo
echo "========== D5.1 · BLOQUEO EN API =========="
docker compose exec -T api sh -lc \
'grep -R "INSPECTION_MOBILE_APP_REQUIRED" /app/dist >/dev/null && grep -R "INSPECTION_INSPECTOR_ROLE_REQUIRED" /app/dist >/dev/null'
echo "Bearer móvil + rol inspector exigidos en runtime: OK"
echo
echo "========== D5.1 · DASHBOARD =========="
docker compose exec -T web sh -lc \
'grep -R "0.19.1" /usr/share/nginx/html/assets >/dev/null && grep -R "Operación exclusiva APK" /usr/share/nginx/html/assets >/dev/null && grep -R "El inspector asignado debe iniciar la visita desde la APK" /usr/share/nginx/html/assets >/dev/null && grep -R "Actas y hallazgos se registran desde la APK" /usr/share/nginx/html/assets >/dev/null && grep -R "respuesta de la empresa, PDF y próximo control" /usr/share/nginx/html/assets >/dev/null'
echo "Dashboard administrativo y footer v0.19.1: OK"
echo
echo "========== D5.1 · CONTENEDORES =========="
docker compose ps
echo
echo "ACEPTACIÓN DE FASE D5.1: OK"
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
fail() {
echo "ERROR: $*" >&2
exit 1
}
echo "========== D5.2 · MIGRACIONES =========="
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" || fail "Quedaron migraciones pendientes"
echo "Migraciones pendientes: no"
echo
echo "========== D5.2 · HEALTH =========="
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
grep -q '"version":"0.19.2"' <<<"$dhv2_internal" || fail "Health interno no informa v0.19.2"
grep -q '"phase":"D5.2"' <<<"$dhv2_internal" || fail "Health interno no informa D5.2"
grep -q '"version":"0.19.2"' <<<"$dhv2_external" || fail "Health externo no informa v0.19.2"
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
echo "$dhv2_external"
echo
echo "========== D5.2 · RUTA ADMINISTRATIVA PROTEGIDA =========="
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o /tmp/dhv2-d5-2-response.json -w '%{http_code}' \
"$dhv2_base_url/api/v3/finding-catalog/admin")"
test "$dhv2_code" = "401" || fail "/api/v3/finding-catalog/admin devolvió HTTP $dhv2_code; se esperaba 401"
rm -f /tmp/dhv2-d5-2-response.json
echo "/api/v3/finding-catalog/admin sin sesión: 401 OK"
echo
echo "========== D5.2 · CATÁLOGO Y REVISIONES =========="
dhv2_db_check="$(docker compose exec -T db sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
SELECT 'categories_baseline', COUNT(*) >= 19 FROM finding_categories;
SELECT 'catalog_items_baseline', COUNT(*) >= 170 FROM finding_catalog_items;
SELECT 'versions_table', COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'finding_catalog_item_versions';
SELECT 'version_rows_match', NOT EXISTS (
SELECT 1
FROM finding_catalog_items item
WHERE NOT EXISTS (
SELECT 1 FROM finding_catalog_item_versions version
WHERE version.item_id = item.id AND version.revision = item.revision
)
);
SELECT 'revision_constraints', COUNT(*)
FROM information_schema.table_constraints
WHERE table_schema = 'public'
AND table_name = 'finding_catalog_item_versions'
AND constraint_name IN (
'uq_finding_catalog_item_versions_revision',
'chk_finding_catalog_item_versions_revision',
'chk_finding_catalog_item_versions_snapshot',
'fk_finding_catalog_item_versions_item',
'fk_finding_catalog_item_versions_actor'
);
SELECT 'catalog_manage_roles', COUNT(*)
FROM role_permissions role_permission
INNER JOIN roles role ON role.id = role_permission.role_id
INNER JOIN permissions permission ON permission.id = role_permission.permission_id
WHERE role.code IN ('admin', 'supervisor')
AND permission.code = 'finding_catalog.manage';
SELECT 'version_select', has_table_privilege(:'app_user', 'finding_catalog_item_versions', 'SELECT');
SELECT 'version_insert', has_table_privilege(:'app_user', 'finding_catalog_item_versions', 'INSERT');
SELECT 'version_update', has_table_privilege(:'app_user', 'finding_catalog_item_versions', 'UPDATE');
SELECT 'version_delete', has_table_privilege(:'app_user', 'finding_catalog_item_versions', 'DELETE');
SELECT 'migration_applied', COUNT(*)
FROM typeorm_migrations
WHERE name = 'PhaseD52FindingCatalogAdministration1787335200000';
SQL
)"
grep -q 'categories_baseline|t' <<<"$dhv2_db_check" || fail "Faltan categorías de la línea base"
grep -q 'catalog_items_baseline|t' <<<"$dhv2_db_check" || fail "Faltan tipos de hallazgo de la línea base"
grep -q 'versions_table|1' <<<"$dhv2_db_check" || fail "No se creó la tabla de revisiones"
grep -q 'version_rows_match|t' <<<"$dhv2_db_check" || fail "Hay tipos sin snapshot de su revisión vigente"
grep -q 'revision_constraints|5' <<<"$dhv2_db_check" || fail "Faltan restricciones de integridad del historial"
grep -q 'catalog_manage_roles|2' <<<"$dhv2_db_check" || fail "Admin y supervisor no conservan gestión del catálogo"
grep -q 'version_select|t' <<<"$dhv2_db_check" || fail "La API no puede leer revisiones"
grep -q 'version_insert|t' <<<"$dhv2_db_check" || fail "La API no puede crear revisiones"
grep -q 'version_update|f' <<<"$dhv2_db_check" || fail "La API no debe modificar revisiones"
grep -q 'version_delete|f' <<<"$dhv2_db_check" || fail "La API no debe borrar revisiones"
grep -q 'migration_applied|1' <<<"$dhv2_db_check" || fail "La migración D5.2 no figura aplicada"
echo "$dhv2_db_check"
echo
echo "========== D5.2 · API COMPILADA =========="
docker compose exec -T api sh -lc \
'grep -R "FindingCatalogController" /app/dist >/dev/null && grep -R "FINDING_CATALOG_ITEM_UPDATED" /app/dist >/dev/null'
echo "Administración, auditoría y versionado cargados: OK"
echo
echo "========== D5.2 · DASHBOARD =========="
docker compose exec -T web sh -lc \
'grep -R "0.19.2" /usr/share/nginx/html/assets >/dev/null && grep -R "Catálogo administrable" /usr/share/nginx/html/assets >/dev/null && grep -R "Catálogo de hallazgos" /usr/share/nginx/html/assets >/dev/null && grep -R "Nueva categoría" /usr/share/nginx/html/assets >/dev/null && grep -R "Nuevo tipo de hallazgo" /usr/share/nginx/html/assets >/dev/null && grep -R "Los cambios crean una nueva revisión" /usr/share/nginx/html/assets >/dev/null'
echo "Catálogo administrativo y footer v0.19.2: OK"
echo
echo "========== D5.2 · CONTENEDORES =========="
docker compose ps
echo
echo "ACEPTACIÓN DE FASE D5.2: OK"
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
cd "$ROOT"
DB_SERVICE="${DB_SERVICE:-db}"
API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}"
psql_app() {
docker compose exec -T "$DB_SERVICE" sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -v ON_ERROR_STOP=1' <<<"$1"
}
echo "========== D5.3.1 · VERSIONES =========="
API_VERSION="$(node -p "require('./api-v3/package.json').version")"
WEB_VERSION="$(node -p "require('./web-v2/package.json').version")"
[[ "$API_VERSION" == "0.19.3-1" ]]
[[ "$WEB_VERSION" == "0.19.3-1" ]]
echo "API: $API_VERSION"
echo "WEB: $WEB_VERSION"
echo
echo "========== D5.3.1 · MIGRACIONES =========="
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -q "Pending migrations: no" <<<"$MIGRATION_OUTPUT"
echo
echo "========== D5.3.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -q '"version":"0.19.3-1"' <<<"$HEALTH"
grep -q '"phase":"D5.3.1"' <<<"$HEALTH"
echo
echo "========== D5.3.1 · BOOTSTRAP PROTEGIDO =========="
STATUS_CODE="$(curl -sS -o /tmp/dhv2-d531-bootstrap-status.txt -w '%{http_code}' "$API_URL/asset-types/bootstrap-status")"
[[ "$STATUS_CODE" == "401" ]]
echo "/asset-types/bootstrap-status sin sesión: 401 OK"
POST_CODE="$(curl -sS -o /tmp/dhv2-d531-bootstrap-post.txt -w '%{http_code}' -X POST "$API_URL/asset-types/bootstrap-defaults")"
[[ "$POST_CODE" == "401" ]]
echo "/asset-types/bootstrap-defaults sin sesión: 401 OK"
echo
echo "========== D5.3.1 · PRESET EN SOURCE =========="
grep -q "mendoza-hidrocarburos-v1" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "Área / Concesión" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "Empresa / Operadora" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "Instalación de superficie" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "Ducto / Cañería" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "MASTER_BOOTSTRAP_REQUIRES_EMPTY_MASTER" api-v3/src/asset-master/asset-types.service.ts
grep -q "ASSET_MASTER_BOOTSTRAPPED" api-v3/src/database/entities/audit-event.entity.ts
echo "Preset, bloqueo de Maestro no vacío y auditoría: OK"
echo
echo "========== D5.3.1 · DASHBOARD =========="
grep -q "Instalar configuración base" web-v2/src/pages/AssetTypesPage.tsx
grep -q "No carga datos reales automáticamente" web-v2/src/pages/AssetTypesPage.tsx
grep -q "0.19.3-1" web-v2/src/config/version.ts
echo "Configuración inicial visible y footer correcto: OK"
echo
echo "========== D5.3.1 · ESTADO DEL MAESTRO =========="
TYPE_COUNT="$(psql_app 'SELECT COUNT(*) FROM asset_types;')"
ASSET_COUNT="$(psql_app 'SELECT COUNT(*) FROM assets;')"
echo "Tipos actuales: $TYPE_COUNT"
echo "Activos actuales: $ASSET_COUNT"
if [[ "$TYPE_COUNT" == "0" ]]; then
echo "Maestro vacío: el botón de configuración inicial quedará habilitado."
else
echo "Maestro ya configurado: el backend rechazará una nueva inicialización automática."
fi
echo
echo "========== D5.3.1 · CONTENEDORES =========="
docker compose ps -a
echo
echo "ACEPTACIÓN DE FASE D5.3.1: OK"
+81
View File
@@ -0,0 +1,81 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d53102-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.10.2 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-10.2" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-10.2" ]]
grep -Fq "API_PHASE = 'D5.3.10.2'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.10.2 · Navegación del Maestro'" web-v2/src/config/version.ts
echo "API: 0.19.3-10.2"
echo "WEB: 0.19.3-10.2"
echo
echo "========== D5.3.10.2 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
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.10.2 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-10.2"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.10.2"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.10.2 · NAVEGACIÓN DEL MAESTRO =========="
grep -Fq "@Get(':id/lineage')" api-v3/src/asset-master/assets.controller.ts
grep -Fq 'WITH RECURSIVE lineage AS' api-v3/src/asset-master/assets.service.ts
grep -Fq "label: 'Navegar'" web-v2/src/features/assets/AssetCenterTabs.tsx
grep -Fq 'asset-browser-entry-grid' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'Áreas y yacimientos' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'Instalaciones y estructura' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'Equipos y otros activos' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'asset-browser-breadcrumb' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'getAssetLineage' web-v2/src/pages/AssetEditorPage.tsx
grep -Fq "Omit<NonNullable<Parameters<typeof listAssetTreeChildren>[0]>, 'parentId' | 'limit'>" web-v2/src/features/assets/AssetHierarchyView.tsx
echo "Navegación por niveles y breadcrumb: OK"
echo
echo "========== D5.3.10.2 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
if grep -Fq 'Flujo seguro D5.3.9' web-v2/src/pages/AssetImportsPage.tsx; then
echo "ERROR: quedó texto interno de fase en la interfaz"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.10.2 · SEGURIDAD HEREDADA =========="
grep -Fq 'PLAN_DEPENDENCY_REVIEW_CODES' api-v3/src/asset-imports/asset-import-plan.ts
grep -Fq 'directReviewItems' api-v3/src/asset-imports/asset-import-plan.ts
grep -Fq 'private async masterStateFingerprint' api-v3/src/asset-imports/asset-imports.service.ts
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.10.2 · 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.10.2: OK"
+95
View File
@@ -0,0 +1,95 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d53111-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.11.1 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-11.1" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-11.1" ]]
grep -Fq "API_PHASE = 'D5.3.11.1'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.11.1 · Navegación de importados'" web-v2/src/config/version.ts
echo "API: 0.19.3-11.1"
echo "WEB: 0.19.3-11.1"
echo
echo "========== D5.3.11.1 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
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.11.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-11.1"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.11.1"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.11.1 · IMPORTACIÓN PARCIAL =========="
grep -Fq "@Get('reviews')" api-v3/src/asset-imports/asset-imports.controller.ts
grep -Fq "@Post(':id/apply-safe')" api-v3/src/asset-imports/asset-imports.controller.ts
grep -Fq 'safePlanItems(allItems)' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "status: 'PARTIAL'" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'batchApplicationProgress' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "history.batch_id=\$1 AND item.action='CREATE' AND item.status='APPLIED'" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'Revisiones pendientes' web-v2/src/features/assets/AssetImportReviewsPanel.tsx
grep -Fq 'Importar registros seguros' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'Importación parcial en curso' web-v2/src/pages/AssetImportsPage.tsx
echo "Aplicación segura y bandeja de revisiones: OK"
echo
echo "========== D5.3.11.1 · MAESTRO HEREDADO =========="
grep -Fq "@Get(':id/lineage')" api-v3/src/asset-master/assets.controller.ts
grep -Fq 'asset-browser-entry-grid' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'asset-browser-breadcrumb' web-v2/src/features/assets/AssetHierarchyView.tsx
echo "Navegación y breadcrumb del Maestro: OK"
echo
echo "========== D5.3.11.1 · ACTIVOS IMPORTADOS EN NAVEGACIÓN =========="
grep -Fq 'SELECT asset.operator_company_id AS company_id' api-v3/src/asset-master/asset-operational-relations.service.ts
grep -Fq 'WHERE asset.operational_area_id = $1' api-v3/src/asset-master/asset-operational-relations.service.ts
grep -Fq 'SELECT asset.operational_area_id AS area_id' api-v3/src/asset-master/asset-operational-relations.service.ts
grep -Fq 'WHERE asset.operator_company_id = $1' api-v3/src/asset-master/asset-operational-relations.service.ts
grep -Fq 'Áreas con relación registrada o activos asignados' web-v2/src/features/assets/AssetHierarchyView.tsx
echo "Contexto importado visible sin crear relaciones formales: OK"
echo
echo "========== D5.3.11.1 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
if grep -Fq 'Flujo seguro D5.3.9' web-v2/src/pages/AssetImportsPage.tsx; then
echo "ERROR: quedó texto interno de fase en la interfaz"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.11.1 · SEGURIDAD HEREDADA =========="
grep -Fq 'PLAN_DEPENDENCY_REVIEW_CODES' api-v3/src/asset-imports/asset-import-plan.ts
grep -Fq 'private async masterStateFingerprint' api-v3/src/asset-imports/asset-imports.service.ts
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.11.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.11.1: OK"
+86
View File
@@ -0,0 +1,86 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5311-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.11 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-11" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-11" ]]
grep -Fq "API_PHASE = 'D5.3.11'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.11 · Importación parcial y revisiones'" web-v2/src/config/version.ts
echo "API: 0.19.3-11"
echo "WEB: 0.19.3-11"
echo
echo "========== D5.3.11 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
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.11 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-11"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.11"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.11 · IMPORTACIÓN PARCIAL =========="
grep -Fq "@Get('reviews')" api-v3/src/asset-imports/asset-imports.controller.ts
grep -Fq "@Post(':id/apply-safe')" api-v3/src/asset-imports/asset-imports.controller.ts
grep -Fq 'safePlanItems(allItems)' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "status: 'PARTIAL'" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'batchApplicationProgress' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "history.batch_id=\$1 AND item.action='CREATE' AND item.status='APPLIED'" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'Revisiones pendientes' web-v2/src/features/assets/AssetImportReviewsPanel.tsx
grep -Fq 'Importar registros seguros' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'Importación parcial en curso' web-v2/src/pages/AssetImportsPage.tsx
echo "Aplicación segura y bandeja de revisiones: OK"
echo
echo "========== D5.3.11 · MAESTRO HEREDADO =========="
grep -Fq "@Get(':id/lineage')" api-v3/src/asset-master/assets.controller.ts
grep -Fq 'asset-browser-entry-grid' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'asset-browser-breadcrumb' web-v2/src/features/assets/AssetHierarchyView.tsx
echo "Navegación y breadcrumb del Maestro: OK"
echo
echo "========== D5.3.11 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
if grep -Fq 'Flujo seguro D5.3.9' web-v2/src/pages/AssetImportsPage.tsx; then
echo "ERROR: quedó texto interno de fase en la interfaz"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.11 · SEGURIDAD HEREDADA =========="
grep -Fq 'PLAN_DEPENDENCY_REVIEW_CODES' api-v3/src/asset-imports/asset-import-plan.ts
grep -Fq 'private async masterStateFingerprint' api-v3/src/asset-imports/asset-imports.service.ts
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.11 · 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.11: OK"
+95
View File
@@ -0,0 +1,95 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5312-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.12 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-12" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-12" ]]
grep -Fq "API_PHASE = 'D5.3.12'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.12 · Inventarios por empresa'" web-v2/src/config/version.ts
echo "API: 0.19.3-12"
echo "WEB: 0.19.3-12"
echo
echo "========== D5.3.12 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
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.12 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-12"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.12"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.12 · INVENTARIOS POR EMPRESA =========="
grep -Fq "to: '/inventarios', label: 'Inventarios'" web-v2/src/layout/AppLayout.tsx
grep -Fq '<span className="eyebrow">INVENTARIOS</span><h1>Inventarios</h1>' web-v2/src/pages/AssetsPage.tsx
grep -Fq 'INVENTARIOS POR EMPRESA' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'Cada empresa tiene su propio inventario.' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'INVENTARIO DE EMPRESA' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'Áreas del inventario' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'Ruta del inventario' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -Fq 'path="/inventarios"' web-v2/src/app/App.tsx
grep -Fq 'path="/activos"' web-v2/src/app/App.tsx
if grep -RIn --include='*.ts' --include='*.tsx' 'Maestro' web-v2/src; then
echo "ERROR: quedó terminología anterior visible en el frontend"
exit 1
fi
echo "Concepto Inventarios aplicado en navegación y presentación: OK"
echo
echo "========== D5.3.12 · IMPORTACIÓN PARCIAL HEREDADA =========="
grep -Fq "@Get('reviews')" api-v3/src/asset-imports/asset-imports.controller.ts
grep -Fq "@Post(':id/apply-safe')" api-v3/src/asset-imports/asset-imports.controller.ts
grep -Fq 'safePlanItems(allItems)' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'Revisiones pendientes' web-v2/src/features/assets/AssetImportReviewsPanel.tsx
grep -Fq 'Importar registros seguros' web-v2/src/pages/AssetImportsPage.tsx
echo "Importación parcial y revisiones: OK"
echo
echo "========== D5.3.12 · NAVEGACIÓN DE IMPORTADOS HEREDADA =========="
grep -Fq 'SELECT asset.operator_company_id AS company_id' api-v3/src/asset-master/asset-operational-relations.service.ts
grep -Fq 'WHERE asset.operational_area_id = $1' api-v3/src/asset-master/asset-operational-relations.service.ts
grep -Fq 'SELECT asset.operational_area_id AS area_id' api-v3/src/asset-master/asset-operational-relations.service.ts
grep -Fq 'WHERE asset.operator_company_id = $1' api-v3/src/asset-master/asset-operational-relations.service.ts
echo "Inventarios importados navegables por empresa: OK"
echo
echo "========== D5.3.12 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.12 · SEGURIDAD HEREDADA =========="
grep -Fq 'private async masterStateFingerprint' api-v3/src/asset-imports/asset-imports.service.ts
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.12 · 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.12: OK"
+104
View File
@@ -0,0 +1,104 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5313-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.13 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-13" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-13" ]]
grep -Fq "API_PHASE = 'D5.3.13'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.13 · Seguimiento de hallazgos'" web-v2/src/config/version.ts
echo "API: 0.19.3-13"
echo "WEB: 0.19.3-13"
echo
echo "========== D5.3.13 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
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.13 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-13"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.13"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.13 · DOS VENCIMIENTOS =========="
grep -Fq "COMPANY_OVERDUE" api-v3/src/inspection-findings/dto/list-inspection-findings-query.dto.ts
grep -Fq "VERIFICATION_OVERDUE" api-v3/src/inspection-findings/dto/list-inspection-findings-query.dto.ts
grep -Fq 'companyOverdue' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'verificationOverdue' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'Vencimiento administrativo' web-v2/src/pages/FindingsPage.tsx
grep -Fq 'Vencimiento de verificación' web-v2/src/pages/FindingsPage.tsx
grep -Fq 'Fecha de verificación / resolución' web-v2/src/pages/FindingDetailPage.tsx
echo "Vencimiento administrativo y vencimiento de verificación separados: OK"
echo
echo "========== D5.3.13 · BANDEJA DE HALLAZGOS =========="
grep -Fq "this.findings.listGlobal(query)" api-v3/src/inspection-findings/inspection-findings.controller.ts
grep -Fq 'listInspectionFindingsGlobal' web-v2/src/lib/api.ts
grep -Fq 'Bandeja de trabajo para respuestas de empresas' web-v2/src/pages/FindingsPage.tsx
grep -Fq 'path="/hallazgos/:id"' web-v2/src/app/App.tsx
grep -Fq 'PROGRAMAR VERIFICACIÓN' web-v2/src/pages/FindingsPage.tsx
echo "Bandeja global de seguimiento: OK"
echo
echo "========== D5.3.13 · RESPUESTA, DOCUMENTACIÓN Y CIERRE =========="
grep -Fq "@Patch(':id/close')" api-v3/src/inspection-findings/inspection-findings.controller.ts
grep -Fq 'finding.status = InspectionFindingStatus.CLOSED' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'INSPECTION_FINDING_CLOSED' api-v3/src/database/entities/audit-event.entity.ts
grep -Fq 'closeInspectionFinding' web-v2/src/pages/FindingDetailPage.tsx
grep -Fq 'InspectionEvidencePanel' web-v2/src/pages/FindingDetailPage.tsx
echo "Seguimiento de oficina y cierre auditado: OK"
echo
echo "========== D5.3.13 · DASHBOARD =========="
grep -Fq 'overdueCompanyResponses' api-v3/src/dashboard/dashboard.service.ts
grep -Fq 'awaitingVerificationSchedule' api-v3/src/dashboard/dashboard.service.ts
grep -Fq 'VERIFICACIONES VENCIDAS' web-v2/src/pages/DashboardPage.tsx
grep -Fq 'ESPERANDO RESPUESTA' web-v2/src/pages/DashboardPage.tsx
echo "Dashboard administrativo y operativo: OK"
echo
echo "========== D5.3.13 · IMPORTACIÓN E INVENTARIOS HEREDADOS =========="
grep -Fq "to: '/inventarios', label: 'Inventarios'" web-v2/src/layout/AppLayout.tsx
grep -Fq "@Get('reviews')" api-v3/src/asset-imports/asset-imports.controller.ts
grep -Fq "@Post(':id/apply-safe')" api-v3/src/asset-imports/asset-imports.controller.ts
echo "Inventarios e importación parcial preservados: OK"
echo
echo "========== D5.3.13 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.13 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.13 · 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.13: OK"
+89
View File
@@ -0,0 +1,89 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5314-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.14 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-14" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-14" ]]
grep -Fq "API_PHASE = 'D5.3.14'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.14 · Expediente técnico'" web-v2/src/config/version.ts
echo "API: 0.19.3-14"
echo "WEB: 0.19.3-14"
echo
echo "========== D5.3.14 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
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.14 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-14"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.14"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.14 · EXPEDIENTE TÉCNICO =========="
grep -Fq "@Get(':id/dossier')" api-v3/src/asset-master/assets.controller.ts
grep -Fq 'async dossier(id: string)' api-v3/src/asset-master/assets.service.ts
grep -Fq 'inspection_finding_evidence' api-v3/src/asset-master/assets.service.ts
grep -Fq 'inspection_finding_communications' api-v3/src/asset-master/assets.service.ts
grep -Fq 'asset_source_documents' api-v3/src/asset-master/assets.service.ts
grep -Fq 'asset_versions' api-v3/src/asset-master/assets.service.ts
grep -Fq "label: 'Expediente'" web-v2/src/pages/AssetEditorPage.tsx
grep -Fq 'Historia completa del elemento' web-v2/src/features/assets/AssetDossierPanel.tsx
grep -Fq 'Línea de tiempo' web-v2/src/features/assets/AssetDossierPanel.tsx
echo "Expediente técnico y cronología unificada: OK"
echo
echo "========== D5.3.14 · NAVEGACIÓN CRUZADA =========="
grep -Fq 'to={`/inspecciones/${visit.id}`}' web-v2/src/features/assets/AssetDossierPanel.tsx
grep -Fq 'to={`/inspecciones/actas/${act.id}`}' web-v2/src/features/assets/AssetDossierPanel.tsx
grep -Fq 'to={`/hallazgos/${finding.id}`}' web-v2/src/features/assets/AssetDossierPanel.tsx
grep -Fq 'Vencimiento empresa' web-v2/src/features/assets/AssetDossierPanel.tsx
grep -Fq 'Verificación' web-v2/src/features/assets/AssetDossierPanel.tsx
echo "Inventario, inspecciones, actas y hallazgos conectados: OK"
echo
echo "========== D5.3.14 · SEGUIMIENTO HEREDADO =========="
grep -Fq 'companyOverdue' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'verificationOverdue' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'Fecha de verificación / resolución' web-v2/src/pages/FindingDetailPage.tsx
echo "Dos vencimientos del hallazgo preservados: OK"
echo
echo "========== D5.3.14 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.14 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.14 · 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.14: OK"
+88
View File
@@ -0,0 +1,88 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d53151-accept-migration-$$.log"
TMP_SCHEMA="/tmp/dhv2-d53151-schema-$$.log"
trap 'rm -f "$TMP_MIGRATION" "$TMP_SCHEMA"' EXIT
cd "$ROOT"
echo "========== D5.3.15.1 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-15.1" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-15.1" ]]
grep -Fq "API_PHASE = 'D5.3.15.1'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.15.1 · Centro documental'" web-v2/src/config/version.ts
echo "API: 0.19.3-15.1"
echo "WEB: 0.19.3-15.1"
echo
echo "========== D5.3.15.1 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT table_name FROM information_schema.tables WHERE table_schema='\''public'\'' AND table_name='\''inspection_reports'\''; SELECT code FROM permissions WHERE code IN ('\''inspection_reports.read'\'','\''inspection_reports.generate'\'') ORDER BY code;"' | tee "$TMP_SCHEMA"
grep -Fxq 'inspection_reports' "$TMP_SCHEMA"
grep -Fxq 'inspection_reports.generate' "$TMP_SCHEMA"
grep -Fxq 'inspection_reports.read' "$TMP_SCHEMA"
echo
echo "========== D5.3.15.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-15.1"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.15.1"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.15.1 · CENTRO DOCUMENTAL =========="
grep -Fq 'async listGlobal(query: ListInspectionActsQueryDto)' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq "@Controller('inspection-reports')" api-v3/src/inspection-reports/inspection-reports.controller.ts
grep -Fq 'async listPending(query: ListInspectionReportsQueryDto)' api-v3/src/inspection-reports/inspection-reports.service.ts
grep -Fq 'DocumentSequenceType.REPORT' api-v3/src/inspection-reports/inspection-reports.service.ts
grep -Fq 'sha256CanonicalJson(frozenSnapshot)' api-v3/src/inspection-reports/inspection-reports.service.ts
grep -Fq 'path="/actas"' web-v2/src/app/App.tsx
grep -Fq 'path="/informes"' web-v2/src/app/App.tsx
grep -Fq "label: 'Actas'" web-v2/src/layout/AppLayout.tsx
grep -Fq "label: 'Informes'" web-v2/src/layout/AppLayout.tsx
grep -Fq 'inspectionReports,' api-v3/src/asset-master/assets.service.ts
grep -Fq 'dossier.inspectionReports' web-v2/src/features/assets/AssetDossierPanel.tsx
echo "Actas e informes globales conectados: OK"
echo
echo "========== D5.3.15.1 · EMISIÓN CONTROLADA =========="
grep -Fq 'assertMobileInspector(principal)' api-v3/src/inspection-reports/inspection-reports.service.ts
grep -Fq 'INSPECTION_REPORT_ACT_NOT_CLOSED' api-v3/src/inspection-reports/inspection-reports.service.ts
grep -Fq 'dhv2_guard_inspection_report_frozen' api-v3/src/database/migrations/1788026400000-phase-d5-3-15-document-center.ts
grep -Fq 'Pendientes de emisión' web-v2/src/pages/ReportsPage.tsx
echo "Numeración y congelado del informe protegidos: OK"
echo
echo "========== D5.3.15.1 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.15.1 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.15.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.15.1: OK"
+81
View File
@@ -0,0 +1,81 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5316-accept-migration-$$.log"
TMP_SCHEMA="/tmp/dhv2-d5316-schema-$$.log"
trap 'rm -f "$TMP_MIGRATION" "$TMP_SCHEMA"' EXIT
cd "$ROOT"
echo "========== D5.3.16 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-16" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-16" ]]
grep -Fq "API_PHASE = 'D5.3.16'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.16 · Planificación de verificaciones'" web-v2/src/config/version.ts
echo "API: 0.19.3-16"
echo "WEB: 0.19.3-16"
echo
echo "========== D5.3.16 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT table_name FROM information_schema.tables WHERE table_schema='\''public'\'' AND table_name='\''inspection_finding_verification_visits'\''; SELECT code FROM permissions WHERE code='\''inspection_verifications.plan'\'';"' | tee "$TMP_SCHEMA"
grep -Fxq 'inspection_finding_verification_visits' "$TMP_SCHEMA"
grep -Fxq 'inspection_verifications.plan' "$TMP_SCHEMA"
echo
echo "========== D5.3.16 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-16"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.16"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.16 · DOS VENCIMIENTOS =========="
grep -Fq 'finding.company_response_received_on IS NOT NULL' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'finding.next_control_on IS NOT NULL' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'La fecha de verificación no es el vencimiento administrativo.' web-v2/src/pages/VerificationPlanningPage.tsx
echo "Seguimiento administrativo y planificación operativa separados: OK"
echo
echo "========== D5.3.16 · PLANIFICACIÓN =========="
grep -Fq "@Controller('inspection-verifications')" api-v3/src/inspection-verifications/inspection-verifications.controller.ts
grep -Fq 'VERIFICATION_CONTEXT_MIXED' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'VERIFICATION_ALREADY_PLANNED' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'inspection_finding_verification_visits' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'path="/hallazgos/planificacion"' web-v2/src/app/App.tsx
grep -Fq 'Crear visita de verificación' web-v2/src/pages/VerificationPlanningPage.tsx
echo "Bandeja y creación trazable de visitas de verificación: OK"
echo
echo "========== D5.3.16 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.16 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.16 · 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.16: OK"
+84
View File
@@ -0,0 +1,84 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d53171-accept-migration-$$.log"
TMP_SCHEMA="/tmp/dhv2-d53171-schema-$$.log"
trap 'rm -f "$TMP_MIGRATION" "$TMP_SCHEMA"' EXIT
cd "$ROOT"
echo "========== D5.3.17.1 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-17.1" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-17.1" ]]
grep -Fq "API_PHASE = 'D5.3.17.1'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.17.1 · Verificación de campo'" web-v2/src/config/version.ts
echo "API: 0.19.3-17.1"
echo "WEB: 0.19.3-17.1"
echo
echo "========== D5.3.17.1 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT table_name FROM information_schema.tables WHERE table_schema='\''public'\'' AND table_name='\''inspection_finding_verification_visits'\''; SELECT column_name FROM information_schema.columns WHERE table_schema='\''public'\'' AND table_name='\''inspection_finding_verification_visits'\'' AND column_name IN ('\''outcome'\'','\''verified_at'\'','\''target_control_on'\'','\''rescheduled_control_on'\'') ORDER BY column_name;"' | tee "$TMP_SCHEMA"
grep -Fxq 'inspection_finding_verification_visits' "$TMP_SCHEMA"
grep -Fxq 'outcome' "$TMP_SCHEMA"
grep -Fxq 'rescheduled_control_on' "$TMP_SCHEMA"
grep -Fxq 'target_control_on' "$TMP_SCHEMA"
grep -Fxq 'verified_at' "$TMP_SCHEMA"
echo
echo "========== D5.3.17.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-17.1"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.17.1"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.17.1 · RESULTADO DE CAMPO =========="
grep -Fq "@Patch('visits/:visitId/findings/:findingId/result')" api-v3/src/inspection-verifications/inspection-verifications.controller.ts
grep -Fq 'InspectionVerificationOutcome.RESOLVED' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'InspectionVerificationOutcome.NOT_RESOLVED' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'InspectionVerificationOutcome.REQUIRES_NEW_DATE' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'INSPECTION_VERIFICATION_RESULTS_REQUIRED' api-v3/src/inspection-closing/inspection-closing.service.ts
echo "Resultado de verificación controlado: OK"
echo
echo "========== D5.3.17.1 · EVIDENCIA Y BANDEJA =========="
grep -Fq "VERIFICATION = 'VERIFICATION'" api-v3/src/database/entities/inspection-finding-evidence.entity.ts
grep -Fq 'verification_visit_id' api-v3/src/inspection-findings/inspection-evidence.service.ts
grep -Fq 'READY_TO_CLOSE' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'Listos para cerrar' web-v2/src/pages/FindingsPage.tsx
echo "Evidencia de verificación y cierre administrativo: OK"
echo
echo "========== D5.3.17.1 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.17.1 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.17.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.17.1: OK"
+84
View File
@@ -0,0 +1,84 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5317-accept-migration-$$.log"
TMP_SCHEMA="/tmp/dhv2-d5317-schema-$$.log"
trap 'rm -f "$TMP_MIGRATION" "$TMP_SCHEMA"' EXIT
cd "$ROOT"
echo "========== D5.3.17 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-17" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-17" ]]
grep -Fq "API_PHASE = 'D5.3.17'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.17 · Verificación de campo'" web-v2/src/config/version.ts
echo "API: 0.19.3-17"
echo "WEB: 0.19.3-17"
echo
echo "========== D5.3.17 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT table_name FROM information_schema.tables WHERE table_schema='\''public'\'' AND table_name='\''inspection_finding_verification_visits'\''; SELECT column_name FROM information_schema.columns WHERE table_schema='\''public'\'' AND table_name='\''inspection_finding_verification_visits'\'' AND column_name IN ('\''outcome'\'','\''verified_at'\'','\''target_control_on'\'','\''rescheduled_control_on'\'') ORDER BY column_name;"' | tee "$TMP_SCHEMA"
grep -Fxq 'inspection_finding_verification_visits' "$TMP_SCHEMA"
grep -Fxq 'outcome' "$TMP_SCHEMA"
grep -Fxq 'rescheduled_control_on' "$TMP_SCHEMA"
grep -Fxq 'target_control_on' "$TMP_SCHEMA"
grep -Fxq 'verified_at' "$TMP_SCHEMA"
echo
echo "========== D5.3.17 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-17"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.17"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.17 · RESULTADO DE CAMPO =========="
grep -Fq "@Patch('visits/:visitId/findings/:findingId/result')" api-v3/src/inspection-verifications/inspection-verifications.controller.ts
grep -Fq 'InspectionVerificationOutcome.RESOLVED' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'InspectionVerificationOutcome.NOT_RESOLVED' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'InspectionVerificationOutcome.REQUIRES_NEW_DATE' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'INSPECTION_VERIFICATION_RESULTS_REQUIRED' api-v3/src/inspection-closing/inspection-closing.service.ts
echo "Resultado de verificación controlado: OK"
echo
echo "========== D5.3.17 · EVIDENCIA Y BANDEJA =========="
grep -Fq "VERIFICATION = 'VERIFICATION'" api-v3/src/database/entities/inspection-finding-evidence.entity.ts
grep -Fq 'verification_visit_id' api-v3/src/inspection-findings/inspection-evidence.service.ts
grep -Fq 'READY_TO_CLOSE' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'Listos para cerrar' web-v2/src/pages/FindingsPage.tsx
echo "Evidencia de verificación y cierre administrativo: OK"
echo
echo "========== D5.3.17 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.17 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.17 · 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.17: OK"
+88
View File
@@ -0,0 +1,88 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d53182-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.18.2 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-18.2" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-18.2" ]]
grep -Fq "API_PHASE = 'D5.3.18.2'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.18.2 · Expediente operativo'" web-v2/src/config/version.ts
echo "API: 0.19.3-18.2"
echo "WEB: 0.19.3-18.2"
echo
echo "========== D5.3.18.2 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT code FROM permissions WHERE code = '\''inspection_findings.close'\'';"' | grep -Fxq 'inspection_findings.close'
CLOSE_ROLES="$(docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT role.code FROM role_permissions rp JOIN roles role ON role.id=rp.role_id JOIN permissions permission ON permission.id=rp.permission_id WHERE permission.code='\''inspection_findings.close'\'' ORDER BY role.code;"')"
[[ "$CLOSE_ROLES" == $'director\ninspector\nsupervisor' ]]
echo "Cierre habilitado sólo para Director, Inspector y Supervisor: OK"
echo
echo "========== D5.3.18.2 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-18.2"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.18.2"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.18.2 · EXPEDIENTE OPERATIVO =========="
grep -Fq 'officeWorkflow' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'FindingOfficeWorkspace' web-v2/src/pages/FindingDetailPage.tsx
grep -Fq 'Dar curso al hallazgo' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'ACCIÓN RECOMENDADA' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
echo "Ficha única y acción recomendada: OK"
echo
echo "========== D5.3.18.2 · PRESENTACIONES APPEND-ONLY =========="
grep -Fq 'INSPECTION_FINDING_RESPONSE_IMMUTABLE' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'Nueva presentación / ampliación de la empresa' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'Primera respuesta registrada · inmutable' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'Agregar presentación' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq "type: 'COMPANY_RESPONSE'" web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
echo "Respuesta original protegida y presentaciones sucesivas históricas: OK"
echo
echo "========== D5.3.18.2 · CIERRE CONTROLADO =========="
grep -Fq "@RequirePermissions('inspection_findings.close')" api-v3/src/inspection-findings/inspection-findings.controller.ts
grep -Fq "hasPermission('inspection_findings.close')" web-v2/src/pages/FindingDetailPage.tsx
grep -Fq 'El cierre está reservado a Inspector, Supervisor o Director.' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
echo "Cierre separado del seguimiento general: OK"
echo
echo "========== D5.3.18.2 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.18.2 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.18.2 · 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.18.2: OK"
+88
View File
@@ -0,0 +1,88 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d53183-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.18.3 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-18.3" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-18.3" ]]
grep -Fq "API_PHASE = 'D5.3.18.3'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.18.3 · Expediente operativo'" web-v2/src/config/version.ts
echo "API: 0.19.3-18.3"
echo "WEB: 0.19.3-18.3"
echo
echo "========== D5.3.18.3 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT code FROM permissions WHERE code = '\''inspection_findings.close'\'';"' | grep -Fxq 'inspection_findings.close'
CLOSE_ROLES="$(docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT role.code FROM role_permissions rp JOIN roles role ON role.id=rp.role_id JOIN permissions permission ON permission.id=rp.permission_id WHERE permission.code='\''inspection_findings.close'\'' ORDER BY role.code;"')"
[[ "$CLOSE_ROLES" == $'director\ninspector\nsupervisor' ]]
echo "Cierre habilitado sólo para Director, Inspector y Supervisor: OK"
echo
echo "========== D5.3.18.3 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-18.3"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.18.3"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.18.3 · EXPEDIENTE OPERATIVO =========="
grep -Fq 'officeWorkflow' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'FindingOfficeWorkspace' web-v2/src/pages/FindingDetailPage.tsx
grep -Fq 'Dar curso al hallazgo' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'ACCIÓN RECOMENDADA' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
echo "Ficha única y acción recomendada: OK"
echo
echo "========== D5.3.18.3 · PRESENTACIONES APPEND-ONLY =========="
grep -Fq 'INSPECTION_FINDING_RESPONSE_IMMUTABLE' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'Nueva presentación / ampliación de la empresa' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'Primera respuesta registrada · inmutable' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'Agregar presentación' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq "type: 'COMPANY_RESPONSE'" web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
echo "Respuesta original protegida y presentaciones sucesivas históricas: OK"
echo
echo "========== D5.3.18.3 · CIERRE CONTROLADO =========="
grep -Fq "@RequirePermissions('inspection_findings.close')" api-v3/src/inspection-findings/inspection-findings.controller.ts
grep -Fq "hasPermission('inspection_findings.close')" web-v2/src/pages/FindingDetailPage.tsx
grep -Fq 'El cierre está reservado a Inspector, Supervisor o Director.' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
echo "Cierre separado del seguimiento general: OK"
echo
echo "========== D5.3.18.3 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.18.3 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.18.3 · 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.18.3: OK"
+88
View File
@@ -0,0 +1,88 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5318-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.18 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-18" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-18" ]]
grep -Fq "API_PHASE = 'D5.3.18'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.18 · Expediente operativo'" web-v2/src/config/version.ts
echo "API: 0.19.3-18"
echo "WEB: 0.19.3-18"
echo
echo "========== D5.3.18 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
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.18 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-18"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.18"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.18 · EXPEDIENTE OPERATIVO =========="
grep -Fq 'officeWorkflow' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq "'WAITING_COMPANY'" api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq "stage: 'READY_TO_CLOSE'" api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'FindingOfficeWorkspace' web-v2/src/pages/FindingDetailPage.tsx
grep -Fq 'Dar curso al hallazgo' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'ACCIÓN RECOMENDADA' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
echo "Ficha única y acción recomendada: OK"
echo
echo "========== D5.3.18 · RESPUESTA Y DOCUMENTOS =========="
grep -Fq 'Registrar respuesta' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq "type: 'COMPANY_RESPONSE'" web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq "purpose: 'COMPANY_RESPONSE'" web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'companyCommittedCorrectionOn' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'nextControlOn' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'managedCompanyResponse' web-v2/src/features/inspections/InspectionEvidencePanel.tsx
echo "Respuesta, PDF y fechas independientes integrados: OK"
echo
echo "========== D5.3.18 · VERIFICACIÓN Y CIERRE =========="
grep -Fq 'Planificar verificación' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'Abrir verificación' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'Decisión administrativa' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'closeInspectionFinding' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
echo "Planificación, resultado y cierre conectados: OK"
echo
echo "========== D5.3.18 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.18 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.18 · 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.18: OK"
+110
View File
@@ -0,0 +1,110 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d53191-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.19.1 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-19.1" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-19.1" ]]
grep -Fq "API_PHASE = 'D5.3.19.1'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.19.1 · Inventarios y filtros operativos'" web-v2/src/config/version.ts
echo "API: 0.19.3-19.1"
echo "WEB: 0.19.3-19.1"
echo
echo "========== D5.3.19.1 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
for column in common_name; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT column_name FROM information_schema.columns WHERE table_name='assets' AND column_name='$column';\"" | grep -Fxq "$column"
done
for column in response_due_basis response_due_days response_due_base_on report_notified_on; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT column_name FROM information_schema.columns WHERE table_name='inspection_findings' AND column_name='$column';\"" | grep -Fxq "$column"
done
echo "Nombre habitual y metadatos de vencimiento presentes: OK"
echo
echo "========== D5.3.19.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-19.1"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.19.1"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.19.1 · NOMBRE HABITUAL =========="
grep -Fq 'commonName' api-v3/src/asset-master/dto/create-asset.dto.ts
grep -A6 -F 'export function createAsset(input: {' web-v2/src/lib/api.ts | grep -Fq 'commonName?: string | null;'
grep -Fq 'asset.common_name ILIKE' api-v3/src/asset-master/assets.service.ts
grep -Fq 'Nombre habitual / sobrenombre' web-v2/src/pages/AssetEditorPage.tsx
grep -Fq 'Nombre habitual:' web-v2/src/features/assets/AssetDossierPanel.tsx
echo "Alias visible, editable, versionado y buscable: OK"
echo
echo "========== D5.3.19.1 · FILTROS TRANSVERSALES =========="
for file in \
web-v2/src/pages/InspectionVisitsPage.tsx \
web-v2/src/pages/ActsPage.tsx \
web-v2/src/pages/ReportsPage.tsx \
web-v2/src/pages/FindingsPage.tsx; do
grep -Fq 'OperationalFilters' "$file"
done
for key in companyId areaId inspectorId dateFrom dateTo; do
grep -Fq "$key" api-v3/src/inspection-visits/dto/list-inspection-visits-query.dto.ts
grep -Fq "$key" api-v3/src/inspection-acts/dto/list-inspection-acts-query.dto.ts
grep -Fq "$key" api-v3/src/inspection-reports/dto/list-inspection-reports-query.dto.ts
grep -Fq "$key" api-v3/src/inspection-findings/dto/list-inspection-findings-query.dto.ts
done
echo "Empresa, Área, Inspector y rango de fechas disponibles: OK"
echo
echo "========== D5.3.19.1 · PLAZO ADMINISTRATIVO =========="
grep -Fq 'InspectionFindingResponseDueBasis.FINDING_DATE' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'INSPECTION_FINDING_REPORT_NOTIFICATION_REQUIRED' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'INSPECTION_FINDING_RESPONSE_DEADLINE_LOCKED' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'Calcular vencimiento' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'Desde la notificación del informe' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
echo "Origen, días, fecha base y vencimiento administrativo separados: OK"
echo
echo "========== D5.3.19.1 · REGLAS PRESERVADAS =========="
grep -Fq 'assertVisitHasNoAct' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq 'INSPECTION_VISIT_ACT_ALREADY_EXISTS' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq 'INSPECTION_FINDING_RESPONSE_IMMUTABLE' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq "@RequirePermissions('inspection_findings.close')" api-v3/src/inspection-findings/inspection-findings.controller.ts
echo "Una visita = una acta y seguimiento append-only preservados: OK"
echo
echo "========== D5.3.19.1 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.19.1 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.19.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.19.1: OK"
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
cd "$ROOT"
API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}"
echo "========== D5.3.2.1 · VERSIONES =========="
API_VERSION="$(node -p "require('./api-v3/package.json').version")"
WEB_VERSION="$(node -p "require('./web-v2/package.json').version")"
[[ "$API_VERSION" == "0.19.3-2.1" ]]
[[ "$WEB_VERSION" == "0.19.3-2.1" ]]
echo "API: $API_VERSION"
echo "WEB: $WEB_VERSION"
echo
echo "========== D5.3.2.1 · MIGRACIONES =========="
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -q "Pending migrations: no" <<<"$MIGRATION_OUTPUT"
echo
echo "========== D5.3.2.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -q '"version":"0.19.3-2.1"' <<<"$HEALTH"
grep -q '"phase":"D5.3.2.1"' <<<"$HEALTH"
echo
echo "========== D5.3.2.1 · HOTFIX VERSIONADO =========="
grep -q 'SET current_version = COALESCE(current_version, 0) + 1' api-v3/src/asset-master/asset-history.service.ts
grep -q 'SELECT current_version' api-v3/src/asset-master/asset-history.service.ts
test -f api-v3/test/unit/asset-history-capture.test.ts
grep -q 'asset history capture increments then reads current_version' api-v3/test/unit/asset-history-capture.test.ts
echo "Incremento + lectura explícita de current_version y test de regresión: OK"
echo
echo "========== D5.3.2.1 · DEMO =========="
bash -n scripts/dev-seed-mendoza-demo.sh
bash -n scripts/dev-clean-demo.sh
grep -q 'DH-DEV-DEMO:ITN124/2026' api-v3/src/cli/dev-seed-mendoza-demo.ts
echo "Seed y limpiador disponibles: OK"
echo
echo "========== D5.3.2.1 · CONTENEDORES =========="
docker compose ps -a
echo
echo "ACEPTACIÓN DE FASE D5.3.2.1: OK"
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
cd "$ROOT"
DB_SERVICE="${DB_SERVICE:-db}"
API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}"
psql_app() {
docker compose exec -T "$DB_SERVICE" sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -v ON_ERROR_STOP=1' <<<"$1"
}
echo "========== D5.3.2 · VERSIONES =========="
API_VERSION="$(node -p "require('./api-v3/package.json').version")"
WEB_VERSION="$(node -p "require('./web-v2/package.json').version")"
[[ "$API_VERSION" == "0.19.3-2" ]]
[[ "$WEB_VERSION" == "0.19.3-2" ]]
echo "API: $API_VERSION"
echo "WEB: $WEB_VERSION"
echo
echo "========== D5.3.2 · MIGRACIONES =========="
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -q "Pending migrations: no" <<<"$MIGRATION_OUTPUT"
echo
echo "========== D5.3.2 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -q '"version":"0.19.3-2"' <<<"$HEALTH"
grep -q '"phase":"D5.3.2"' <<<"$HEALTH"
echo
echo "========== D5.3.2 · ENDPOINTS PROTEGIDOS =========="
CODE="$(curl -sS -o /tmp/dhv2-d532-enrichment.txt -w '%{http_code}' "$API_URL/asset-types/enrichment-status")"
[[ "$CODE" == "401" ]]
echo "/asset-types/enrichment-status sin sesión: 401 OK"
CODE="$(curl -sS -o /tmp/dhv2-d532-enrich-post.txt -w '%{http_code}' -X POST "$API_URL/asset-types/enrich-defaults")"
[[ "$CODE" == "401" ]]
echo "/asset-types/enrich-defaults sin sesión: 401 OK"
echo
echo "========== D5.3.2 · NOMENCLATURA TÉCNICA =========="
grep -q "code: 'planta'" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "code: 'bateria'" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "code: 'tanque'" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "code: 'separador'" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "code: 'bomba'" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "code: 'caldera'" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "code: 'antorcha'" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "code: 'pozo'" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "metodo_extraccion" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "Bombeo mecánico" api-v3/src/asset-master/asset-master-bootstrap.ts
grep -q "Inyector de agua" api-v3/src/asset-master/asset-master-bootstrap.ts
echo "Familias técnicas y Pozo único con método/función: OK"
echo
echo "========== D5.3.2 · DASHBOARD =========="
grep -q "Completar catálogo técnico" web-v2/src/pages/AssetTypesPage.tsx
grep -q "ampliación no destructiva" web-v2/src/pages/AssetTypesPage.tsx
grep -q "0.19.3-2" web-v2/src/config/version.ts
echo "Ampliación técnica visible y footer correcto: OK"
echo
echo "========== D5.3.2 · DEMO Y LIMPIEZA =========="
grep -q "DH-DEV-DEMO:ITN124/2026" api-v3/src/cli/dev-seed-mendoza-demo.ts
grep -q "DEV-DEMO-TK-57" api-v3/src/cli/dev-seed-mendoza-demo.ts
grep -q "DEV-DEMO-TK-58" api-v3/src/cli/dev-seed-mendoza-demo.ts
grep -q -- "--apply DH-DEV-DEMO" scripts/dev-clean-demo.sh
grep -q "MODO SIMULACIÓN" scripts/dev-clean-demo.sh
bash -n scripts/dev-seed-mendoza-demo.sh
bash -n scripts/dev-clean-demo.sh
echo "Seed marcado + limpiador protegido en dry-run: OK"
echo
echo "========== D5.3.2 · ESTADO DEL MAESTRO =========="
TYPE_COUNT="$(psql_app 'SELECT COUNT(*) FROM asset_types;')"
ASSET_COUNT="$(psql_app 'SELECT COUNT(*) FROM assets;')"
TECH_COUNT="$(psql_app "SELECT COUNT(*) FROM asset_types WHERE code IN ('planta','bateria','tanque','separador','bomba','caldera','antorcha','pozo');")"
echo "Tipos actuales: $TYPE_COUNT"
echo "Activos actuales: $ASSET_COUNT"
echo "Tipos técnicos principales ya instalados: $TECH_COUNT / 8"
if [[ "$TECH_COUNT" -lt "8" ]]; then
echo "Pendiente funcional esperado: entrar a Maestro > Tipos y atributos y pulsar 'Completar catálogo técnico'."
fi
echo
echo "========== D5.3.2 · CONTENEDORES =========="
docker compose ps -a
echo
echo "ACEPTACIÓN DE FASE D5.3.2: OK"
+94
View File
@@ -0,0 +1,94 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5320-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.20 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-20" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-20" ]]
grep -Fq "API_PHASE = 'D5.3.20'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.20 · Informe Word automático'" web-v2/src/config/version.ts
echo "API: 0.19.3-20"
echo "WEB: 0.19.3-20"
echo
echo "========== D5.3.20 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
for column in word_status word_original_name word_stored_name word_mime_type word_size_bytes word_sha256 word_generated_at word_error; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT column_name FROM information_schema.columns WHERE table_name='inspection_reports' AND column_name='$column';\"" | grep -Fxq "$column"
done
echo "Metadatos del Word automático presentes: OK"
echo
echo "========== D5.3.20 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-20"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.20"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.20 · CIERRE DOCUMENTAL =========="
grep -Fq 'await this.reports.ensureFrozenReport(manager, actId, principal, request)' api-v3/src/inspection-closing/inspection-closing.service.ts
grep -Fq 'await this.reports.ensureWordForAct(actId)' api-v3/src/inspection-closing/inspection-closing.service.ts
grep -Fq 'async ensureFrozenReport(' api-v3/src/inspection-reports/inspection-reports.service.ts
echo "Cierre de acta crea y congela automáticamente el informe: OK"
echo
echo "========== D5.3.20 · WORD =========="
grep -Fq "@Get(':id/word')" api-v3/src/inspection-reports/inspection-reports.controller.ts
grep -Fq 'buildInspectionReportWord' api-v3/src/inspection-reports/inspection-report-word.service.ts
grep -Fq 'Descargar Word' web-v2/src/pages/ReportDetailPage.tsx
grep -Fq 'Word disponible' web-v2/src/pages/ReportsPage.tsx
echo "Word de revisión generado desde snapshot congelado y descargable: OK"
echo
echo "========== D5.3.20 · REGLAS PRESERVADAS =========="
grep -Fq 'assertVisitHasNoAct' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq 'INSPECTION_VISIT_ACT_ALREADY_EXISTS' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq 'INSPECTION_FINDING_RESPONSE_IMMUTABLE' api-v3/src/inspection-findings/inspection-findings.service.ts
echo "Una visita = una acta y hallazgos append-only preservados: OK"
echo
echo "========== D5.3.20 · ALCANCE DE CORREO =========="
if grep -RniE --include='*.ts' 'nodemailer|smtp_transport|email_outbox' api-v3/src; then
echo "ERROR: D5.3.20 no debe activar correo sin destinatarios institucionales configurados"
exit 1
fi
echo "Correo automático reservado para la fase de destinatarios y entrega: OK"
echo
echo "========== D5.3.20 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.20 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.20 · 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.20: OK"
+118
View File
@@ -0,0 +1,118 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d53211-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.21.1 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-21.1" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-21.1" ]]
grep -Fq "API_PHASE = 'D5.3.21.1'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.21.1 · Entrega documental'" web-v2/src/config/version.ts
echo "API: 0.19.3-21.1"
echo "WEB: 0.19.3-21.1"
echo
echo "========== D5.3.21.1 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
for table in institutional_delivery_settings inspection_act_pdf_artifacts inspection_document_deliveries; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT to_regclass('public.$table');\"" | grep -Fxq "$table"
done
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT column_name FROM information_schema.columns WHERE table_name='"'"'organization_profiles'"'"' AND column_name='"'"'notification_email'"'"';"' | grep -Fxq 'notification_email'
echo "Destinatarios institucionales, email oficial de empresa y outbox presentes: OK"
echo
echo "========== D5.3.21.1 · PERMISOS =========="
for permission in document_delivery.read document_delivery.manage; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT code FROM permissions WHERE code='$permission';\"" | grep -Fxq "$permission"
done
for role in admin director; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT r.code FROM roles r JOIN role_permissions rp ON rp.role_id=r.id JOIN permissions p ON p.id=rp.permission_id WHERE r.code='$role' AND p.code='document_delivery.manage';\"" | grep -Fxq "$role"
done
echo "Configuración y reintentos documentales protegidos por permisos: OK"
echo
echo "========== D5.3.21.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-21.1"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.21.1"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.21.1 · ACTA PDF Y ENTREGA =========="
grep -Fq 'buildInspectionActPdf' api-v3/src/inspection-reports/inspection-act-pdf.service.ts
grep -Fq "'ACT_PDF'" api-v3/src/inspection-reports/inspection-document-delivery.service.ts
grep -Fq "'REPORT_WORD'" api-v3/src/inspection-reports/inspection-document-delivery.service.ts
grep -Fq 'await this.delivery.dispatchForAct(actId)' api-v3/src/inspection-reports/inspection-reports.service.ts
echo "Acta PDF para empresa/oficina e Informe Word para Director preparados automáticamente: OK"
echo
echo "========== D5.3.21.1 · ESTADOS Y REINTENTOS =========="
for status in WAITING_RECIPIENT WAITING_TRANSPORT WAITING_ARTIFACT SENT FAILED; do
grep -Fq "$status" api-v3/src/inspection-reports/inspection-document-delivery.service.ts
done
grep -Fq "@Post('outbox/:id/retry')" api-v3/src/inspection-reports/document-delivery.controller.ts
grep -Fq "@Post('outbox/retry-pending')" api-v3/src/inspection-reports/document-delivery.controller.ts
echo "Entrega auditable, no bloqueante y reintentable: OK"
echo
echo "========== D5.3.21.1 · CONFIGURACIÓN WEB =========="
grep -Fq 'Email oficial para actas' web-v2/src/features/assets/AssetRegistryPanel.tsx
grep -Fq 'Entrega documental' web-v2/src/pages/DocumentDeliveryPage.tsx
grep -Fq '/admin/document-delivery' web-v2/src/app/App.tsx
echo "Empresa, oficina y Director pueden configurarse sin direcciones hardcodeadas: OK"
echo
echo "========== D5.3.21.1 · SMTP =========="
grep -Fq 'SMTP_HOST' api-v3/src/inspection-reports/smtp-delivery.service.ts
grep -Fq 'STARTTLS' api-v3/src/inspection-reports/smtp-delivery.service.ts
grep -Fq 'MAIL_FROM' docker-compose.yml
SMTP_STATE="$(docker compose exec -T api sh -lc 'if [ -n "$SMTP_HOST" ] && [ -n "$MAIL_FROM" ]; then echo configured; else echo pending; fi')"
if [[ "$SMTP_STATE" == "configured" ]]; then
echo "SMTP disponible en el contenedor API: configuración activa"
else
echo "SMTP no configurado todavía: las entregas quedarán pendientes sin bloquear el cierre"
fi
echo
echo "========== D5.3.21.1 · REGLAS PRESERVADAS =========="
grep -Fq 'assertVisitHasNoAct' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq 'INSPECTION_VISIT_ACT_ALREADY_EXISTS' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq 'INSPECTION_FINDING_RESPONSE_IMMUTABLE' api-v3/src/inspection-findings/inspection-findings.service.ts
echo "Una visita = una acta; Acta cerrada y Hallazgos originales permanecen inmutables: OK"
echo
echo "========== D5.3.21.1 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.21.1 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.21.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.21.1: OK"
+119
View File
@@ -0,0 +1,119 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5322-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.22 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-22" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-22" ]]
grep -Fq "API_PHASE = 'D5.3.22'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.22 · Revisión directiva de Informes'" web-v2/src/config/version.ts
echo "API: 0.19.3-22"
echo "WEB: 0.19.3-22"
echo
echo "========== D5.3.22 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
for table in inspection_report_revisions inspection_report_signatures; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT to_regclass('public.$table');\"" | grep -Fxq "$table"
done
for column in review_status current_revision_number approved_revision_id approved_by approved_at review_note signed_at; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT column_name FROM information_schema.columns WHERE table_name='inspection_reports' AND column_name='$column';\"" | grep -Fxq "$column"
done
echo "Revisiones versionadas, aprobación y firma final presentes: OK"
echo
echo "========== D5.3.22 · PERMISOS DIRECTOR =========="
for permission in inspection_reports.revise inspection_reports.review inspection_reports.sign_final; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT code FROM permissions WHERE code='$permission';\"" | grep -Fxq "$permission"
SEEDED_ROLES="$(docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT COALESCE(string_agg(r.code,',' ORDER BY r.code),'') FROM roles r JOIN role_permissions rp ON rp.role_id=r.id JOIN permissions p ON p.id=rp.permission_id WHERE p.code='$permission' AND r.code IN ('admin','director','supervisor','inspector','auditor');\"")"
[[ "$SEEDED_ROLES" == "director" ]]
done
grep -Fq "role.code = 'director'" api-v3/src/inspection-reports/inspection-report-review.service.ts
echo "Revisión, aprobación y firma final reservadas al Director: OK"
echo
echo "========== D5.3.22 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-22"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.22"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.22 · VERSIONADO WORD =========="
grep -Fq "'AUTO'" api-v3/src/inspection-reports/inspection-report-word.service.ts
grep -Fq "'DIRECTOR_UPLOAD'" api-v3/src/inspection-reports/inspection-report-review.service.ts
grep -Fq 'validateInspectionReportRevisionContainer' api-v3/src/inspection-reports/inspection-report-review.service.ts
grep -Fq 'vbaProject' api-v3/src/inspection-reports/inspection-report-revision-file.ts
echo "Word automático como versión 1 y correcciones append-only con integridad: OK"
echo
echo "========== D5.3.22 · APROBACIÓN Y FIRMA =========="
grep -Fq "review_status = 'APPROVED'" api-v3/src/inspection-reports/inspection-report-review.service.ts
grep -Fq "review_status = 'SIGNED'" api-v3/src/inspection-reports/inspection-report-review.service.ts
grep -Fq 'DH-INSPECTION-REPORT-SIGNATURE-V1' api-v3/src/inspection-reports/inspection-report-review.service.ts
grep -Fq 'sha256CanonicalJson(payload)' api-v3/src/inspection-reports/inspection-report-review.service.ts
echo "Firma electrónica vinculada a Informe congelado y revisión aprobada: OK"
echo
echo "========== D5.3.22 · FLUJO WEB =========="
grep -Fq 'Agregar Word corregido' web-v2/src/pages/ReportDetailPage.tsx
grep -Fq 'Aprobar versión actual' web-v2/src/pages/ReportDetailPage.tsx
grep -Fq 'Firmar informe final' web-v2/src/pages/ReportDetailPage.tsx
grep -Fq 'inspection_reports.sign_final' web-v2/src/pages/ReportDetailPage.tsx
echo "Director puede versionar, aprobar y firmar desde el expediente del Informe: OK"
echo
echo "========== D5.3.22 · REGLAS PRESERVADAS =========="
grep -Fq 'assertVisitHasNoAct' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq 'INSPECTION_VISIT_ACT_ALREADY_EXISTS' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq 'INSPECTION_FINDING_RESPONSE_IMMUTABLE' api-v3/src/inspection-findings/inspection-findings.service.ts
if grep -Fq 'ALTER TABLE inspection_acts' api-v3/src/database/migrations/1788631200000-phase-d5-3-22-director-report-review.ts; then exit 1; fi
if grep -Fq 'ALTER TABLE inspection_findings' api-v3/src/database/migrations/1788631200000-phase-d5-3-22-director-report-review.ts; then exit 1; fi
echo "Una visita = una Acta; Acta y Hallazgos originales siguen inmutables: OK"
echo
echo "========== D5.3.22 · SMTP =========="
SMTP_STATE="$(docker compose exec -T api sh -lc 'if [ -n "$SMTP_HOST" ] && [ -n "$MAIL_FROM" ]; then echo configured; else echo pending; fi')"
if [[ "$SMTP_STATE" == "configured" ]]; then
echo "SMTP configurado: la entrega documental puede operar"
else
echo "SMTP sigue sin configurar: no bloquea revisión, aprobación ni firma"
fi
echo
echo "========== D5.3.22 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.22 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.22 · 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.22: OK"
+115
View File
@@ -0,0 +1,115 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5323-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.23.1 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-23.1" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-23.1" ]]
grep -Fq "API_PHASE = 'D5.3.23.1'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.23.1 · Altas de campo y revisión de Inventarios'" web-v2/src/config/version.ts
echo "API: 0.19.3-23.1"
echo "WEB: 0.19.3-23.1"
echo
echo "========== D5.3.23.1 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT to_regclass('"'"'public.asset_field_discoveries'"'"');"' | grep -Fxq 'asset_field_discoveries'
for column in asset_id visit_id status discovery_notes observed_at created_by reviewed_at reviewed_by review_notes matched_asset_id; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT column_name FROM information_schema.columns WHERE table_name='asset_field_discoveries' AND column_name='$column';\"" | grep -Fxq "$column"
done
echo "Bandeja auditable de altas de campo presente: OK"
echo
echo "========== D5.3.23.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-23.1"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.23.1"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.23.1 · ALTA DE CAMPO =========="
grep -Fq 'assertMobileInspector(principal)' api-v3/src/asset-master/assets.service.ts
grep -Fq "visit.status !== 'IN_PROGRESS'" api-v3/src/asset-master/assets.service.ts
grep -Fq 'AssetDataOrigin.FIELD_SURVEY' api-v3/src/asset-master/assets.service.ts
grep -Fq 'AssetInformationStatus.DRAFT' api-v3/src/asset-master/assets.service.ts
grep -Fq 'fieldDiscoveryInspectionLinks.attach' api-v3/src/asset-master/assets.service.ts
if grep -Fq 'INSERT INTO inspection_' api-v3/src/asset-master/assets.service.ts; then exit 1; fi
grep -Fq 'INSERT INTO inspection_visit_assets' api-v3/src/inspection-operations/field-discovery-inspection-link.service.ts
grep -Fq 'INSERT INTO inspection_act_assets' api-v3/src/inspection-operations/field-discovery-inspection-link.service.ts
grep -Fq 'FIELD_DISCOVERY_ACT_NOT_EDITABLE' api-v3/src/inspection-operations/field-discovery-inspection-link.service.ts
echo "Inspector móvil puede registrar un elemento provisional y usarlo en la visita/Acta: OK"
echo
echo "========== D5.3.23.1 · REVISIÓN DE OFICINA =========="
grep -Fq "field-discoveries/:id/approve" api-v3/src/asset-master/assets.controller.ts
grep -Fq "field-discoveries/:id/match" api-v3/src/asset-master/assets.controller.ts
grep -Fq "field-discoveries/:id/reject" api-v3/src/asset-master/assets.controller.ts
grep -Fq "@RequirePermissions('assets.change_status')" api-v3/src/asset-master/assets.controller.ts
grep -Fq 'FIELD_DISCOVERY_MINIMUM_CONTEXT_MISSING' api-v3/src/asset-master/assets.service.ts
grep -Fq 'FIELD_DISCOVERY_MATCH_CONTEXT_MISMATCH' api-v3/src/asset-master/assets.service.ts
echo "Oficina puede corregir, aprobar, conciliar o rechazar sin borrar historia: OK"
echo
echo "========== D5.3.23.1 · FLUJO WEB =========="
grep -Fq 'Altas encontradas en campo' web-v2/src/pages/FieldDiscoveriesPage.tsx
grep -Fq 'Corregir ficha' web-v2/src/pages/FieldDiscoveriesPage.tsx
grep -Fq 'Conciliar' web-v2/src/pages/FieldDiscoveriesPage.tsx
grep -Fq '/inventarios/revision-campo' web-v2/src/app/App.tsx
grep -Fq 'assets.change_status' web-v2/src/app/App.tsx
echo "Bandeja de revisión de campo disponible en Inventarios: OK"
echo
echo "========== D5.3.23.1 · INMUTABILIDAD =========="
if grep -Fq 'UPDATE inspection_findings SET asset_id' api-v3/src/asset-master/assets.service.ts; then exit 1; fi
if grep -Fq 'UPDATE inspection_act_assets SET asset_id' api-v3/src/asset-master/assets.service.ts; then exit 1; fi
grep -Fq 'matched_asset_id' api-v3/src/database/migrations/1788717600000-phase-d5-3-23-field-discoveries.ts
grep -Fq 'INSPECTION_FINDING_RESPONSE_IMMUTABLE' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'INSPECTION_VISIT_ACT_ALREADY_EXISTS' api-v3/src/inspection-acts/inspection-acts.service.ts
echo "Conciliar no reescribe Actas ni Hallazgos históricos: OK"
echo
echo "========== D5.3.23.1 · SMTP =========="
SMTP_STATE="$(docker compose exec -T api sh -lc 'if [ -n "$SMTP_HOST" ] && [ -n "$MAIL_FROM" ]; then echo configured; else echo pending; fi')"
if [[ "$SMTP_STATE" == "configured" ]]; then
echo "SMTP configurado"
else
echo "SMTP sigue pendiente y no bloquea esta fase"
fi
echo
echo "========== D5.3.23.1 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.23.1 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.23.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.23.1: OK"
+121
View File
@@ -0,0 +1,121 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5324-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.24 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-24" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-24" ]]
grep -Fq "API_PHASE = 'D5.3.24'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.24 · Historial de contexto operativo'" web-v2/src/config/version.ts
echo "API: 0.19.3-24"
echo "WEB: 0.19.3-24"
echo
echo "========== D5.3.24 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT to_regclass('"'"'public.asset_context_history'"'"');"' | grep -Fxq 'asset_context_history'
for column in asset_id parent_id operational_area_id operator_company_id valid_from valid_until change_reason end_reason asset_version_number source request_id created_by ended_by; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT column_name FROM information_schema.columns WHERE table_name='asset_context_history' AND column_name='$column';\"" | grep -Fxq "$column"
done
ACTIVE_DUPLICATES="$(docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT COUNT(*) FROM (SELECT asset_id FROM asset_context_history WHERE valid_until IS NULL GROUP BY asset_id HAVING COUNT(*)>1) duplicated;"')"
[[ "$ACTIVE_DUPLICATES" == "0" ]]
ASSET_COUNT="$(docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT COUNT(*) FROM assets;"')"
ACTIVE_CONTEXT_COUNT="$(docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT COUNT(*) FROM asset_context_history WHERE valid_until IS NULL;"')"
[[ "$ACTIVE_CONTEXT_COUNT" == "$ASSET_COUNT" ]]
echo "Historial temporal de jerarquía, Área y Operadora presente: OK"
echo
echo "========== D5.3.24 · PERMISO DE CAMBIO =========="
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT code FROM permissions WHERE code='"'"'assets.manage_context'"'"';"' | grep -Fxq 'assets.manage_context'
for role in admin supervisor; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT r.code FROM role_permissions rp JOIN roles r ON r.id=rp.role_id JOIN permissions p ON p.id=rp.permission_id WHERE p.code='assets.manage_context' AND r.code='$role';\"" | grep -Fxq "$role"
done
if docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT r.code FROM role_permissions rp JOIN roles r ON r.id=rp.role_id JOIN permissions p ON p.id=rp.permission_id WHERE p.code='"'"'assets.manage_context'"'"' AND r.code='"'"'inspector'"'"';"' | grep -q .; then
echo "ERROR: Inspector no debe poder transferir el contexto de un Inventario consolidado"
exit 1
fi
echo "Cambio formal de contexto reservado a administración de oficina: OK"
echo
echo "========== D5.3.24 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-24"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.24"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.24 · HISTORIA NO DESTRUCTIVA =========="
grep -Fq 'FROM asset_versions version' api-v3/src/database/migrations/1788804000000-phase-d5-3-24-asset-context-history.ts
grep -Fq 'ASSET_CONTEXT_CHANGE_REQUIRES_TRANSFER' api-v3/src/asset-master/assets.service.ts
grep -Fq 'AssetVersionChangeType.CONTEXT_CHANGED' api-v3/src/asset-master/assets.service.ts
grep -Fq 'AuditAction.ASSET_CONTEXT_CHANGED' api-v3/src/asset-master/assets.service.ts
if grep -Fq 'DELETE FROM asset_context_history' api-v3/src/asset-master/assets.service.ts; then exit 1; fi
echo "Cambio actual conserva contexto anterior y genera versión/auditoría: OK"
echo
echo "========== D5.3.24 · RECONSTRUCCIÓN TEMPORAL =========="
grep -Fq 'FROM asset_context_history history' api-v3/src/asset-master/asset-temporal.service.ts
grep -Fq 'history.valid_from <= $2' api-v3/src/asset-master/asset-temporal.service.ts
grep -Fq 'history.valid_until IS NULL OR history.valid_until > $2' api-v3/src/asset-master/asset-temporal.service.ts
grep -Fq "@Get(':id/context-history')" api-v3/src/asset-master/assets.controller.ts
grep -Fq "@Post(':id/context')" api-v3/src/asset-master/assets.controller.ts
echo "La consulta histórica resuelve el contexto vigente en la fecha solicitada: OK"
echo
echo "========== D5.3.24 · FLUJO WEB =========="
grep -Fq 'Jerarquía, Área y Operadora' web-v2/src/features/assets/AssetContextHistoryPanel.tsx
grep -Fq 'Cambiar contexto' web-v2/src/features/assets/AssetContextHistoryPanel.tsx
grep -Fq 'Historial de contexto' web-v2/src/features/assets/AssetContextHistoryPanel.tsx
grep -Fq 'canDirectContextEdit' web-v2/src/pages/AssetEditorPage.tsx
grep -Fq "hasPermission('assets.manage_context')" web-v2/src/pages/AssetEditorPage.tsx
echo "Inventario muestra contexto vigente, historial y cambio con motivo/fecha: OK"
echo
echo "========== D5.3.24 · REGLAS PRESERVADAS =========="
grep -Fq 'INSPECTION_VISIT_ACT_ALREADY_EXISTS' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq 'INSPECTION_FINDING_RESPONSE_IMMUTABLE' api-v3/src/inspection-findings/inspection-findings.service.ts
if grep -Fq 'UPDATE inspection_findings SET asset_id' api-v3/src/asset-master/assets.service.ts; then exit 1; fi
echo "Una visita = una Acta y documentos/hallazgos históricos no se reescriben: OK"
echo
echo "========== D5.3.24 · SMTP =========="
SMTP_STATE="$(docker compose exec -T api sh -lc 'if [ -n "$SMTP_HOST" ] && [ -n "$MAIL_FROM" ]; then echo configured; else echo pending; fi')"
if [[ "$SMTP_STATE" == "configured" ]]; then echo "SMTP configurado"; else echo "SMTP sigue pendiente y no bloquea esta fase"; fi
echo
echo "========== D5.3.24 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.24 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.24 · 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.24: OK"
+119
View File
@@ -0,0 +1,119 @@
#!/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}"
TMP_MIGRATION="/tmp/dhv2-d5325-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.25 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-25" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-25" ]]
grep -Fq "API_PHASE = 'D5.3.25'" api-v3/src/version.ts
grep -Fq "APP_PHASE = 'Fase D5.3.25 · Historial de verificaciones'" web-v2/src/config/version.ts
echo "API: 0.19.3-25"
echo "WEB: 0.19.3-25"
echo
echo "========== D5.3.25 · DB Y MIGRACIONES =========="
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT to_regclass('"'"'public.inspection_finding_verification_events'"'"');"' | grep -Fxq 'inspection_finding_verification_events'
for column in finding_id verification_visit_id event_type occurred_at target_control_on previous_control_on next_control_on outcome notes actor_user_id actor_username created_at; do
docker compose exec -T db sh -lc "psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -Atc \"SELECT column_name FROM information_schema.columns WHERE table_name='inspection_finding_verification_events' AND column_name='$column';\"" | grep -Fxq "$column"
done
TRIGGER_COUNT="$(docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT COUNT(*) FROM pg_trigger WHERE tgname='"'"'trg_inspection_verification_result_immutable'"'"' AND NOT tgisinternal;"')"
[[ "$TRIGGER_COUNT" == "1" ]]
echo "Ledger append-only y protección de resultados presentes: OK"
echo
echo "========== D5.3.25 · BACKFILL HISTÓRICO =========="
LINK_COUNT="$(docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT COUNT(*) FROM inspection_finding_verification_visits;"')"
PLANNED_EVENT_COUNT="$(docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT COUNT(*) FROM inspection_finding_verification_events WHERE event_type='"'"'VISIT_PLANNED'"'"';"')"
RESULT_LINK_COUNT="$(docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT COUNT(*) FROM inspection_finding_verification_visits WHERE outcome IS NOT NULL;"')"
RESULT_EVENT_COUNT="$(docker compose exec -T db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT COUNT(*) FROM inspection_finding_verification_events WHERE event_type='"'"'RESULT_RECORDED'"'"';"')"
(( PLANNED_EVENT_COUNT >= LINK_COUNT ))
(( RESULT_EVENT_COUNT >= RESULT_LINK_COUNT ))
echo "Antecedentes de visitas y resultados reconstruidos sin borrar historia: OK"
echo
echo "========== D5.3.25 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-25"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.25"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.25 · RESULTADOS INMUTABLES =========="
grep -Fq 'VERIFICATION_RESULT_IMMUTABLE' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq 'protect_inspection_verification_result_immutability' api-v3/src/database/migrations/1788890400000-phase-d5-3-25-verification-event-history.ts
grep -Fq 'InspectionFindingVerificationEventType.RESULT_RECORDED' api-v3/src/inspection-verifications/inspection-verifications.service.ts
echo "Un resultado de campo registrado no puede sobrescribirse: OK"
echo
echo "========== D5.3.25 · REPROGRAMACIONES APPEND-ONLY =========="
grep -Fq 'InspectionFindingVerificationEventType.CONTROL_DATE_DEFINED' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'InspectionFindingVerificationEventType.CONTROL_DATE_CHANGED' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'InspectionFindingVerificationEventType.CONTROL_DATE_CLEARED' api-v3/src/inspection-findings/inspection-findings.service.ts
if grep -Rnh 'DELETE FROM inspection_finding_verification_events' api-v3/src/inspection-findings api-v3/src/inspection-verifications; then exit 1; fi
echo "Cada cambio de fecha conserva la fecha anterior como evento: OK"
echo
echo "========== D5.3.25 · VERIFICACIONES SUCESIVAS =========="
grep -Fq 'InspectionFindingVerificationEventType.VISIT_PLANNED' api-v3/src/inspection-verifications/inspection-verifications.service.ts
grep -Fq "visit.status IN ('DRAFT', 'PLANNED', 'IN_PROGRESS')" api-v3/src/inspection-verifications/inspection-verifications.service.ts
if grep -Fq 'DELETE FROM inspection_finding_verification_visits' api-v3/src/inspection-verifications/inspection-verifications.service.ts; then exit 1; fi
echo "Múltiples verificaciones sucesivas quedan como intentos históricos independientes: OK"
echo
echo "========== D5.3.25 · EXPEDIENTE WEB =========="
grep -Fq "@Get(':id/verification-history')" api-v3/src/inspection-findings/inspection-findings.controller.ts
grep -Fq 'inspection_finding_verification_events event' api-v3/src/inspection-findings/inspection-findings.service.ts
grep -Fq 'Historial completo de verificaciones' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
grep -Fq 'verificationHistory.map' web-v2/src/features/inspections/FindingOfficeWorkspace.tsx
echo "Hallazgo muestra cronología completa de fechas, visitas y resultados: OK"
echo
echo "========== D5.3.25 · REGLAS PRESERVADAS =========="
grep -Fq 'INSPECTION_VISIT_ACT_ALREADY_EXISTS' api-v3/src/inspection-acts/inspection-acts.service.ts
grep -Fq 'INSPECTION_FINDING_RESPONSE_IMMUTABLE' api-v3/src/inspection-findings/inspection-findings.service.ts
if grep -Fq 'UPDATE inspection_findings SET asset_id' api-v3/src/asset-master/assets.service.ts; then exit 1; fi
echo "Una visita = una Acta; respuesta original, hallazgos y documentos históricos siguen protegidos: OK"
echo
echo "========== D5.3.25 · SMTP =========="
SMTP_STATE="$(docker compose exec -T api sh -lc 'if [ -n "$SMTP_HOST" ] && [ -n "$MAIL_FROM" ]; then echo configured; else echo pending; fi')"
if [[ "$SMTP_STATE" == "configured" ]]; then echo "SMTP configurado"; else echo "SMTP sigue pendiente y no bloquea esta fase"; fi
echo
echo "========== D5.3.25 · HIGIENE DE CÓDIGO =========="
if grep -RnhE --include='*.ts' --include='*.tsx' '^[[:space:]]*(//|/\*|\*)' api-v3/src web-v2/src; then
echo "ERROR: quedaron comentarios narrativos en código de producción"
exit 1
fi
if grep -nE '/\*|\*/' web-v2/src/styles.css; then
echo "ERROR: quedaron comentarios internos en estilos de producción"
exit 1
fi
echo "Código de producción preparado para revisión externa: OK"
echo
echo "========== D5.3.25 · SEGURIDAD HEREDADA =========="
STRONG_BACKUP="$(find /root/DH_V2_BACKUPS -maxdepth 1 -type d -name 'INVENTARIO_A_REVISAR_*' -printf '%T@ %p\n' | sort -nr | head -n1 | cut -d' ' -f2-)"
[[ -n "$STRONG_BACKUP" && -s "$STRONG_BACKUP/BACKUP_OK.txt" ]]
grep -Fq 'INVENTARIO A REVISAR' "$STRONG_BACKUP/BACKUP_OK.txt"
echo "Backup fuerte de referencia: $STRONG_BACKUP"
echo
echo "========== D5.3.25 · 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.25: OK"
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
cd "$ROOT"
DB_SERVICE="${DB_SERVICE:-db}"
API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}"
psql_app() {
docker compose exec -T "$DB_SERVICE" sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -v ON_ERROR_STOP=1' <<<"$1"
}
echo "========== D5.3.3 · VERSIONES =========="
API_VERSION="$(node -p "require('./api-v3/package.json').version")"
WEB_VERSION="$(node -p "require('./web-v2/package.json').version")"
[[ "$API_VERSION" == "0.19.3-3" ]]
[[ "$WEB_VERSION" == "0.19.3-3" ]]
echo "API: $API_VERSION"
echo "WEB: $WEB_VERSION"
echo
echo "========== D5.3.3 · MIGRACIONES =========="
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -q "Pending migrations: no" <<<"$MIGRATION_OUTPUT"
echo
echo "========== D5.3.3 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -q '"version":"0.19.3-3"' <<<"$HEALTH"
grep -q '"phase":"D5.3.3"' <<<"$HEALTH"
echo
echo "========== D5.3.3 · ENDPOINTS PROTEGIDOS =========="
CODE="$(curl -sS -o /tmp/dhv2-d533-registry.txt -w '%{http_code}' "$API_URL/source-documents")"
[[ "$CODE" == "401" ]]
echo "/source-documents sin sesión: 401 OK"
CODE="$(curl -sS -o /tmp/dhv2-d533-assets-registry.txt -w '%{http_code}' "$API_URL/assets/550e8400-e29b-41d4-a716-446655440000/registry")"
[[ "$CODE" == "401" ]]
echo "/assets/:id/registry sin sesión: 401 OK"
echo
echo "========== D5.3.3 · MODELO =========="
echo "operational_status_type|$(psql_app "SELECT COUNT(*) FROM pg_type WHERE typname='asset_operational_status';")"
echo "assets_operational_status|$(psql_app "SELECT COUNT(*) FROM information_schema.columns WHERE table_name='assets' AND column_name='operational_status';")"
echo "organization_profiles|$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name='organization_profiles';")"
echo "organization_memberships|$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name='organization_memberships';")"
echo "source_documents|$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name='source_documents';")"
echo "asset_source_documents|$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name='asset_source_documents';")"
echo "external_identifiers|$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name='asset_external_identifiers';")"
echo "legal_rights|$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name='area_legal_rights';")"
echo "legal_right_organizations|$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name='area_legal_right_organizations';")"
echo "area_relation_role|$(psql_app "SELECT COUNT(*) FROM information_schema.columns WHERE table_name='area_company_relations' AND column_name='relation_role';")"
[[ "$(psql_app "SELECT COUNT(*) FROM pg_type WHERE typname='asset_operational_status';")" == "1" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name IN ('organization_profiles','organization_memberships','source_documents','asset_source_documents','asset_external_identifiers','area_legal_rights','area_legal_right_organizations');")" == "7" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM information_schema.columns WHERE table_name='area_company_relations' AND column_name IN ('relation_role','participation_percent','legal_instrument','source_document_id');")" == "4" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM permissions WHERE code IN ('asset_registry.read','asset_registry.manage','assets.change_operational_status');")" == "3" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM asset_types WHERE code='area' AND name='Área';")" == "1" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM asset_types WHERE code='empresa' AND name='Organización';")" == "1" ]]
echo
echo "========== D5.3.3 · INTEGRIDAD =========="
[[ "$(psql_app "SELECT COUNT(*) FROM pg_trigger WHERE tgname IN ('trg_organization_profiles_role','trg_organization_memberships_role','trg_area_legal_rights_role','trg_area_legal_right_org_role') AND NOT tgisinternal;")" == "4" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM pg_indexes WHERE indexname IN ('uq_organization_memberships_active_role','uq_area_company_relations_active_role','uq_area_legal_right_org_active_role','uq_asset_external_identifiers_active_namespace_value');")" == "4" ]]
echo "Roles, vigencias e identificadores protegidos: OK"
echo
echo "========== D5.3.3 · SOURCE / DASHBOARD =========="
grep -q "OrganizationKind.UTE" api-v3/src/asset-master/asset-registry.service.ts
grep -q "relation_role = 'OPERATOR'" api-v3/src/asset-master/assets.service.ts
grep -q "Estado operativo" web-v2/src/pages/AssetEditorPage.tsx
grep -q "Organización operadora" web-v2/src/pages/AssetEditorPage.tsx
grep -q "0.19.3-3" web-v2/src/config/version.ts
bash -n scripts/dev-clean-demo.sh
echo "Semántica Área / Organización y estado operativo: OK"
echo
echo "========== D5.3.3 · DEMO =========="
DEMO_COUNT="$(psql_app "SELECT COUNT(*) FROM assets WHERE code LIKE 'DEV-DEMO-%';")"
echo "Activos DEV-DEMO actuales: $DEMO_COUNT"
if [[ "$DEMO_COUNT" -gt "0" ]]; then
echo "El demo previo fue preservado por la migración. Puede limpiarse y resembrarse después del deploy para aplicar documento fuente y estado operativo."
fi
echo
echo "========== D5.3.3 · CONTENEDORES =========="
docker compose ps -a
echo
echo "ACEPTACIÓN DE FASE D5.3.3: OK"
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
cd "$ROOT"
API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}"
DB_SERVICE="${DB_SERVICE:-db}"
psql_app() {
docker compose exec -T "$DB_SERVICE" sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -v ON_ERROR_STOP=1' <<<"$1"
}
echo "========== D5.3.4 · VERSIONES =========="
API_VERSION="$(node -p "require('./api-v3/package.json').version")"
WEB_VERSION="$(node -p "require('./web-v2/package.json').version")"
[[ "$API_VERSION" == "0.19.3-4" ]]
[[ "$WEB_VERSION" == "0.19.3-4" ]]
echo "API: $API_VERSION"
echo "WEB: $WEB_VERSION"
echo
echo "========== D5.3.4 · MIGRACIONES =========="
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -q "Pending migrations: no" <<<"$MIGRATION_OUTPUT"
echo
echo "========== D5.3.4 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -q '"version":"0.19.3-4"' <<<"$HEALTH"
grep -q '"phase":"D5.3.4"' <<<"$HEALTH"
echo
echo "========== D5.3.4 · ASSET CENTER PROTEGIDO =========="
CODE="$(curl -sS -o /tmp/dhv2-d534-tree.txt -w '%{http_code}' "$API_URL/assets/tree")"
[[ "$CODE" == "401" ]]
echo "/assets/tree sin sesión: 401 OK"
echo
echo "========== D5.3.4 · MODELO D5.3.3 DISPONIBLE =========="
[[ "$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name IN ('organization_profiles','organization_memberships','source_documents','asset_source_documents','asset_external_identifiers','area_legal_rights','area_legal_right_organizations');")" == "7" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM information_schema.columns WHERE table_name='assets' AND column_name='operational_status';")" == "1" ]]
echo "Registro documental, Organizaciones y estado operativo: OK"
echo
echo "========== D5.3.4 · API =========="
grep -q "@Get('tree')" api-v3/src/asset-master/assets.controller.ts
grep -q "asset_external_identifiers external_id" api-v3/src/asset-master/assets.service.ts
grep -q 'assetsNeedValidation' api-v3/src/dashboard/dashboard.service.ts
grep -q 'plannedInspections' api-v3/src/dashboard/dashboard.service.ts
echo "Jerarquía, búsqueda por identificador y resumen operativo: OK"
echo
echo "========== D5.3.4 · DASHBOARD / UX =========="
grep -q 'AssetCenterTabs' web-v2/src/pages/AssetsPage.tsx
grep -q 'Pendientes de validar' web-v2/src/pages/AssetsPage.tsx
grep -q 'Agregar activo aquí' web-v2/src/pages/AssetEditorPage.tsx
grep -q 'Documentos y registro' web-v2/src/pages/AssetEditorPage.tsx
grep -q 'Configuración avanzada' web-v2/src/pages/AssetTypesPage.tsx
grep -q 'Roles y permisos' web-v2/src/layout/AppLayout.tsx
grep -q 'Qué requiere atención' web-v2/src/pages/DashboardPage.tsx
grep -q '0.19.3-4' web-v2/src/config/version.ts
echo "Asset Center, alta contextual y administración simplificada: OK"
echo
echo "========== D5.3.4 · CONTENEDORES =========="
docker compose ps -a
echo
echo "ACEPTACIÓN DE FASE D5.3.4: OK"
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
cd "$ROOT"
API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}"
DB_SERVICE="${DB_SERVICE:-db}"
psql_app() {
docker compose exec -T "$DB_SERVICE" sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER"' <<<"$1"
}
echo "========== D5.3.5.1 · VERSIONES =========="
API_VERSION="$(node -p "require('./api-v3/package.json').version")"
WEB_VERSION="$(node -p "require('./web-v2/package.json').version")"
[[ "$API_VERSION" == "0.19.3-5.1" ]]
[[ "$WEB_VERSION" == "0.19.3-5.1" ]]
echo "API: $API_VERSION"
echo "WEB: $WEB_VERSION"
echo
echo "========== D5.3.5.1 · MIGRACIONES =========="
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -q "Pending migrations: no" <<<"$MIGRATION_OUTPUT"
echo
echo "========== D5.3.5.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -q '"version":"0.19.3-5.1"' <<<"$HEALTH"
grep -q '"phase":"D5.3.5.1"' <<<"$HEALTH"
echo
echo "========== D5.3.5.1 · RUTAS PROTEGIDAS =========="
CODE="$(curl -sS -o /tmp/dhv2-d535-imports.txt -w '%{http_code}' "$API_URL/asset-imports")"
[[ "$CODE" == "401" ]]
echo "/asset-imports sin sesión: 401 OK"
CODE="$(curl -sS -o /tmp/dhv2-d535-tree.txt -w '%{http_code}' "$API_URL/assets/tree-children")"
[[ "$CODE" == "401" ]]
echo "/assets/tree-children sin sesión: 401 OK"
echo
echo "========== D5.3.5.1 · MODELO DE IMPORTACIÓN =========="
grep -q 'export interface ImportBatchRow' api-v3/src/asset-imports/asset-imports.service.ts
echo "Contrato público ImportBatchRow: OK"
[[ "$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name IN ('asset_import_batches','asset_import_rows');")" == "2" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM pg_type WHERE typname IN ('asset_import_batch_status','asset_import_row_status','asset_import_suggested_action');")" == "3" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM permissions WHERE code IN ('asset_imports.read','asset_imports.manage');")" == "2" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM pg_indexes WHERE indexname IN ('idx_assets_code_trgm','idx_assets_name_trgm','idx_asset_external_identifiers_value_trgm','idx_asset_attribute_values_text_trgm');")" == "4" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM pg_extension WHERE extname='pg_trgm';")" == "1" ]]
[[ "$(psql_app "SELECT has_table_privilege(:'app_user','asset_import_batches','SELECT') AND has_table_privilege(:'app_user','asset_import_batches','INSERT') AND has_table_privilege(:'app_user','asset_import_batches','UPDATE');")" == "t" ]]
[[ "$(psql_app "SELECT has_table_privilege(:'app_user','asset_import_batches','DELETE');")" == "f" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM typeorm_migrations WHERE name='PhaseD535ImportCenterScalability1787594400000';")" == "1" ]]
echo "Staging, permisos, pg_trgm y protección contra DELETE: OK"
echo
echo "========== D5.3.5.1 · PARSER XLSX =========="
docker compose exec -T api sh -lc 'command -v unzip >/dev/null && unzip -v >/dev/null'
grep -q 'MAX_ASSET_IMPORT_BYTES' api-v3/src/asset-imports/asset-import-parser.ts
grep -q 'MENDOZA_INVENTORY_V1' api-v3/src/asset-imports/asset-import-profiles.ts
grep -q 'MENDOZA_YACIMIENTOS_V1' api-v3/src/asset-imports/asset-import-profiles.ts
grep -q 'INVENTORY_ID_MULTIPLE_LOCATIONS' api-v3/src/asset-imports/asset-import-profiles.ts
echo "Parser XLSX/CSV, perfiles y conflictos: OK"
echo
echo "========== D5.3.5.1 · NO MODIFICA EL MAESTRO =========="
grep -q 'INSERT INTO asset_import_batches' api-v3/src/asset-imports/asset-imports.service.ts
! grep -Eq 'INSERT INTO assets[[:space:]]*\\(' api-v3/src/asset-imports/asset-imports.service.ts
! grep -Eq 'UPDATE assets[[:space:]]+SET' api-v3/src/asset-imports/asset-imports.service.ts
echo "El Centro de Importaciones sólo analiza/stagea: OK"
echo
echo "========== D5.3.5.1 · ASSET CENTER ESCALABLE =========="
grep -q "@Get('tree-children')" api-v3/src/asset-master/assets.controller.ts
grep -q 'listAssetTreeChildren' web-v2/src/features/assets/AssetHierarchyView.tsx
! grep -q 'listAssetTree(' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -q 'Los hijos se cargan recién al expandir' web-v2/src/features/assets/AssetHierarchyView.tsx
echo "Jerarquía lazy-loading: OK"
echo
echo "========== D5.3.5.1 · DASHBOARD =========="
grep -q 'Centro de importaciones' web-v2/src/pages/AssetImportsPage.tsx
grep -q 'No crea ni modifica activos' web-v2/src/pages/AssetImportsPage.tsx
grep -q "path=\"/importaciones\"" web-v2/src/app/App.tsx
grep -q "label: 'Importaciones'" web-v2/src/layout/AppLayout.tsx
grep -q '0.19.3-5.1' web-v2/src/config/version.ts
echo "Centro de Importaciones y navegación: OK"
echo
echo "========== D5.3.5.1 · CONTENEDORES =========="
docker compose ps -a
echo
echo "ACEPTACIÓN DE FASE D5.3.5.1: OK"
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
cd "$ROOT"
API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}"
DB_SERVICE="${DB_SERVICE:-db}"
psql_app() {
docker compose exec -T "$DB_SERVICE" sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER"' <<<"$1"
}
echo "========== D5.3.5 · VERSIONES =========="
API_VERSION="$(node -p "require('./api-v3/package.json').version")"
WEB_VERSION="$(node -p "require('./web-v2/package.json').version")"
[[ "$API_VERSION" == "0.19.3-5" ]]
[[ "$WEB_VERSION" == "0.19.3-5" ]]
echo "API: $API_VERSION"
echo "WEB: $WEB_VERSION"
echo
echo "========== D5.3.5 · MIGRACIONES =========="
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -q "Pending migrations: no" <<<"$MIGRATION_OUTPUT"
echo
echo "========== D5.3.5 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -q '"version":"0.19.3-5"' <<<"$HEALTH"
grep -q '"phase":"D5.3.5"' <<<"$HEALTH"
echo
echo "========== D5.3.5 · RUTAS PROTEGIDAS =========="
CODE="$(curl -sS -o /tmp/dhv2-d535-imports.txt -w '%{http_code}' "$API_URL/asset-imports")"
[[ "$CODE" == "401" ]]
echo "/asset-imports sin sesión: 401 OK"
CODE="$(curl -sS -o /tmp/dhv2-d535-tree.txt -w '%{http_code}' "$API_URL/assets/tree-children")"
[[ "$CODE" == "401" ]]
echo "/assets/tree-children sin sesión: 401 OK"
echo
echo "========== D5.3.5 · MODELO DE IMPORTACIÓN =========="
[[ "$(psql_app "SELECT COUNT(*) FROM information_schema.tables WHERE table_name IN ('asset_import_batches','asset_import_rows');")" == "2" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM pg_type WHERE typname IN ('asset_import_batch_status','asset_import_row_status','asset_import_suggested_action');")" == "3" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM permissions WHERE code IN ('asset_imports.read','asset_imports.manage');")" == "2" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM pg_indexes WHERE indexname IN ('idx_assets_code_trgm','idx_assets_name_trgm','idx_asset_external_identifiers_value_trgm','idx_asset_attribute_values_text_trgm');")" == "4" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM pg_extension WHERE extname='pg_trgm';")" == "1" ]]
[[ "$(psql_app "SELECT has_table_privilege(:'app_user','asset_import_batches','SELECT') AND has_table_privilege(:'app_user','asset_import_batches','INSERT') AND has_table_privilege(:'app_user','asset_import_batches','UPDATE');")" == "t" ]]
[[ "$(psql_app "SELECT has_table_privilege(:'app_user','asset_import_batches','DELETE');")" == "f" ]]
[[ "$(psql_app "SELECT COUNT(*) FROM typeorm_migrations WHERE name='PhaseD535ImportCenterScalability1787594400000';")" == "1" ]]
echo "Staging, permisos, pg_trgm y protección contra DELETE: OK"
echo
echo "========== D5.3.5 · PARSER XLSX =========="
docker compose exec -T api sh -lc 'command -v unzip >/dev/null && unzip -v >/dev/null'
grep -q 'MAX_ASSET_IMPORT_BYTES' api-v3/src/asset-imports/asset-import-parser.ts
grep -q 'MENDOZA_INVENTORY_V1' api-v3/src/asset-imports/asset-import-profiles.ts
grep -q 'MENDOZA_YACIMIENTOS_V1' api-v3/src/asset-imports/asset-import-profiles.ts
grep -q 'INVENTORY_ID_MULTIPLE_LOCATIONS' api-v3/src/asset-imports/asset-import-profiles.ts
echo "Parser XLSX/CSV, perfiles y conflictos: OK"
echo
echo "========== D5.3.5 · NO MODIFICA EL MAESTRO =========="
grep -q 'INSERT INTO asset_import_batches' api-v3/src/asset-imports/asset-imports.service.ts
! grep -Eq 'INSERT INTO assets[[:space:]]*\\(' api-v3/src/asset-imports/asset-imports.service.ts
! grep -Eq 'UPDATE assets[[:space:]]+SET' api-v3/src/asset-imports/asset-imports.service.ts
echo "El Centro de Importaciones sólo analiza/stagea: OK"
echo
echo "========== D5.3.5 · ASSET CENTER ESCALABLE =========="
grep -q "@Get('tree-children')" api-v3/src/asset-master/assets.controller.ts
grep -q 'listAssetTreeChildren' web-v2/src/features/assets/AssetHierarchyView.tsx
! grep -q 'listAssetTree(' web-v2/src/features/assets/AssetHierarchyView.tsx
grep -q 'Los hijos se cargan recién al expandir' web-v2/src/features/assets/AssetHierarchyView.tsx
echo "Jerarquía lazy-loading: OK"
echo
echo "========== D5.3.5 · DASHBOARD =========="
grep -q 'Centro de importaciones' web-v2/src/pages/AssetImportsPage.tsx
grep -q 'No crea ni modifica activos' web-v2/src/pages/AssetImportsPage.tsx
grep -q "path=\"/importaciones\"" web-v2/src/app/App.tsx
grep -q "label: 'Importaciones'" web-v2/src/layout/AppLayout.tsx
grep -q '0.19.3-5' web-v2/src/config/version.ts
echo "Centro de Importaciones y navegación: OK"
echo
echo "========== D5.3.5 · CONTENEDORES =========="
docker compose ps -a
echo
echo "ACEPTACIÓN DE FASE D5.3.5: OK"
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
cd "$ROOT"
API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}"
echo "========== D5.3.6.3 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-6.3" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-6.3" ]]
echo "API: 0.19.3-6.3"
echo "WEB: 0.19.3-6.3"
echo
echo "========== D5.3.6.3 · MIGRACIONES =========="
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -Fq 'Pending migrations: no' <<<"$MIGRATION_OUTPUT"
echo
echo "========== D5.3.6.3 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-6.3"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.6.3"' <<<"$HEALTH"
echo
echo "========== D5.3.6.3 · RUTAS PROTEGIDAS =========="
CODE="$(curl -sS -o /tmp/dhv2-d5363-reconcile.txt -w '%{http_code}' -X POST "$API_URL/asset-imports/00000000-0000-4000-8000-000000000000/reconcile")"
[[ "$CODE" == "401" ]]
echo "/asset-imports/:id/reconcile sin sesión: 401 OK"
echo
echo "========== D5.3.6.3 · NORMALIZACIÓN =========="
grep -Fq 'technicalNormalizationSuggestion' api-v3/src/asset-imports/asset-import-profiles.ts
grep -Fq 'normalizedFamily' api-v3/src/asset-imports/asset-import-profiles.ts
grep -Fq 'normalizedSubtype' api-v3/src/asset-imports/asset-import-profiles.ts
grep -Fq 'conditionStatusSuggestion' api-v3/src/asset-imports/asset-import-profiles.ts
echo "Familia/subtipo y estados separados: OK"
echo
echo "========== D5.3.6.3 · MATCHING CONSERVADOR =========="
grep -Fq 'INVENTORY_ID_OR_CODE_EXACT' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'AREA_FIELD_EXACT' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'ORGANIZATION_NAME_EXACT' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'MATCH_MULTIPLE_ASSETS' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'masterModified: false' api-v3/src/asset-imports/asset-imports.service.ts
if grep -Eq 'INSERT INTO assets[[:space:]]*\(' api-v3/src/asset-imports/asset-imports.service.ts; then
echo "ERROR: D5.3.6 no debe insertar activos"; exit 1
fi
if grep -Eq 'UPDATE assets[[:space:]]+SET' api-v3/src/asset-imports/asset-imports.service.ts; then
echo "ERROR: D5.3.6 no debe actualizar activos"; exit 1
fi
echo "Conciliación sin escrituras al Maestro: OK"
grep -Fq 'afterData: { ...summary }' api-v3/src/asset-imports/asset-imports.service.ts
echo "Resumen de conciliación compatible con auditoría: OK"
echo
echo "========== D5.3.6.3 · DASHBOARD =========="
grep -Fq 'Conciliar con Maestro' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'Boolean(detail.analysis?.reconciliation)' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'Analizables' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq "detail.profileCode === 'MENDOZA_YACIMIENTOS_V1'" web-v2/src/pages/AssetImportsPage.tsx
grep -Fq '1 · Análisis' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq '4 · Importación' web-v2/src/pages/AssetImportsPage.tsx
echo "Preview por perfil y flujo por etapas: OK"
echo
echo "========== D5.3.6.3 · CONTENEDORES =========="
docker compose ps -a
echo
echo "ACEPTACIÓN DE FASE D5.3.6.3: OK"
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
cd "$ROOT"
API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}"
echo "========== D5.3.6 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-6" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-6" ]]
echo "API: 0.19.3-6"
echo "WEB: 0.19.3-6"
echo
echo "========== D5.3.6 · MIGRACIONES =========="
MIGRATION_OUTPUT="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
echo "$MIGRATION_OUTPUT"
grep -Fq 'Pending migrations: no' <<<"$MIGRATION_OUTPUT"
echo
echo "========== D5.3.6 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-6"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.6"' <<<"$HEALTH"
echo
echo "========== D5.3.6 · RUTAS PROTEGIDAS =========="
CODE="$(curl -sS -o /tmp/dhv2-d536-reconcile.txt -w '%{http_code}' -X POST "$API_URL/asset-imports/00000000-0000-4000-8000-000000000000/reconcile")"
[[ "$CODE" == "401" ]]
echo "/asset-imports/:id/reconcile sin sesión: 401 OK"
echo
echo "========== D5.3.6 · NORMALIZACIÓN =========="
grep -Fq 'technicalNormalizationSuggestion' api-v3/src/asset-imports/asset-import-profiles.ts
grep -Fq 'normalizedFamily' api-v3/src/asset-imports/asset-import-profiles.ts
grep -Fq 'normalizedSubtype' api-v3/src/asset-imports/asset-import-profiles.ts
grep -Fq 'conditionStatusSuggestion' api-v3/src/asset-imports/asset-import-profiles.ts
echo "Familia/subtipo y estados separados: OK"
echo
echo "========== D5.3.6 · MATCHING CONSERVADOR =========="
grep -Fq 'INVENTORY_ID_OR_CODE_EXACT' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'AREA_FIELD_EXACT' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'ORGANIZATION_NAME_EXACT' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'MATCH_MULTIPLE_ASSETS' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'masterModified: false' api-v3/src/asset-imports/asset-imports.service.ts
if grep -Eq 'INSERT INTO assets[[:space:]]*\(' api-v3/src/asset-imports/asset-imports.service.ts; then
echo "ERROR: D5.3.6 no debe insertar activos"; exit 1
fi
if grep -Eq 'UPDATE assets[[:space:]]+SET' api-v3/src/asset-imports/asset-imports.service.ts; then
echo "ERROR: D5.3.6 no debe actualizar activos"; exit 1
fi
echo "Conciliación sin escrituras al Maestro: OK"
echo
echo "========== D5.3.6 · DASHBOARD =========="
grep -Fq 'Conciliar con Maestro' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'Analizables' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq "detail.profileCode === 'MENDOZA_YACIMIENTOS_V1'" web-v2/src/pages/AssetImportsPage.tsx
grep -Fq '1 · Análisis' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq '4 · Importación' web-v2/src/pages/AssetImportsPage.tsx
echo "Preview por perfil y flujo por etapas: OK"
echo
echo "========== D5.3.6 · CONTENEDORES =========="
docker compose ps -a
echo
echo "ACEPTACIÓN DE FASE D5.3.6: OK"
+136
View File
@@ -0,0 +1,136 @@
#!/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-d537-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.7.1 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-7.1" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-7.1" ]]
grep -Fq "API_PHASE = 'D5.3.7.1'" api-v3/src/version.ts
echo "API: 0.19.3-7.1"
echo "WEB: 0.19.3-7.1"
echo
echo "========== D5.3.7.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.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.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-7.1"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.7.1"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.7.1 · RUTAS PROTEGIDAS =========="
CODE="$(curl -sS -o /tmp/dhv2-d537-orgs-$$.txt -w '%{http_code}' "$API_URL/asset-imports/context/organizations")"
[[ "$CODE" == "401" ]]
CODE="$(curl -sS -o /tmp/dhv2-d537-plan-$$.txt -w '%{http_code}' "$API_URL/asset-imports/$DUMMY_UUID/plan")"
[[ "$CODE" == "401" ]]
CODE="$(curl -sS -o /tmp/dhv2-d537-apply-$$.txt -w '%{http_code}' -X POST -H 'Content-Type: application/json' -d '{"planHash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' "$API_URL/asset-imports/$DUMMY_UUID/apply")"
[[ "$CODE" == "401" ]]
rm -f /tmp/dhv2-d537-orgs-$$.txt /tmp/dhv2-d537-plan-$$.txt /tmp/dhv2-d537-apply-$$.txt
echo "Contexto, plan y aplicación requieren sesión: 401 OK"
echo
echo "========== D5.3.7.1 · MODELO Y PERMISOS =========="
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.asset_import_plans') IS NOT NULL AND to_regclass('public.asset_import_plan_items') IS NOT NULL THEN 'TABLES_OK' ELSE 'TABLES_BAD' END;
SELECT CASE WHEN EXISTS (SELECT 1 FROM permissions WHERE code='asset_imports.apply') THEN 'PERMISSION_OK' ELSE 'PERMISSION_BAD' END;
SELECT CASE WHEN NOT EXISTS (
SELECT role.code FROM roles role
WHERE role.code IN ('admin','director')
AND NOT EXISTS (
SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id
WHERE rp.role_id=role.id AND p.code='asset_imports.apply'
)
) THEN 'ROLE_GRANTS_OK' ELSE 'ROLE_GRANTS_BAD' END;
SELECT CASE WHEN
has_table_privilege(:'app_user','asset_import_plans','SELECT') AND
has_table_privilege(:'app_user','asset_import_plans','INSERT') AND
has_table_privilege(:'app_user','asset_import_plans','UPDATE') AND
NOT has_table_privilege(:'app_user','asset_import_plans','DELETE') AND
has_table_privilege(:'app_user','asset_import_plan_items','SELECT') AND
has_table_privilege(:'app_user','asset_import_plan_items','INSERT') AND
has_table_privilege(:'app_user','asset_import_plan_items','UPDATE') AND
NOT has_table_privilege(:'app_user','asset_import_plan_items','DELETE')
THEN 'APP_PRIVILEGES_OK' ELSE 'APP_PRIVILEGES_BAD' END;
SELECT CASE WHEN EXISTS (SELECT 1 FROM typeorm_migrations WHERE name='PhaseD537ImportPlanningApplication1787680800000') THEN 'MIGRATION_OK' ELSE 'MIGRATION_BAD' END;
SQL
)"
echo "$DB_CHECK"
grep -Fq 'TABLES_OK' <<<"$DB_CHECK"
grep -Fq 'PERMISSION_OK' <<<"$DB_CHECK"
grep -Fq 'ROLE_GRANTS_OK' <<<"$DB_CHECK"
grep -Fq 'APP_PRIVILEGES_OK' <<<"$DB_CHECK"
grep -Fq 'MIGRATION_OK' <<<"$DB_CHECK"
echo
echo "========== D5.3.7.1 · PLAN SEGURO =========="
grep -Fq 'export interface PlanItemRow' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "status === 'CONFLICT' || status === 'WARNING'" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'PLAN_SOURCE_GROUP_REQUIRES_DECISION' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "organizationKind:/^UTE\\b/i" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "operationalAreaEntityKey:explicitlyUnassigned?null:areaItem.entityKey" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "const operatorUnassigned = isExplicitlyUnassignedOperator(operator)" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "operatorUnassigned ? '' : plainImportKey(operator)" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'generatedImportCode(plan.id, item.assetTypeCode, item.entityKey)' api-v3/src/asset-imports/asset-imports.service.ts
echo "Warnings bloqueantes, entidades únicas, UTE, operador no asignado y jerarquía controlada: OK"
echo
echo "========== D5.3.7.1 · APLICACIÓN TRANSACCIONAL =========="
grep -Fq 'async applyPlan' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'this.dataSource.transaction' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'ASSET_IMPORT_PLAN_STALE' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'ASSET_IMPORT_PARENT_RULE_INVALID' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'ASSET_IMPORT_OPERATIONAL_CONTEXT_INCOMPLETE' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'const areaKey=String(item.payload.operationalAreaEntityKey' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'PLAN_MATCH_OPERATIONAL_CONTEXT_MISMATCH' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'revalidateMatchedOperationalContext' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "permission: 'asset_imports.apply'" web-v2/src 2>/dev/null || grep -Fq "asset_imports.apply" web-v2/src/pages/AssetImportsPage.tsx
echo "Hash, transacción, jerarquía, contexto operativo y permiso de aplicación: OK"
echo
echo "========== D5.3.7.1 · REVERSIÓN LÓGICA =========="
grep -Fq "information_status='INACTIVE'" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'imported_asset_id=NULL' api-v3/src/asset-imports/asset-imports.service.ts
if grep -Fq 'DELETE FROM assets' api-v3/src/asset-imports/asset-imports.service.ts; then
echo "ERROR: la reversión no debe borrar assets"; exit 1
fi
echo "Rollback lógico, trazable y sin DELETE de activos: OK"
echo
echo "========== D5.3.7.1 · FRONTEND =========="
grep -Fq '4 · Plan' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq '5 · Confirmación' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq '6 · Aplicación' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'APLICAR' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'operatorAssetId' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'Revertir lote' web-v2/src/pages/AssetImportsPage.tsx
echo "Plan, confirmación, operador obligatorio y reversión visibles: OK"
echo
echo "========== D5.3.7.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.1: OK"
+126
View File
@@ -0,0 +1,126 @@
#!/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"
+128
View File
@@ -0,0 +1,128 @@
#!/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-d53722-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.2 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-7.2.2" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-7.2.2" ]]
grep -Fq "API_PHASE = 'D5.3.7.2.2'" api-v3/src/version.ts
echo "API: 0.19.3-7.2.2"
echo "WEB: 0.19.3-7.2.2"
echo
echo "========== D5.3.7.2.2 · 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.2 · 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.2 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-7.2.2"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.7.2.2"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.7.2.2 · 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.2 · 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.2 · 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.2 · 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
grep -Fq '::integer,$${base+2}::uuid' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'row.row_number=v.row_number' api-v3/src/asset-imports/asset-imports.service.ts
echo "Aplicación transaccional, casteo row_number y reversión lógica relacional: OK"
echo
echo "========== D5.3.7.2.2 · 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.2 · 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.2: OK"
+129
View File
@@ -0,0 +1,129 @@
#!/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-d53723-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.3 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-7.2.3" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-7.2.3" ]]
grep -Fq "API_PHASE = 'D5.3.7.2.3'" api-v3/src/version.ts
echo "API: 0.19.3-7.2.3"
echo "WEB: 0.19.3-7.2.3"
echo
echo "========== D5.3.7.2.3 · 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.3 · 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.3 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-7.2.3"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.7.2.3"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.7.2.3 · 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.3 · 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.3 · 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.3 · 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
grep -Fq '::integer,$${base+2}::uuid' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'row.row_number=v.row_number' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'applied_asset_id=$2::uuid,applied_object_id=($2::uuid)::text' api-v3/src/asset-imports/asset-imports.service.ts
echo "Aplicación transaccional, casteos SQL seguros y reversión lógica relacional: OK"
echo
echo "========== D5.3.7.2.3 · 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.3 · 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.3: OK"

Some files were not shown because too many files have changed in this diff Show More