# Stress Test — pre-launch real

> **Estado:** comando implementado y validado con N=2/3 sin Airtable.
> **Fecha:** 2026-05-07
> **Archivos:** `app/Console/Commands/StressTest.php` + `app/Console/Commands/StressCleanup.php`

## Para qué sirve

Simular adquisición masiva de proyectos antes del lanzamiento público, para
validar que el pipeline completo aguanta carga sin que se rompa nada y que
cualquier cliente pagante pueda loguearse en `bewpro.com` y ver su proyecto.

**Cubre desde** la creación del User local **hasta** que el cliente entra a
`/my-projects` y ve sus proyectos en estado `active` post-provisión.

## Qué NO simula

- **Stripe Checkout real** — no abre la página de Stripe ni cobra con tarjetas
  test. Va directo al flow del webhook (`AirtableService` + `ClientProvisioningService`).
- **Emails** — el comando NO llama a `Mail::send`. Los 8 emails de credenciales
  los manda VPS1 cuando provisione (eso depende del SMTP de VPS1).

## Identificadores de cleanup

| Campo | Pattern | Para |
|---|---|---|
| `User.email` | `stress_NNN@bewpro-test.local` | Identificar users + cleanup |
| `TenantProject.stripe_customer_id` | `cus_stress_NNN_PP` | Idempotency key + cleanup |
| `TenantProject.stripe_subscription_id` | `sub_stress_NNN_PP` | Mock |
| `Airtable.Stripe_Session_ID` | `cs_test_stress_NNN_PP_*` | Idempotency en Airtable |
| `Airtable.Name` | `STRESS_TEST_NNN_<projectTitle>` | Identificar visualmente en Airtable UI |

El **Slug** que se manda a Airtable ES REAL (del catalog). Esto permite que
`process-airtable.sh` en VPS1 lo procese sin distinción y dispare el provisioning
en VPS3 (HestiaCP).

## Pre-requisitos

1. **Stripe TEST mode** en `.env` (`STRIPE_SECRET=sk_test_*`). El comando aborta
   si detecta `sk_live_*`.
2. **Rol `Client`** existe en BD: `php artisan db:seed --class=RolesSeeder`.
3. **Catalog cargado** en `database/seeders/products/catalog.json`.
4. **Airtable token** y `AIRTABLE_SUBSCRIPTIONS_TABLE` configurados (default
   `tblzCgJZCbbt5j13Q` = tabla Projects).
5. **VPS1 + VPS3 listos**: cron `process-airtable.sh` corriendo, `select_target_server()`
   ruteando a VPS3, scripts de HestiaCP funcionales (`v-add-user`, `v-add-web-domain`,
   `v-add-database`, etc.).

## Uso

### Dry-run (no persiste)

```bash
php artisan bewpro:stress-test --dry-run
# Muestra plan: usuarios, projects, distribución, identificadores. No escribe nada.
```

### Smoke test sin Airtable (solo BD local)

```bash
php artisan bewpro:stress-test --users=2 --projects=3 --skip-airtable --no-confirm
# Crea 2 users + 3 TenantProjects en BD local. No toca Airtable.
# Útil para validar la mecánica antes de pegarle a producción.
```

### Smoke test CON Airtable real (3 records → cron VPS1 → VPS3)

```bash
php artisan bewpro:stress-test --users=2 --projects=3
# Crea 2 users + 3 TenantProjects + 3 records en Airtable Projects con
# Pipeline_Status="Required" y Name="STRESS_TEST_001_*".
# El cron VPS1 los detectará en max 5 min y disparará provisioning en VPS3.
```

### Batch real

```bash
php artisan bewpro:stress-test
# Default: 100 users / 150 projects con distribución 60·1 + 30·2 + 10·3 = 150.
```

### Continuar desde un número específico (batches incrementales)

```bash
# Primer batch de 8
php artisan bewpro:stress-test --users=8 --projects=8

# Si querés sumar 10 más sin pisar los 8 ya creados:
php artisan bewpro:stress-test --users=10 --projects=10 --start-from=9
```

## Distribución de proyectos

| Users | Projects | Distribución |
|---|---|---|
| 100 | 150 | 60·1 + 30·2 + 10·3 = 150 (hardcoded) |
| 8 | 8 | 8·1 = 8 |
| Custom | Custom | Round-robin: empieza con 1 cada user, suma extras hasta cap=5 |

## Cleanup

```bash
# Dry-run primero
php artisan bewpro:stress-cleanup --dry-run

# Borra users + TenantProjects locales + marca Airtable records como Cancelled
php artisan bewpro:stress-cleanup

# Si querés BORRAR los Airtable records (no marcarlos Cancelled)
php artisan bewpro:stress-cleanup --airtable-delete
```

**Cuidado:** el cleanup NO borra cuentas creadas en VPS3 por el cron real. Esas
hay que borrarlas con `v-delete-user` desde VPS3 o desde el panel HestiaCP.

## Output esperado

```
═══════════════════════════════════════════════════════════════
  BewPro Stress Test — pre-launch real
═══════════════════════════════════════════════════════════════

  ✓ Stripe TEST mode (sk_test_51T9…)
  ✓ Rol Client existe en BD
  ✓ Catalog cargado (253 shop products)
  ✓ Airtable configurado (base appRxvpzqCmNsw2JN)
  ✓ DB: bp-bewpro

Plan de ejecución:
+------------------+--------------------------------+
| Parámetro        | Valor                          |
+------------------+--------------------------------+
| Usuarios a crear | 8 (numerados 1…8)              |
| Total proyectos  | 8                              |
| Distribución     | 8×1 proyectos por usuario      |
| ...              | ...                            |
+------------------+--------------------------------+

¿Confirmás ejecutar el stress test? (yes/no) [no]:
```

## Métricas a observar durante el batch grande

1. **Tiempo total** del comando — para 100/150 esperar 30-90s con Airtable
   (limit rate ~5 req/s + throttle 250ms cada 5).
2. **Airtable rate limits** — 5 req/s soft cap. El comando ya hace `usleep(250000)`
   cada 5 records. Si ves 429s en logs, subir el delay.
3. **Cron VPS1** — `tail -f /var/log/bewpro-cron.log` mientras el cron procesa los
   records. Cada 5 min agarra los `Required` y dispara el provisioning.
4. **VPS3 capacity** — 150 cuentas HestiaCP es un peak considerable. Monitorear
   CPU + RAM + disk durante la ola. Si el VPS satura, parar el cron temporalmente
   (`crontab -e` en VPS1) y procesar de a tandas.
5. **Portal `/my-projects`** — entrar con uno de los sample logins
   (`stress_001@bewpro-test.local` / `Test1234!`) y validar que el dashboard
   carga rápido aún cuando los 100 users existan en la misma DB.

## Riesgos y mitigaciones

| Riesgo | Mitigación |
|---|---|
| 8 emails de credenciales rebotan en `bewpro-test.local` | El dominio no existe, el SMTP los marca como undeliverable. Acumulado puede afectar deliverability del sender (`hola@bewpro.com`). Para batches grandes, suspender SMTP en VPS1 o usar un dominio catch-all. |
| Airtable se mezcla con records reales | Identificables por prefijo `STRESS_TEST_` en `Name` y `cus_stress_*` en `Stripe_Customer_ID`. Filtros visuales en Airtable UI. Cleanup masivo con un comando. |
| VPS3 satura con 150 cuentas | Ejecutar batches incrementales con `--start-from`. Pausar cron VPS1 entre tandas. |
| Stripe LIVE mode accidentalmente | El comando aborta en validación si detecta `sk_live_*`. |
| Pipeline interno cambia (slugs, statuses) | El comando reusa los mismos `Service` + `Model` constants — si cambian ahí, esto se rompe igual que el StripeWebhookController real. Buen smell test. |

## Anclajes

- Comando: `app/Console/Commands/StressTest.php`
- Cleanup: `app/Console/Commands/StressCleanup.php`
- Flow real replicado: `app/Http/Controllers/StripeWebhookController.php::handleCheckoutSessionCompleted`
- Services: `app/Services/AirtableService.php`, `app/Services/ClientProvisioningService.php`
- Doc complementaria: [`../guia-provisionado.md`](../guia-provisionado.md), [`../infraestructura-operativa.md`](../infraestructura-operativa.md)
