fase 1 productizacion: capa de configuracion (glosario multi-giro, sucursales y categorias configurables, ajustes de clinica por instancia, colores de marca, horarios, URLs de pago dinamicas) + script de provisioning de instancia nueva con datos demo + frontend consumiendo la config
This commit is contained in:
224
docs/nueva-instancia.md
Normal file
224
docs/nueva-instancia.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# Nueva instancia white-label (provisioning automatizado)
|
||||
|
||||
Cómo levantar una clínica/spa nueva sobre el mismo código y la misma VPS, con
|
||||
sus propios secretos, puerto, base de datos, vhost, marca y configuración. Es
|
||||
la versión automatizada de `docs/segunda-instancia.md` (que sigue vigente como
|
||||
referencia del procedimiento manual y del caso `clinica2`).
|
||||
|
||||
El código de los addons es compartido y read-only (`/root/skeen-odoo/addons`);
|
||||
cada instancia es solo infra + base de datos + configuración. La marca no vive
|
||||
en el código: sale del parámetro `skeen.clinic.brand` (API `/brand`).
|
||||
|
||||
## Requisitos previos
|
||||
|
||||
- VPS con Odoo, PostgreSQL, nginx, Node y el venv `/root/odoo-venv` ya
|
||||
instalados (ver `instalacion.md`).
|
||||
- Al menos una instancia de referencia (`skeen_odoo`) con los módulos `skeen_*`
|
||||
instalados: de ahí se copia la lista de módulos a inicializar.
|
||||
- Un slug, nombre comercial, dominio y puerto libres para la instancia nueva.
|
||||
|
||||
## Uso en 2 pasos
|
||||
|
||||
```bash
|
||||
cd /root/skeen-proyecto/infra/provision
|
||||
|
||||
# Paso 1 — SIEMPRE simular primero. No escribe ni ejecuta nada.
|
||||
./provision_clinica.py \
|
||||
--slug spa-bella --nombre "Spa Bella" \
|
||||
--dominio spa.consultoria-as.com --puerto 8071 --db spa_bella_odoo \
|
||||
--pack spa --demo --dry-run
|
||||
|
||||
# Paso 2 — ejecución real (coordinada, fuera de horario pico).
|
||||
./provision_clinica.py \
|
||||
--slug spa-bella --nombre "Spa Bella" \
|
||||
--dominio spa.consultoria-as.com --puerto 8071 --db spa_bella_odoo \
|
||||
--pack spa --demo
|
||||
```
|
||||
|
||||
## Opciones
|
||||
|
||||
| Opción | Default | Descripción |
|
||||
|---|---|---|
|
||||
| `--slug` | — | Identificador corto, `^[a-z0-9-]+$`. Nombra conf, unit, vhost y frontend. |
|
||||
| `--nombre` | — | Nombre comercial (marca, empresa, menú raíz). |
|
||||
| `--dominio` | — | Dominio público, p. ej. `spa.consultoria-as.com`. |
|
||||
| `--puerto` | — | Puerto HTTP de Odoo; longpolling usa `puerto+3`. |
|
||||
| `--db` | `<slug>_odoo` | Nombre de la base de datos. |
|
||||
| `--db-role` | `<slug>` | Rol PostgreSQL. Por defecto se crea uno propio con password nueva; `--db-role skeen` usa el rol compartido (ver abajo). |
|
||||
| `--pack` | `generico` | Giro para glosario y demo: `generico`, `spa`, `clinica`, `derma`. |
|
||||
| `--demo` | off | Siembra servicios, clientes, empleados, citas, paquetes y monedero de ejemplo. |
|
||||
| `--demo-limpia` | off | Corre `demo_limpiar.py` al final (deja la instancia sin datos demo). |
|
||||
| `--dominio-lan` | — | Puerto LAN extra en el vhost (p. ej. `8881` → `http://<ip>:8881`). |
|
||||
| `--admin-login` | `admin` | Login del usuario admin del frontend. |
|
||||
| `--pie` | genérico | Línea del pie de página de la marca. |
|
||||
| `--salida` | `/root/backups/<slug>-credenciales-<fecha>.txt` | Archivo de credenciales (chmod 600). |
|
||||
| `--sin-wacrm` / `--con-wacrm` | bloquear | Por defecto el vhost corta `/api/odoo/skeen/frontend/v1/wacrm/` con 403. |
|
||||
| `--skip-build` | off | No recompilar el frontend; reusar `/root/skeen-frontend/dist`. |
|
||||
| `--force` | off | Continuar aunque existan colisiones (puerto/DB/unit/vhost/dirs). |
|
||||
| `--dry-run` | off | Imprime pasos, contenido de archivos y comandos sin ejecutar nada. |
|
||||
|
||||
## Qué crea
|
||||
|
||||
Con `--slug spa-bella --puerto 8071 --db spa_bella_odoo`:
|
||||
|
||||
| Recurso | Valor |
|
||||
|---|---|
|
||||
| Rol PostgreSQL | `spa_bella` (LOGIN, password aleatoria) |
|
||||
| Base de datos | `spa_bella_odoo` (owner = rol propio) |
|
||||
| Conf Odoo | `/root/spa-bella-odoo.conf` (chmod 600) |
|
||||
| data_dir | `/var/lib/odoo-spa-bella` |
|
||||
| Log | `/tmp/odoo-spa-bella.log` |
|
||||
| Unit systemd | `odoo-spa-bella.service` |
|
||||
| Vhost nginx | `/etc/nginx/sites-available/spa-bella` + symlink en `sites-enabled` |
|
||||
| Frontend | `/var/www/spa-bella-frontend` |
|
||||
| Credenciales | `/root/backups/spa-bella-credenciales-<fecha>.txt` |
|
||||
|
||||
## Orden de ejecución del script
|
||||
|
||||
1. **Validaciones**: slug/DB/puerto libres, sin unit/vhost/dir/conf previos.
|
||||
Aborta listando colisiones salvo `--force`.
|
||||
2. **Secretos propios**: `admin_passwd` (master de Odoo), `db_password` y
|
||||
password del admin del frontend, todos aleatorios.
|
||||
3. **DB**: crea el rol y `su postgres -c "createdb -O <rol> <db>"` (el rol
|
||||
`skeen` no tiene CREATEDB; se crea como `postgres`).
|
||||
4. **`odoo.conf`** desde plantilla: puerto, db, addons_path, log propio,
|
||||
`without_demo = all`, sin workers.
|
||||
5. **Unit systemd** desde plantilla + `daemon-reload` + `enable`.
|
||||
6. **Módulos**: corre `odoo-bin ... --stop-after-init --no-http -i <módulos>`.
|
||||
7. **Post-instalación** (`postinstall_clinica.py`): marca, empresa, admin,
|
||||
y siembra tolerante de glosario/sucursal/settings (abajo).
|
||||
8. **Menú raíz**: actualiza el xmlid `skeen_pacientes.menu_skeen_root` a
|
||||
`{"en_US": "<nombre>"}` (igual que `clinica2_odoo`).
|
||||
9. **Vhost nginx** + `nginx -t` + `reload`.
|
||||
10. **Frontend**: build compartido + `rsync --delete` + logos + `chown`.
|
||||
11. **Arranque**: `systemctl start`.
|
||||
12. **Credenciales** y resumen (público, LAN, logs, cómo limpiar).
|
||||
|
||||
### Lista de módulos a inicializar
|
||||
|
||||
Se toma de la instancia de referencia en vez de hardcodearla:
|
||||
|
||||
```bash
|
||||
su postgres -c "psql -d skeen_odoo -Atc \
|
||||
\"SELECT name FROM ir_module_module WHERE state='installed' AND name LIKE 'skeen%'\""
|
||||
```
|
||||
|
||||
El script consulta eso y ordena los módulos topológicamente con
|
||||
`ir_module_module_dependency` (dependencias primero). Odoo resuelve igual las
|
||||
dependencias, pero el orden explícito evita la trampa documentada en
|
||||
`segunda-instancia.md` (el menú raíz debe existir antes de que las vistas que
|
||||
lo referencian se carguen).
|
||||
|
||||
## Post-instalación y el nuevo sistema de configuración
|
||||
|
||||
`postinstall_clinica.py` corre con el ORM de Odoo y hace:
|
||||
|
||||
- Brand neutro en `skeen.clinic.brand` (nombre comercial, logos
|
||||
`/brand/logo-blanco.svg` y `/brand/logo-negro.svg`, pie genérico).
|
||||
- `res.company.name` = nombre.
|
||||
- Admin del frontend (`skeen.frontend.user`) con la password generada y
|
||||
`must_change_password = True`.
|
||||
- Oculta WACRM en `skeen.menu_roles` (salvo `--con-wacrm`).
|
||||
|
||||
Y **solo si existen en runtime** siembra los parámetros/modelos del nuevo
|
||||
sistema de configuración (otro cambio en curso):
|
||||
|
||||
| Qué | Cómo lo detecta | Qué siembra |
|
||||
|---|---|---|
|
||||
| Glosario | llave `ir.config_parameter` `skeen.glosario` **o** modelo `skeen.glosario` | `pack` elegido |
|
||||
| `skeen.clinic.settings` | el modelo existe | nombre, `url_publica = https://<dominio>`, horario 08:00–19:00, moneda MXN |
|
||||
| `skeen.sucursal` | el modelo existe | 1 sucursal default con el nombre del negocio |
|
||||
| `skeen.service.category` | el modelo existe | semilla de categorías del pack |
|
||||
|
||||
Lo que no exista se reporta como `TODO` en la salida y **no aborta** el
|
||||
provisioning. Cuando ese cambio esté desplegado, una corrida nueva ya lo
|
||||
siembra; para una instancia ya creada, se puede re-ejecutar solo el
|
||||
post-install:
|
||||
|
||||
```bash
|
||||
cat > /tmp/post.json <<'JSON'
|
||||
{"conf": "/root/spa-bella-odoo.conf", "db": "spa_bella_odoo",
|
||||
"nombre": "Spa Bella", "dominio": "spa.consultoria-as.com",
|
||||
"pack": "spa", "admin_login": "admin", "admin_password": "<la-generada>",
|
||||
"con_wacrm": false, "demo": false}
|
||||
JSON
|
||||
/root/odoo-venv/bin/python /root/skeen-proyecto/infra/provision/postinstall_clinica.py /tmp/post.json
|
||||
```
|
||||
|
||||
## Datos demo
|
||||
|
||||
`--demo` siembra ~10 servicios con precios, ~15 clientes, 2–3 empleados con
|
||||
`staff_type`, ~20 citas en la semana actual y algunos paquetes/monedero, todo
|
||||
coherente con el `--pack`. Los registros quedan marcados (`DEMO-` en códigos de
|
||||
servicio, `referred_by='DEMO'`, `[DEMO]` en notas) y sus ids en el parámetro
|
||||
`skeen.demo.ids`.
|
||||
|
||||
Para borrarlos antes de entregar la instancia a un cliente:
|
||||
|
||||
```bash
|
||||
/root/odoo-venv/bin/python /root/skeen-proyecto/infra/provision/demo_limpiar.py \
|
||||
--conf /root/spa-bella-odoo.conf --db spa_bella_odoo
|
||||
```
|
||||
|
||||
(o pasar `--demo-limpia` en la corrida de provisioning).
|
||||
|
||||
## Frontend
|
||||
|
||||
El build es **compartido**: un solo bundle (`npm run build` en
|
||||
`/root/skeen-frontend`) se copia con `rsync -a --delete` a cada
|
||||
`/var/www/<slug>-frontend`. La marca y el giro salen del API, no del build; por
|
||||
eso no hace falta compilar por instancia (usar `--skip-build` cuando ya haya un
|
||||
`dist/` fresco). Los logos genéricos se copian desde
|
||||
`/var/www/clinica2-frontend/brand/` (o `frontend/public/brand/`) a
|
||||
`<destino>/brand/`.
|
||||
|
||||
## Acceso público (paso manual)
|
||||
|
||||
Igual que con `clinica2`, el túnel cloudflared es remotamente administrado: hay
|
||||
que agregar el ingress en el dashboard (Zero Trust → Networks → Tunnels):
|
||||
|
||||
```yaml
|
||||
- hostname: spa.consultoria-as.com
|
||||
service: http://localhost:80
|
||||
```
|
||||
|
||||
El vhost ya escucha en el 80 con ese `server_name`. El `--dominio-lan` deja
|
||||
además un acceso LAN permanente.
|
||||
|
||||
## Rol compartido vs rol propio (decisión de diseño)
|
||||
|
||||
El procedimiento manual creaba la DB con `-O skeen` y usaba
|
||||
`db_password = skeen_dev_2026` para todas las instancias. Eso obliga a
|
||||
reutilizar la misma password y no permite "secretos propios por instancia". Por
|
||||
eso el script, por defecto, crea un **rol propio** (`<slug>`) con password
|
||||
aleatoria y hace a ese rol owner de la DB. No toca el rol `skeen` ni ninguna
|
||||
instancia existente.
|
||||
|
||||
Si se necesita reproducir el esquema compartido:
|
||||
|
||||
```bash
|
||||
./provision_clinica.py ... --db-role skeen
|
||||
```
|
||||
|
||||
En ese modo el script avisa que `db_password` en la conf debe coincidir con la
|
||||
password real del rol (no la cambia).
|
||||
|
||||
## Fallos a medias y limpieza
|
||||
|
||||
Si algo falla, el script imprime **qué quedó hecho** y el comando para
|
||||
deshacerlo (rol, DB, conf, unit, vhost, frontend, service). Opciones:
|
||||
`--force` para saltar colisiones, o limpiar a mano con la lista de undo que
|
||||
imprime.
|
||||
|
||||
## Verificación tras una corrida real
|
||||
|
||||
```bash
|
||||
systemctl status odoo-<slug> # active (running)
|
||||
tail -50 /tmp/odoo-<slug>.log
|
||||
curl -s http://127.0.0.1:<puerto>/web/health
|
||||
curl -s http://127.0.0.1:<puerto>/skeen/frontend/v1/brand # nombre comercial
|
||||
nginx -t
|
||||
```
|
||||
|
||||
Y en el navegador: login con el admin del frontend (fuerza cambio de password),
|
||||
verificar el nombre en el menú, el logo y que WACRM esté bloqueado (403).
|
||||
Reference in New Issue
Block a user