#!/bin/bash
# process-airtable-dual.sh — Provisiona proyectos desde Airtable con distribución automática entre 2 VPS
# Uso: ./process-airtable-dual.sh [--dry-run]
#
# Requiere: /root/scripts/.airtable.env con:
#   AIRTABLE_TOKEN=pat...
#   AIRTABLE_BASE_ID=app...
#   AIRTABLE_TABLE_ID=tbl...
#
# Campos usados desde Airtable (tabla Projects):
#   Name              → título del proyecto
#   Cpanel_User       → base_name cPanel (si vacío, se genera desde Name)
#   Email             → email del admin (si vacío: admin@{base_name}.bewpro.com)
#   Slug (from Product) → product slug para bewpro:new
#   Domain            → URL del sitio (si vacío: https://{base_name}.bewpro.com)
#   Notes             → texto: contiene CLEAN=true y/o COLORS=#HEX,... (opciones de provisión)
#
# Escribe de vuelta en Airtable:
#   Pipeline_Status      → "On Development"
#   Cpanel_User          → base_name usado
#   Provisioned_DB       → {base_name}_bp
#   Provisioned_Password → password generado
#   Server               → "vps1" o "vps2" (nuevo campo)

set -uo pipefail

########################################
# LOCK — evitar ejecuciones concurrentes
########################################

LOCK_FILE="/tmp/process-airtable.lock"
MAX_RUNTIME=3600  # 1 hora máximo (ajustar según cantidad de proyectos)

if [[ -f "$LOCK_FILE" ]]; then
  PID=$(cat "$LOCK_FILE")
  if kill -0 "$PID" 2>/dev/null; then
    LOCK_AGE=$(( $(date +%s) - $(stat -c %Y "$LOCK_FILE") ))
    if [[ "$LOCK_AGE" -gt "$MAX_RUNTIME" ]]; then
      echo "WARN: Proceso ${PID} lleva ${LOCK_AGE}s — matando por timeout..." >&2
      kill -9 "$PID" 2>/dev/null || true
      rm -f "$LOCK_FILE"
    else
      echo "Ya hay una instancia corriendo (PID: ${PID}, hace ${LOCK_AGE}s). Saliendo."
      exit 0
    fi
  else
    echo "Lock obsoleto (PID ${PID} ya no existe), limpiando..."
    rm -f "$LOCK_FILE"
  fi
fi

echo $$ > "$LOCK_FILE"
trap "rm -f ${LOCK_FILE}" EXIT INT TERM


SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SETUP_SCRIPT="${SCRIPT_DIR}/setup_cd_project4.sh"
CONFIG_FILE="/root/scripts/.airtable.env"

########################################
# CONFIGURACIÓN DUAL VPS
########################################

VPS1_IP="72.61.45.136"
VPS2_IP="179.43.124.219"
VPS2_PORT="5633"
VPS1_NAME="VPS Hostinger 1"
VPS2_NAME="VPS Donweb 1"

########################################
# FLAGS
########################################

DRY_RUN=false
for arg in "$@"; do
  [[ "$arg" == "--dry-run" ]] && DRY_RUN=true
done

########################################
# CHEQUEO DE ROOT
########################################

if [[ "$(id -u)" -ne 0 ]]; then
  echo "Este script debe ejecutarse como root." >&2
  exit 1
fi

########################################
# DEPENDENCIAS
########################################

for cmd in curl python3 jq openssl ssh; do
  if ! command -v "$cmd" &>/dev/null; then
    echo "ERROR: '$cmd' no está instalado." >&2
    exit 1
  fi
done

if [[ ! -x "$SETUP_SCRIPT" ]]; then
  echo "ERROR: setup_cd_project4.sh no encontrado o no ejecutable: ${SETUP_SCRIPT}" >&2
  exit 1
fi

########################################
# CARGAR CONFIG DE AIRTABLE
########################################

if [[ -f "$CONFIG_FILE" ]]; then
  source "$CONFIG_FILE"
fi

AIRTABLE_TOKEN="${AIRTABLE_TOKEN:-}"
AIRTABLE_BASE_ID="${AIRTABLE_BASE_ID:-}"
AIRTABLE_TABLE_ID="${AIRTABLE_TABLE_ID:-}"

if [[ -z "$AIRTABLE_TOKEN" || -z "$AIRTABLE_BASE_ID" || -z "$AIRTABLE_TABLE_ID" ]]; then
  echo "ERROR: Faltan variables de Airtable en ${CONFIG_FILE}" >&2
  exit 1
fi

AIRTABLE_API_URL="https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/${AIRTABLE_TABLE_ID}"
SLACK_WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"

########################################
# FUNCIONES SLACK
########################################

slack_notify() {
  local msg="$1"
  if [[ -n "$SLACK_WEBHOOK_URL" ]]; then
    curl -s -X POST "$SLACK_WEBHOOK_URL" \
      -H 'Content-type: application/json' \
      -d '{"text": "'"$msg"'"}' > /dev/null 2>&1 &
  fi
}

########################################
# FUNCIONES DUAL VPS
########################################

get_vps1_count() {
  /usr/local/cpanel/bin/whmapi1 listaccts 2>/dev/null | grep -c "^      user:" || echo 0
}

get_vps2_count() {
  ssh -p "${VPS2_PORT}" -o StrictHostKeyChecking=no -o ConnectTimeout=5 \
    root@${VPS2_IP} \
    "/usr/local/cpanel/bin/whmapi1 listaccts 2>/dev/null | grep -c '^      user:'" \
    2>/dev/null || echo 999
}

select_target_server() {
  local vps1_count vps2_count

  # Verificar conectividad VPS2
  if ! ssh -p "${VPS2_PORT}" -o StrictHostKeyChecking=no -o ConnectTimeout=5 \
    root@${VPS2_IP} "echo ok" >/dev/null 2>&1; then
    echo "WARN: VPS2 no disponible — usando VPS1" >&2
    echo "${VPS1_NAME}|${VPS1_IP}"
    return
  fi

  vps1_count=$(get_vps1_count)
  vps2_count=$(get_vps2_count)

  # Limpiar: dejar solo dígitos
  vps1_count="${vps1_count//[^0-9]/}"
  vps2_count="${vps2_count//[^0-9]/}"

  # Defaults si quedaron vacíos
  vps1_count="${vps1_count:-0}"
  vps2_count="${vps2_count:-999}"

  echo "  Cuentas VPS1: ${vps1_count} | Cuentas VPS2: ${vps2_count}" >&2

  if [[ "$vps2_count" -le "$vps1_count" ]]; then
    echo "${VPS2_NAME}|${VPS2_IP}"
  else
    echo "${VPS1_NAME}|${VPS1_IP}"
  fi
}
run_setup_on_server() {
  local target_ip="$1"
  local base_name="$2"
  local email="$3"
  local title="$4"
  local slug="$5"
  local password="$6"
  local domain="$7"

  if [[ "$target_ip" == "$VPS1_IP" ]]; then
    # Ejecutar local — pasar IP del server para DNS
    TARGET_SERVER_IP="${VPS1_IP}" bash "$SETUP_SCRIPT" "$base_name" "$email" "$title" "$slug" "$password" "$domain"
  else
    # Ejecutar remoto via SSH
    ssh -p "${VPS2_PORT}" -o StrictHostKeyChecking=no root@${target_ip} \
      "TARGET_SERVER_IP='${target_ip}' CLEAN_PROVISION='${CLEAN_PROVISION:-false}' BRAND_COLORS='${BRAND_COLORS:-}' \
       bash /root/scripts/setup_cd_project4.sh \
       '$base_name' '$email' '$title' '$slug' '$password' '$domain'"
  fi
}

########################################
# FUNCIONES AIRTABLE
########################################

airtable_get_required() {
  local filter
  filter=$(python3 -c "import urllib.parse; print(urllib.parse.quote('{Pipeline_Status}=\"Required\"'))")
  curl -s \
    -H "Authorization: Bearer ${AIRTABLE_TOKEN}" \
    -H "Content-Type: application/json" \
    "${AIRTABLE_API_URL}?filterByFormula=${filter}&sort%5B0%5D%5Bfield%5D=Name&sort%5B0%5D%5Bdirection%5D=asc"
}

airtable_patch() {
  local record_id="$1"
  local payload="$2"
  curl -s -X PATCH \
    -H "Authorization: Bearer ${AIRTABLE_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "$payload" \
    "${AIRTABLE_API_URL}/${record_id}"
}

airtable_mark_processing() {
  local record_id="$1"
  airtable_patch "$record_id" '{"fields":{"Pipeline_Status":"Processing"}}'
}

airtable_mark_failed() {
  local record_id="$1"
  airtable_patch "$record_id" '{"fields":{"Pipeline_Status":"Failed"}}'
}

# Retry policy: marca el record con un counter en Notes para que el próximo
# run lo retomé. Después de MAX_RETRIES vuelve al "Failed" definitivo.
# Se usa cuando el setup script falla pero podría ser transitorio (ej: DNS no propagó).
airtable_mark_retry() {
  local record_id="$1"
  local current_notes="$2"   # texto actual del campo Notes (puede tener "RETRY=N")
  local current_count
  if echo "$current_notes" | grep -qE 'RETRY=[0-9]+'; then
    current_count=$(echo "$current_notes" | grep -oE 'RETRY=[0-9]+' | tail -1 | cut -d= -f2)
  else
    current_count=0
  fi
  local new_count=$((current_count + 1))
  local new_notes
  if [[ -n "$current_notes" ]]; then
    new_notes=$(echo "$current_notes" | sed 's/RETRY=[0-9]*//g' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
    new_notes="${new_notes}\nRETRY=${new_count}"
  else
    new_notes="RETRY=${new_count}"
  fi
  local payload
  payload=$(python3 -c "
import json, sys
data = {'fields': {'Pipeline_Status': 'Required', 'Notes': sys.argv[1]}}
print(json.dumps(data))
" "$new_notes")
  airtable_patch "$record_id" "$payload"
  echo "$new_count"  # devuelve el counter
}

# Constantes de retry
MAX_RETRIES=2

airtable_mark_on_development() {
  local record_id="$1"
  local db_name="$2"
  local password="$3"
  local cpanel_user="$4"
  local app_url="$5"
  local server_name="$6"   # vps1 o vps2

  local grace_end
  grace_end=$(date -d "+15 days" '+%Y-%m-%d' 2>/dev/null || date -v+15d '+%Y-%m-%d' 2>/dev/null || "")

  local payload
  payload=$(python3 -c "
import json, sys
data = {
    'fields': {
        'Pipeline_Status': 'On Development',
        'Cpanel_User': sys.argv[1],
        'Provisioned_DB': sys.argv[2],
        'Provisioned_Password': sys.argv[3],
        'Domain': sys.argv[4],
        'SERVER': sys.argv[6]
    }
}
if sys.argv[5]:
    data['fields']['Grace_Period_End'] = sys.argv[5]
print(json.dumps(data))
" "$cpanel_user" "$db_name" "$password" "$app_url" "$grace_end" "$server_name")

  airtable_patch "$record_id" "$payload"
}

airtable_create_subscription() {
  local project_record_id="$1"
  local project_name="$2"
  local cpanel_user="$3"
  local app_url="$4"
  local stripe_customer_id="${5:-}"
  local amount_usd="${6:-}"
  local grace_end="${7:-}"

  local payload
  payload=$(python3 -c "
import json, sys
data = {
    'fields': {
        'Project': sys.argv[1],
        'Copia de Project': [sys.argv[2]],
        'Billing_Model': 'Stripe',
        'Status': 'Active',
        'Payment_Status': 'Paid',
        'Payment_Method': 'Stripe',
        'Plan': 'Monthly',
        'Provisioned_At': __import__('datetime').date.today().isoformat(),
        'Cpanel_User': sys.argv[3],
        'App_URL': sys.argv[4],
    }
}
if sys.argv[5]:
    data['fields']['Stripe_Customer_ID'] = sys.argv[5]
if sys.argv[6]:
    try:
        data['fields']['Amount_USD'] = float(sys.argv[6])
    except: pass
if sys.argv[7]:
    data['fields']['Grace_Period_End'] = sys.argv[7]
print(json.dumps(data))
" "$project_name" "$project_record_id" "$cpanel_user" "$app_url" \
  "$stripe_customer_id" "$amount_usd" "$grace_end")

  curl -s -X POST \
    -H "Authorization: Bearer ${AIRTABLE_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "$payload" \
    "https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/tblnpr52JhFBBi2Mg"
}

########################################
# PARSEAR REGISTROS DE AIRTABLE
########################################

parse_records() {
  local json="$1"

  python3 - "$json" <<'PYEOF'
import sys, json, re

raw = sys.argv[1]
try:
    data = json.loads(raw)
except Exception as e:
    print(f"ERROR parsing JSON: {e}", file=sys.stderr)
    sys.exit(1)

records = data.get('records', [])
if not records:
    print("NO_RECORDS")
    sys.exit(0)

def make_base_name(title, record_id):
    base = re.sub(r'[^a-z0-9]', '', title.lower())[:11]
    suffix = re.sub(r'[^a-z0-9]', '', record_id.lower())[-3:]
    return (base + suffix)[:14]

def make_subdomain(title):
    slug = re.sub(r'[^a-z0-9]+', '-', title.lower().strip()).strip('-')
    return slug[:63]

def normalize_domain(domain, slug):
    d = domain.split('\n')[0].strip()
    if not d or d in ('---', '-', 'N/A', 'n/a'):
        return f"https://{slug}.bewpro.com"
    if not re.match(r'^https?://[a-zA-Z0-9]', d) and not re.match(r'^[a-zA-Z0-9][\w.-]+\.[a-zA-Z]{2,}$', d):
        return f"https://{slug}.bewpro.com"
    if not d.startswith('http'):
        return f"https://{d}"
    return d


for r in records:
    fields = r.get('fields', {})
    record_id   = r.get('id', '')
    name        = fields.get('Name', '').strip()
    cpanel_user = fields.get('Cpanel_User', '').strip()
    email       = fields.get('Email', '').strip()

    slug_raw = fields.get('Slug (from Product)', [])
    if isinstance(slug_raw, list):
        slug = slug_raw[0].strip() if slug_raw else ''
    else:
        slug = str(slug_raw).strip()
    if not slug:
        slug = fields.get('Slug', '').strip()

    domain_raw = fields.get('Domain', '').strip()
    notes      = fields.get('Notes', '').strip()

    clean  = 'false'
    colors = ''
    for line in notes.split('\n'):
        line = line.strip()
        if line.startswith('CLEAN='):
            clean = line.split('=', 1)[1].strip().lower()
        elif line.startswith('COLORS='):
            colors = line.split('=', 1)[1].strip()

    if not name or not slug:
        print(f"SKIP|{record_id}|{name}|missing name or slug", file=sys.stderr)
        continue

    base_name = cpanel_user if cpanel_user else make_base_name(name, record_id)
    if not email:
        email = f"admin@{base_name}.bewpro.com"

    # Subdominio legible desde el nombre del proyecto
    subdomain = make_subdomain(name) if not domain_raw.strip() or domain_raw.strip() in ('---', '-', 'N/A', 'n/a') else None
    domain = normalize_domain(domain_raw, subdomain or make_subdomain(name))

    print(f"{record_id}|{name}|{base_name}|{email}|{slug}|{domain}|{clean}|{colors}")
PYEOF
}

########################################
# MAIN
########################################

echo "============================================="
echo "  PROCESS AIRTABLE DUAL VPS — Provisioning"
if $DRY_RUN; then
  echo "  MODE: DRY RUN (sin cambios reales)"
fi
echo "============================================="
echo ""

echo "Consultando Airtable (Pipeline_Status = Required)..."
RESPONSE=$(airtable_get_required)

if echo "$RESPONSE" | python3 -c \
  "import sys,json; d=json.load(sys.stdin); sys.exit(0 if 'records' in d else 1)" 2>/dev/null; then
  :
else
  echo "ERROR: Respuesta inesperada de Airtable:" >&2
  echo "$RESPONSE" >&2
  exit 1
fi

RECORDS=$(parse_records "$RESPONSE")

if [[ "$RECORDS" == "NO_RECORDS" || -z "$RECORDS" ]]; then
  echo "No hay proyectos con Pipeline_Status = 'Required'. Nada que hacer."
  exit 0
fi

COUNT=$(echo "$RECORDS" | grep -c '.' || true)
echo "Proyectos a provisionar: ${COUNT}"
echo ""

SUCCEEDED=0
FAILED=0
FAILED_NAMES=()

while IFS='|' read -r RECORD_ID NAME BASE_NAME EMAIL SLUG DOMAIN CLEAN_FLAG COLORS_VAL; do

  # Seleccionar servidor por cada proyecto (re-evalúa cuentas en tiempo real)
  SERVER_SELECTION=$(select_target_server)
  TARGET_SERVER=$(echo "$SERVER_SELECTION" | cut -d'|' -f1)
  TARGET_IP=$(echo "$SERVER_SELECTION" | cut -d'|' -f2)

  echo "---------------------------------------------"
  echo "  Proyecto:  ${NAME}"
  echo "  base_name: ${BASE_NAME}"
  echo "  Email:     ${EMAIL}"
  echo "  Slug:      ${SLUG}"
  echo "  Domain:    ${DOMAIN}"
  echo "  Servidor:  ${TARGET_SERVER} (${TARGET_IP})"
  echo "  Clean:     ${CLEAN_FLAG:-false}"
  echo "  Colors:    ${COLORS_VAL:-default}"
  echo ""

  PASSWORD=$(openssl rand -base64 12 | tr -d '/+=')

  # Sanitize NAME for shell safety (escape single quotes like TAW'S)
  NAME_SAFE=$(echo "$NAME" | sed "s/'/'\\\\''/g")

  if $DRY_RUN; then
    echo "  [DRY RUN] setup en ${TARGET_SERVER}: ${BASE_NAME} ${EMAIL} \"${NAME}\" ${SLUG} ${DOMAIN}"
    echo ""
    continue
  fi

  # Marcar como Processing — evita race condition con crons paralelos
  echo "  Marcando como Processing en Airtable..."
  airtable_mark_processing "$RECORD_ID" > /dev/null

  export CLEAN_PROVISION="${CLEAN_FLAG:-false}"
  export BRAND_COLORS="${COLORS_VAL:-}"

  # Ejecutar setup en el servidor elegido
  APP_URL=""
  if APP_URL=$(run_setup_on_server \
    "$TARGET_IP" "$BASE_NAME" "$EMAIL" "$NAME_SAFE" "$SLUG" "$PASSWORD" "$DOMAIN"); then

    echo "  OK — ${APP_URL}"

    # 1. Actualizar Project en Airtable (con campo Server)
    echo "  Actualizando Airtable..."
    PATCH_RESULT=$(airtable_mark_on_development \
      "$RECORD_ID" "${BASE_NAME}_bp" "$PASSWORD" "$BASE_NAME" "$APP_URL" "$TARGET_SERVER")

    if echo "$PATCH_RESULT" | python3 -c \
      "import sys,json; d=json.load(sys.stdin); sys.exit(0 if 'id' in d else 1)" 2>/dev/null; then
      echo "    Project: Pipeline_Status=On Development, Server=${TARGET_SERVER}"
    else
      echo "  WARN: Project PATCH falló:" >&2
      echo "$PATCH_RESULT" >&2
    fi

    # 2. Crear Subscription record
    STRIPE_CID=$(echo "$RESPONSE" | python3 -c "
import sys,json
data = json.load(sys.stdin)
for r in data.get('records',[]):
    if r['id'] == sys.argv[1]:
        print(r.get('fields',{}).get('Stripe_Customer_ID',''))
        break
" "$RECORD_ID" 2>/dev/null || echo "")

    AMOUNT=$(echo "$RESPONSE" | python3 -c "
import sys,json
data = json.load(sys.stdin)
for r in data.get('records',[]):
    if r['id'] == sys.argv[1]:
        print(r.get('fields',{}).get('Amount_USD',''))
        break
" "$RECORD_ID" 2>/dev/null || echo "")

    GRACE_END=$(echo "$RESPONSE" | python3 -c "
import sys,json
data = json.load(sys.stdin)
for r in data.get('records',[]):
    if r['id'] == sys.argv[1]:
        print(r.get('fields',{}).get('Grace_Period_End',''))
        break
" "$RECORD_ID" 2>/dev/null || echo "")

    # Fallback: 15 dias desde hoy (form-reseller sin trial de Stripe)
    if [[ -z "$GRACE_END" ]]; then
      GRACE_END=$(date -d '+15 days' '+%Y-%m-%d' 2>/dev/null || date -v+15d '+%Y-%m-%d' 2>/dev/null || '')
    fi

    SUB_RESULT=$(airtable_create_subscription \
      "$RECORD_ID" "$NAME" "$BASE_NAME" "$APP_URL" "$STRIPE_CID" "$AMOUNT" "$GRACE_END")

    if echo "$SUB_RESULT" | python3 -c \
      "import sys,json; d=json.load(sys.stdin); sys.exit(0 if 'id' in d else 1)" 2>/dev/null; then
      echo "    Subscription: creada (Stripe/Active)"
    else
      echo "  WARN: Subscription no creada:" >&2
      echo "$SUB_RESULT" >&2
    fi

    # 3. Enviar emails
    GRACE_END=$(date -d "+15 days" '+%Y-%m-%d' 2>/dev/null || date -v+15d '+%Y-%m-%d' 2>/dev/null || "")
    # Sanitize NAME for shell safety (escape single quotes)

  echo "  Enviando emails (cliente: ${EMAIL})..."

    ARTISAN_CMD="php artisan bewpro:send-welcome '${EMAIL}' '${NAME_SAFE}' '${APP_URL}' '${PASSWORD}' --product='${SLUG}'"
    if [[ -n "${GRACE_END}" ]]; then
      ARTISAN_CMD="${ARTISAN_CMD} --grace-end='${GRACE_END}'"
    fi

    if [[ "$TARGET_IP" == "$VPS1_IP" ]]; then
      su - "${BASE_NAME}" -c "
        cd public_html/git-files/${BASE_NAME} && ${ARTISAN_CMD}
      " >&2 && echo "    Emails: enviados via artisan" || {
        echo "  WARN: artisan falló, intentando fallback..." >&2
        PHP_BIN_GLOBAL=$(which php 2>/dev/null || echo "php")
        EMAIL_SCRIPT="${SCRIPT_DIR}/send-welcome-email.php"
        if [[ -f "$EMAIL_SCRIPT" ]]; then
          $PHP_BIN_GLOBAL "$EMAIL_SCRIPT" \
            "$EMAIL" "$NAME" "$APP_URL" "$PASSWORD" "$EMAIL" 2>/dev/null \
            && echo "    Email: enviado via fallback" \
            || echo "  WARN: email no enviado" >&2
        fi
      }
    else
      ssh -p "${VPS2_PORT}" -o StrictHostKeyChecking=no root@${TARGET_IP} \
        "su - '${BASE_NAME}' -c \"cd public_html/git-files/${BASE_NAME} && ${ARTISAN_CMD}\"" >&2 \
        && echo "    Emails: enviados via artisan en VPS2" \
        || echo "  WARN: email no enviado en VPS2" >&2
    fi

    slack_notify "✅ *Provisioning completado*\nProyecto: ${NAME}\nURL: ${APP_URL}\nServidor: ${TARGET_SERVER}\nProducto: ${SLUG}"
    SUCCEEDED=$((SUCCEEDED + 1))

  else
    SETUP_EXIT=$?
    echo "  ERROR: Provisioning falló (exit ${SETUP_EXIT}) para '${NAME}' en ${TARGET_SERVER}." >&2

    # Retry policy: si el setup completó pero la validación post falló (exit 2),
    # o si fue un error transitorio bajo MAX_RETRIES, devolver a Required para
    # próximo cron. Después de MAX_RETRIES, marcar Failed definitivo.
    RETRY_COUNT=$(airtable_mark_retry "$RECORD_ID" "${NOTES_VAL:-}" 2>/dev/null | tail -1)
    RETRY_COUNT="${RETRY_COUNT:-99}"  # si falla el patch, asumimos max para no loop infinito

    if [[ "$RETRY_COUNT" -le "$MAX_RETRIES" ]] 2>/dev/null; then
      echo "    Retry ${RETRY_COUNT}/${MAX_RETRIES} programado — Pipeline_Status=Required" >&2
      slack_notify "🔁 *Provisioning retry ${RETRY_COUNT}/${MAX_RETRIES}*\nProyecto: ${NAME}\nServidor: ${TARGET_SERVER}\nExit: ${SETUP_EXIT}"
    else
      echo "    Max retries alcanzado — Pipeline_Status=Failed" >&2
      airtable_mark_failed "$RECORD_ID" > /dev/null
      slack_notify "❌ *Provisioning FALLÓ definitivo (${MAX_RETRIES}+1 intentos)*\nProyecto: ${NAME}\nServidor: ${TARGET_SERVER}\nProducto: ${SLUG}\nExit: ${SETUP_EXIT}"
    fi

    FAILED=$((FAILED + 1))
    FAILED_NAMES+=("$NAME")
  fi

  echo ""
done <<< "$RECORDS"

echo "============================================="
if $DRY_RUN; then
  echo "  DRY RUN completado — ${COUNT} proyecto(s) en cola"
else
  echo "  Resultado: ${SUCCEEDED} OK, ${FAILED} fallidos"
  if [[ ${#FAILED_NAMES[@]} -gt 0 ]]; then
    echo "  Fallidos:"
    for n in "${FAILED_NAMES[@]}"; do
      echo "    - ${n}"
    done
  fi
fi
echo "============================================="

[[ $FAILED -gt 0 ]] && exit 1
exit 0
