# OTEC — versión adaptada para hosting cPanel (sin root)

Copia de `plataforma` modificada para poder correr en un cPanel de terceros
(sin acceso WHM/root), donde el usuario MySQL de la cuenta **no** tiene
privilegio `CREATE DATABASE` / `CREATE USER`. Probado y confirmado paso a
paso el 2026-09-28 contra la cuenta `vacunapr` en `cp007.servidoresph.com`.

Si más adelante mueves OTEC a un VPS con MySQL root propio (ej. el mismo
servidor donde corre Guardix), esta copia NO es necesaria: ahí sirve la
copia original con `PermissionControlledMySQLDatabaseManager` de
`stancl/tenancy` tal cual venía.

## Qué cambia respecto al original

1. **`app/Services/Tenancy/CpanelUapiMySQLDatabaseManager.php`** (nuevo).
   Reemplaza la creación/borrado de base + usuario MySQL por cada OTEC.
   En vez de `CREATE DATABASE` / `CREATE USER` / `GRANT` por SQL (que este
   hosting no permite), llama a la API de cuenta de cPanel (`uapi`), que sí
   puede hacerlo porque corre con los permisos internos de cPanel, no con
   los del usuario MySQL de la app.

   Ya viene con salvaguardas:
   - Verifica, después de crear la base, que el nombre no quedó truncado
     (cPanel puede truncar nombres largos **sin avisar** — lo confirmamos
     con una prueba real: pedimos 62 caracteres y quedó en 55, con
     `status: 1` de éxito igual). Si detecta un nombre distinto al pedido,
     aborta y borra lo que se alcanzó a crear.
   - Si falla la creación del usuario o el otorgamiento de privilegios,
     revierte la base ya creada, para no dejar tenants a medio configurar.
   - Como usa `uapi` (proceso externo) y no SQL, tampoco genera el problema
     de "commit implícito" que `CREATE`/`DROP DATABASE`/`USER` causan dentro
     de una transacción de Laravel (la nota que ya tenían en
     `OtecProvisioner::eliminar()`).

2. **`config/tenancy.php`**. El manager de `mysql`/`mariadb` ahora se elige
   con la variable de entorno `TENANT_DB_MANAGER`:
   - `cpanel_uapi` (default en el `.env` de esta copia) → usa la clase nueva.
   - cualquier otro valor → vuelve al comportamiento original
     (`PermissionControlledMySQLDatabaseManager` / `MySQLDatabaseManager`,
     según `TENANT_DB_USER_PER_TENANT`), para cuando se despliegue en un
     servidor con MySQL root propio.

3. **`app/Services/OtecProvisioner.php`**. Límite de largo del `slug` de
   OTEC bajado de 40 a 25 caracteres. Motivo: en cPanel el nombre final de
   la base es `<prefijo_cuenta>_otec_<slug>` (ej. `vacunapr_otec_ainhoa`).
   Con slug de 40 se podía llegar muy cerca del límite de truncado que
   encontramos en las pruebas (55 caracteres). Con 25 queda con margen de
   sobra. Este límite es ajustable si cambian de hosting.

4. **`.env` y `.env.example`**:
   - `TENANT_DB_MANAGER=cpanel_uapi` (nuevo).
   - `DB_DATABASE` / `DB_USERNAME` / `DB_PASSWORD` de la conexión **central**
     puestos en placeholder (`vacunapr_otec_central` / `CAMBIAR_POR_LA_CLAVE_REAL`):
     las credenciales del VPS anterior no sirven aquí y no tenía sentido
     dejarlas, para que no se usen por error. Hay que crear esa base +
     usuario en cPanel > MySQL Databases antes del primer deploy (ver abajo).
   - `SESSION_DRIVER`, `CACHE_STORE` y `QUEUE_CONNECTION` cambiados de
     `redis` a `database`: en esta cuenta cPanel no hay servidor Redis
     instalado (solo la extensión PHP, sin `redis-cli`) y no hay root para
     instalarlo. Las tablas `sessions`, `cache`, `jobs`/`failed_jobs` ya
     existen en las migraciones de la base central, así que el cambio no
     requiere migraciones nuevas. Si el hosting llega a ofrecer Redis como
     addon más adelante, se puede volver a `redis` sin tocar código.

5. **`vendor/` no está incluido** (pesaba 826 MB, en su mayoría carpetas
   `.git` de los paquetes de Composer). Se reconstruye con
   `composer install` usando el `composer.lock` que sí viene incluido.

## Antes del primer deploy

En el **Terminal de cPanel**, con la cuenta destino:

1. Crear la base y usuario de la conexión **central** (una sola vez, a mano
   o por `uapi`, como cualquier app normal — no dinámico como los tenants):

   ```bash
   uapi Mysql create_database name=vacunapr_otec_central
   uapi Mysql create_user name=vacunapr_otec_central password='UNA_CLAVE_FUERTE'
   uapi Mysql set_privileges_on_database user=vacunapr_otec_central database=vacunapr_otec_central privileges=ALL
   ```

   Actualiza `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD` en `.env` con esos
   mismos valores.

2. Confirmar que `shell_exec`/`Process` funciona igual desde PHP-FPM (el
   proceso que realmente atiende las peticiones web), no solo desde PHP CLI
   por SSH — eso ya se probó por CLI, falta confirmarlo por web:

   ```bash
   php -r "echo shell_exec('uapi Mysql list_databases 2>&1');" > /tmp/test-cli.txt
   ```

   y además, desde un archivo público accesible por navegador (bórralo
   después de probar):

   ```php
   <?php echo '<pre>'.shell_exec('uapi Mysql list_databases 2>&1').'</pre>';
   ```

   Si ambos devuelven el mismo listado de bases, confirmado.

## Deploy

En el **Terminal de cPanel**, dentro de la carpeta donde subas el proyecto:

```bash
composer install --no-dev --prefer-dist --optimize-autoloader
php artisan key:generate
php artisan migrate --database=central --force
php artisan storage:link
```

Ajustar en cPanel:

- **Document Root** del dominio/subdominio apuntando a `public/` del
  proyecto (no a la raíz).
- **Cron** cada minuto ejecutando `php artisan schedule:run` (necesario
  para el scheduler de Laravel).
- **Wildcard DNS/subdominio** `*.<tu-dominio-otec>` apuntando al mismo
  Document Root, para que cada OTEC (`ainhoa.tu-dominio.cl`, etc.) resuelva.

## Prueba de aceptación (antes de dar de alta un cliente real)

```bash
php artisan tinker
```

```php
$r = app(App\Services\OtecProvisioner::class)->crear([
    'slug' => 'prueba1',
    'nombre' => 'OTEC de Prueba',
    'email' => 'prueba@ejemplo.cl',
]);
```

Revisar en cPanel > MySQL Databases que aparezca `vacunapr_otec_prueba1`
con su propio usuario. Entrar a `prueba1.<tu-dominio-otec>/panel` y
verificar que carga. Luego eliminar:

```php
App\Models\Tenant::find('prueba1')->delete();
```

Y confirmar en cPanel que la base y el usuario desaparecieron.

## Origen de este respaldo

Rescatado el 2026-09-28 desde el VPS `169.197.92.212`
(`ssg.guardix.cl`), bases `plataforma_central` (17 tablas) y `otec_ainhoa`
(32 tablas, tenant real del cliente Ainhoa). Esos dumps SQL están en la
carpeta hermana `Respaldo final rescatado VPS con BD principal y 1 tenant`,
junto al respaldo de archivos original con `vendor/` incluido.
