#!/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"