# Arquitectura de tenants — clones independientes del repo

> **Última actualización**: 2026-05-05 (descubrimiento durante debug del fix legal-bar).
> **Estado**: ✅ documentado, ❌ propagación de cambios sigue siendo on-demand manual.

## Hecho clave

Cada tenant pagante de bewpro.com tiene su **propio clone independiente** del repo `cd-system` en su propio cPanel user. Los `git pull` en `bewpro22` (sitio principal) **NO se propagan automáticamente** a tenants ya provisionados.

```
/home/bewpro22/public_html/bewpro/                 ← bewpro.com (sitio principal, codebase canónico)
/home/<tenant-user>/public_html/git-files/<name>/  ← cada tenant tiene SU clone
```

Apache vhost de cada tenant tiene `.htaccess` que rewrites a `git-files/<name>/public/`.

## Doble VPS — los tenants viven en ambos

| VPS | IP | Cuentas | Rol |
|-----|-----|---------|-----|
| **VPS1 Hostinger** | 72.61.45.136 | bewpro22 + ~36 tenants | Sitio principal `bewpro.com` + algunos tenants |
| **VPS2 Donweb** | 179.43.124.219:5633 | ~21 tenants | Solo tenants pagantes (no `bewpro.com`) |

El `bewpro:new` y `setup_cd_project*.sh` deciden a qué VPS provisionar según `select_target_server()` (load balancing simple). Por eso **un tenant puede estar en cualquiera de los 2 VPS** — al hacer fix on-demand hay que verificar dónde vive (DNS lookup: `dig +short <subdomain>`).

**Caso real (2026-05-05)**: el tenant `inmobiliaria-cc.bewpro.com` resolvió a `179.43.124.219` (VPS2 Donweb). El fix del legal-bar hecho en VPS1 (bewpro22) no aplica acá. Para arreglarlo: SSH a VPS2 + pull en su clone.

## Por qué importa

Cambios cross-cutting (fixes UI, mejoras generales, security patches, refactors) **NO se aplican retroactivamente** a tenants ya provisionados. Cada tenant queda "freezado" en el HEAD del repo del momento que fue provisionado.

| Acción | Afecta bewpro22? | Afecta tenants ya provisionados? | Afecta tenants NUEVOS (provisionados post-cambio)? |
|---|---|---|---|
| Push a branch `cd-system` del repo | SÍ (al pullear bewpro22) | NO automáticamente | ✅ SÍ (clone toma HEAD del repo en ese momento) |
| Migration nueva | SÍ (corre `migrate` en bewpro22) | NO — cada tenant queda con schema viejo | ✅ SÍ (corre migrate al provisionar) |
| Fix UI/blade | SÍ | NO | ✅ SÍ |
| Cambio de helper PHP | SÍ | NO | ✅ SÍ |
| Security patch | SÍ | NO — tenants vulnerables hasta actualización manual | ✅ SÍ |

**Consecuencia clave**: el "drift" se acumula con tenants ya provisionados (a menos que se actualicen on-demand), pero la **calidad del baseline mejora con cada push** — los tenants nuevos siempre arrancan con el HEAD más reciente del repo en GitHub.

## Cómo se provisiona un tenant nuevo

1. Cliente paga via Stripe Checkout
2. `StripeWebhookController.handleCheckoutSessionCompleted` crea record en Airtable con `Pipeline_Status='Required'`
3. Cron `process-airtable.sh` en VPS1 (cada minuto) detecta Required y ejecuta `bewpro:new`
4. El comando:
   - Crea cPanel account vía WHM
   - **Clona el repo `cd-system` en `/home/<user>/public_html/git-files/<name>/`** con HEAD del momento (snapshot)
   - Crea DB MySQL `<user>_<dbname>`
   - Configura `.env` con `APP_NAME`, `DB_*`, secrets propios
   - Corre `migrate` + seeders
   - Setea `.htaccess` rewriting a `public/`

> **Implicancia**: el HEAD que recibe el clone es el del momento del provisioning. Sin mecanismo automático para mantenerlo al día.

## Política de propagación de cambios (decisión 2026-05-05)

> **Founder explícito**: "no propaguemos por ahora a todos lados porque algunos pueden tener particularidades que serán actualizadas de a poco para evitar problemas de migraciones o cosas del estilo".

**Significado operativo**:
- ❌ NO ejecutar batch operations tipo `find /home -name .git -path '*git-files*' -exec git pull` masivos.
- ❌ NO asumir que un fix llegó automáticamente a tenants.
- ✅ Para bewpro.com: pull + cache clear en `/home/bewpro22/public_html/bewpro/`.
- ✅ Para tenants: caso a caso, evaluando customizaciones específicas + decisión del founder.
- ✅ Si hace falta cross-tenant fix masivo: discutir el approach antes de ejecutar.

## Approach correcto para fix de un tenant específico

```bash
# 1. SSH al VPS1 (o usar el alias vps1-claude)
ssh vps1-claude

# 2. Identificar el path del clone del tenant
sudo find /home/<cuenta-cpanel>/public_html/git-files/ -maxdepth 2 -name '.git' -type d

# 3. Pull como el user dueño
cd /home/<cuenta-cpanel>/public_html/git-files/<name>/
sudo -u <cuenta-cpanel> git fetch origin cd-system
sudo -u <cuenta-cpanel> git reset --hard origin/cd-system  # Solo si NO hay customizaciones locales
# o
sudo -u <cuenta-cpanel> git pull --rebase origin cd-system  # Si hay customizaciones

# 4. Correr migrations si hay nuevas
sudo -u <cuenta-cpanel> /opt/cpanel/ea-php82/root/usr/bin/php artisan migrate --force

# 5. Limpiar caches
sudo -u <cuenta-cpanel> /opt/cpanel/ea-php82/root/usr/bin/php artisan config:clear
sudo -u <cuenta-cpanel> /opt/cpanel/ea-php82/root/usr/bin/php artisan view:clear
sudo -u <cuenta-cpanel> /opt/cpanel/ea-php82/root/usr/bin/php artisan route:clear
```

## Propagación selectiva a N tenants — `batch-tenant-pull.sh`

> Script canónico para actualizar **una lista explícita** de tenants (NO masivo).
> Vive en VPS1 como `/root/scripts/batch-tenant-pull.sh`, versionado en repo bajo `scripts/`.

### Cuándo usarlo

Cuando hay un push importante (fix UI, security patch, mejora cross-cutting) que querés llevar a un **subset acotado** de tenants — típicamente los demos comerciales activos o un cluster de la misma campaña. Respeta la política del founder ("no propagar masivamente").

### Features

- **Auto-detect VPS** del tenant (VPS1 local / VPS2 / VPS3) via `id <user>` en cada uno.
- **Auto-detect panel** (cPanel `public_html/git-files/{user}/` vs HestiaCP `web/{domain}/public_html/git-files/{user}/`).
- **Stash defensive**: preserva cambios locales (`database/seeders/project-data/*.json` que se modifican durante el provisioning) antes del pull, hace pop después.
- **Pre-check up-to-date**: opcional skip si el tenant ya está en HEAD remoto.
- **Cache clear automático**: `php artisan config:clear` + `view:clear` + `route:clear`.
- **Dry-run mode**: muestra qué haría sin tocar nada.
- **CSV log**: dump de resultados por tenant para auditoría.
- **Confirm interactivo** antes de tocar el primer tenant (`--no-confirm` para CI).
- **Manejo de conflictos**: si `git pull --rebase` o `git stash pop` falla, aborta ese tenant y sigue con el siguiente (no rompe el batch).

### Uso típico

```bash
ssh vps1-claude

# 1. Primero dry-run para ver qué haría
/root/scripts/batch-tenant-pull.sh \
  --users=user1,user2,user3 \
  --dry-run

# 2. Ejecutar real, con export del log
/root/scripts/batch-tenant-pull.sh \
  --users=user1,user2,user3 \
  --export-log=/tmp/batch-pull-$(date +%Y-%m-%d).csv

# 3. Verificar (HTTP 200 en home + /about + /contact por cada subdomain)
for sub in subdomain1 subdomain2; do
  curl -s -o /dev/null -w "%{http_code}\n" "https://${sub}.bewpro.com/about"
done
```

### Opciones del script

| Flag | Descripción |
|---|---|
| `--users=u1,u2,...` | Lista comma-separated de cpanel_users (requerido o `--users-file`) |
| `--users-file=path` | Alternativa: archivo con un user por línea (acepta `#` comments) |
| `--branch=cd-system` | Branch a pullear (default `cd-system`) |
| `--dry-run` | Simula sin tocar; muestra plan + HEADs |
| `--skip-if-uptodate` | Salta tenants ya en HEAD remoto |
| `--export-log=path` | Path para CSV: user,location,app_dir,head_before,head_after,status |
| `--no-cache-clear` | No correr `artisan *:clear` post-pull |
| `--no-confirm` | Salta confirm interactivo (peligroso para producción) |

### Ejemplos reales documentados

**1. Propagación 10 tenants de Maxi (2026-05-15)** — fix `/about` y `/contact` 500:

```bash
/root/scripts/batch-tenant-pull.sh \
  --users=demom3tl,demomysr,demomsng,demomcg4,demomtsh,demomeax,demoms1q,demomw61,demomhok,maxir7qq \
  --export-log=/tmp/batch-pull-maxi-2026-05-15.csv
```

Resultado: 9/10 actualizados de HEAD `b3c08e21` → `feda0d6b`. `maxir7qq` tenía DNS apuntando al VPS equivocado (issue separado de infra).

### Migrations: NO incluidas por default

El script **no ejecuta `migrate`** automáticamente — algunos tenants tienen schema custom o pueden romper. Si el push contiene migrations nuevas que SÍ querés correr:

```bash
# Identificar migrations nuevas:
git log <head_before>..origin/cd-system -- database/migrations/

# Aplicar manualmente al tenant después del pull:
sudo -u <user> /opt/cpanel/ea-php82/root/usr/bin/php artisan migrate --force
```

### Output del CSV

```csv
user,location,app_dir,head_before,head_after,status
demom3tl,local,/home/demom3tl/public_html/git-files/demom3tl,b3c08e21,feda0d6b,ok
demomysr,local,/home/demomysr/public_html/git-files/demomysr,b3c08e21,feda0d6b,ok
maxir7qq,vps2,/home/maxir7qq/public_html/git-files/maxir7qq,feda0d6b,feda0d6b,uptodate_skipped
```

Status posibles: `ok`, `uptodate_skipped`, `dry_run`, `not_found`, `app_dir not found`, `pull_failed`, `stash_pop_conflict`.

### Política — solo lista explícita

> **Recordatorio**: el script NO acepta wildcards ni "todos los tenants". La lista debe ser explícita. Esto es por diseño: respeta la política del founder de propagación caso a caso.

### Cómo desplegar nuevas versiones del script

```bash
# 1. Editar localmente en el repo
vim scripts/batch-tenant-pull.sh

# 2. Lint y commit
bash -n scripts/batch-tenant-pull.sh
git add scripts/batch-tenant-pull.sh && git commit -m "..." && git push

# 3. Deploy a VPS1
scp scripts/batch-tenant-pull.sh vps1-claude:/root/scripts/batch-tenant-pull.sh
ssh vps1-claude 'chmod +x /root/scripts/batch-tenant-pull.sh && bash -n /root/scripts/batch-tenant-pull.sh'
```

## Caso ejemplo: bug del legal-bar (2026-05-05)

- **Bug**: la banda `cd-legal-bar` aparecía en footers de TODOS los tenants (debería ser solo bewpro.com).
- **Fix**: `_footer.blade.php` con check `is_main_site()` (whitelist de hosts via env `BEWPRO_MAIN_HOSTS`).
- **Aplicado en**: bewpro22 (HEAD `5a1e75c4`).
- **NO aplicado a**: otros tenants (incluyendo `inmobiliaria-cc.bewpro.com` que reportó el bug).
- **Decisión del founder**: NO propagar masivamente. Cada tenant se actualizará "de a poco" cuando convenga.
- **Resultado**: el bug **persiste** en tenants no actualizados hasta que se aplique manualmente el pull.

## TODO arquitectónico (futuro)

Diseñar estrategia de propagación de fixes cross-tenant que respete customizaciones por-tenant. Posibles approaches:

1. **Cherry-pick por tenant**: maintainer mergea fix por tenant cuando lo aprueba.
2. **Branches por-tenant en cd-system**: cada tenant tiene una branch propia que rebase periódicamente sobre `cd-system`.
3. **Sistema "core + overrides"**: separar core (auto-update) de overrides por-tenant (manual).
4. **Feature flags por tenant**: un fix nuevo se activa por flag, default OFF, opt-in por tenant.
5. **Status quo**: actualización 100% manual por decisión del founder. Aceptable mientras volumen sea bajo.

Mientras se decide, la práctica vigente es status quo (#5).

## Referencias

- Plan provisioning: `docs/bewpro2.0/guia-provisionado.md`
- Pipeline operativo: `docs/bewpro2.0/infraestructura-operativa.md`
- VPS topology: `docs/bewpro2.0/infrastructure/00-overview.md`
- Stripe webhook → Airtable → provisioning: `app/Http/Controllers/StripeWebhookController.php`
