segunda instancia white-label (clinica2) + fix de orden de menus en instalacion limpia

Infra (todo convive en la misma VPS, mismo addons_path read-only):
- infra/clinica2/: odoo.conf (puerto 8070, db clinica2_odoo, data_dir
  propio), odoo-clinica2.service (sin env vars de WACRM/Supabase/Hermes),
  nginx-vhost (80 con server_name clinica2.consultoria-as.com + 8880 LAN,
  /api/odoo -> 8070, /wacrm/ cortado con 403), postinstall.py (brand neutro,
  menu WACRM vacio, admin con password propio y must_change_password)
- docs/segunda-instancia.md: mapa completo de la instalacion, pasos para
  reproducir (instancia 3+), pendiente del hostname en dashboard del tunel
  (remotamente administrado), aislamiento de datos escenario B
- frontend/public/brand/: logos SVG neutros + favicon para la instancia 2

Fix de menus (aplica a todas las instalaciones limpias, una sola pasada):
- menu_skeen_root se mueve a skeen_pacientes (modulo raiz de la jerarquia);
  en bases existentes se transfirio el ownership del xmlid (ir_model_data)
  asi actualizar no duplica el menu
- menu_skeen_citas (padre) se declara en skeen_citas/views/menus.xml que
  carga antes que bloqueo/lista_espera; menu_views.xml conserva las entradas
  con action (cargan despues de cita_views, donde se define la accion)
- referencias actualizadas: pacientes(catalogos/recetas), monedero, ventas,
  inventario, visitas -> skeen_pacientes.menu_skeen_root

Verificacion: produccion (skeen_odoo) actualizada sin duplicar menus y API
intacta; instalacion limpia clinica2_odoo 9 modulos OK; 15/15 tests
end-to-end (brand neutro, login admin, WACRM oculto+403, datos vacios)
This commit is contained in:
2026-10-08 00:44:30 +00:00
parent 2f7cbe237e
commit 582c267ec0
19 changed files with 332 additions and 14 deletions

91
docs/segunda-instancia.md Normal file
View File

@@ -0,0 +1,91 @@
# Segunda instancia white-label (sin branding SKEEN)
Segunda clínica sobre el mismo código y la misma VPS. La marca no vive en el
código: sale del parámetro `skeen.clinic.brand` (ver `GET /skeen/frontend/v1/brand`),
así que esta y futuras instancias son solo infra + config.
## Mapa de la instalación
| Componente | SKEEN (instancia 1) | Clínica 2 (instancia white-label) |
|---|---|---|
| Odoo HTTP | 8069 | **8070** |
| Odoo longpolling | 8072 | **8073** |
| Base de datos | `skeen_odoo` | **`clinica2_odoo`** |
| Conf | `/root/skeen-odoo/odoo.conf` | **`/root/clinica2-odoo.conf`** |
| Systemd | `odoo-skeen.service` | **`odoo-clinica2.service`** |
| Frontend | `/var/www/skeen-frontend` | **`/var/www/clinica2-frontend`** |
| Nginx | vhost `skeen-crm` (80) | **vhost `clinica2` (80 + 8880)** |
| Acceso LAN | http://192.168.10.114 | **http://192.168.10.114:8880** |
| Acceso público | skeen.consultoria-as.com | **clinica2.consultoria-as.com (pendiente: dashboard del túnel)** |
| WhatsApp (WACRM) | integrado | **oculto + bloqueado (escenario B)** |
`addons_path` se comparte (read-only): `/root/skeen-odoo/addons`. Un solo
código, N bases de datos.
## Cómo se levantó (orden de pasos)
Los archivos canónicos están en `infra/clinica2/`:
1. `odoo.conf` — copia del de SKEEN con `db_name=clinica2_odoo`, `http_port=8070`,
`longpolling_port=8073`, `data_dir=/var/lib/odoo-clinica2`.
2. Crear la base (el rol `skeen` no tiene CREATEDB; se crea como `postgres`):
`CREATE DATABASE clinica2_odoo OWNER skeen ...`
3. Instalar módulos limpio (sin demo):
`odoo-bin -c /root/clinica2-odoo.conf -d clinica2_odoo --without-demo=all --init=skeen_whatsapp,skeen_chat,skeen_visitas --stop-after-init --no-http`
(skeen_whatsapp arrastra citas/pacientes/ventas/pagos/monedero; visitas
arrastra inventario).
4. `postinstall.py` — brand neutro (`skeen.clinic.brand`), menú WACRM vacío
para todos (`skeen.menu_roles`), password de admin propio +
`must_change_password`, empresa renombrada.
5. `systemctl enable --now odoo-clinica2`.
6. `nginx-vhost` → `/etc/nginx/sites-enabled/clinica2` + `nginx -t && reload`.
7. Frontend: mismo build de SKEEN (`rsync -a dist/ /var/www/clinica2-frontend/`)
más `public/brand/` (logos SVG neutros + favicon, versionados en el repo).
## Pendiente para acceso público (único paso manual)
El túnel cloudflared corre con `--token` (remotamente administrado): el
`config.yml` local NO aplica. En el dashboard de Cloudflare (Zero Trust →
Networks → Tunnels → f2517233…) agregar el ingress:
```yaml
- hostname: clinica2.consultoria-as.com
service: http://localhost:80
```
El vhost nginx ya escucha en el 80 con ese `server_name`; al agregar el
hostname el túnel enruta solo. El 8880 queda como acceso LAN permanente.
## Aislamiento de datos (escenario B: sin WhatsApp)
- Sin `SUPABASE_URL` / `WACRM_API_*` / `HERMES_BRIDGE_URL` en la unit systemd
(el backend respondería "Falta configuración").
- Aun así, el backend tiene fallback a `localhost:3000`; por eso el vhost
nginx corta `/api/odoo/skeen/frontend/v1/wacrm/` con `return 403;` — doble
candado, la instancia 2 no puede leer ni escribir las conversaciones de
SKEEN aunque alguien llame la API a mano.
- Menú WACRM oculto para todos los roles vía matriz (`/wacrm/*: []`).
Para activar WhatsApp en la instancia 2 (escenario A) se necesita: número
propio de WhatsApp Business + decisión de WACRM multi-clínica (estimar aparte).
## Fix de menús que esta fase destapó (aplica a TODAS las instancias)
Las vistas XML referenciaban `skeen_citas.menu_skeen_root`, pero `skeen_citas`
depende de `skeen_pacientes`: en instalación limpia de una sola pasada el
xmlid aún no existe y la carga falla (la base de SKEEN se construyó por fases
y por eso nunca explotó). Se movió el menú raíz a `skeen_pacientes` (módulo
raíz de la jerarquía) y todas las referencias apuntan a
`skeen_pacientes.menu_skeen_root`. En bases existentes se transfirió el
ownership del xmlid (`UPDATE ir_model_data SET module='skeen_pacientes' …`),
así actualizar no duplica el menú. Mismo tratamiento para el menú padre de
Citas (`skeen_citas/views/menus.xml` carga antes que bloqueos/lista de espera).
## Operación habitual
- Logs: `tail -f /tmp/odoo2.log` (instancia 2) vs `/tmp/odoo.log` (SKEEN).
- Ambas instancias comparten código: un `git pull` + rebuild afecta el build
de ambas solo si se vuelven a copiar los `dist/` (cada uno tiene el suyo).
- Para una tercera clínica: repetir pasos 1–7 con `clinica3` (puertos 8074/8075,
db `clinica3_odoo`, vhost propio) y setear su brand desde
Configuración → Clínica o con `PUT /brand`.