# Licenciamiento por plan Producto comercial: cada instancia white-label puede llevar una **licencia firmada** que decide qué módulos incluye y sus límites de uso. Sin licencia aplicada (el default) el sistema se comporta **exactamente como hoy**. ## Bloque `licencia` de clinic-settings ```json { "plan": "custom", "modo": "aviso", "cliente": "", "vence": "", "clave": "", "modulos": [], "limites": { "usuarios_max": 0, "sucursales_max": 0, "citas_mes_max": 0, "almacenamiento_mb": 0 } } ``` * `plan`: `basico | profesional | enterprise | custom`. * `modo`: `aviso` (default; **solo advierte**) o `bloqueo` (aplica de verdad). * `limites`: `0` = **sin límite** (default → nada cambia). * `modulos`: lista de módulos incluidos; **vacío = todos**. * `clave`: la clave firmada por el vendedor. Vacía = sin licencia aplicada. La licencia **nunca** bloquea lo indispensable: login, agenda, pacientes, POS, inventario, reportes y configuración siempre están disponibles. Solo aplica a los *módulos gateables* y a los límites indicados. ## Tabla de planes | Plan | Módulos incluidos | | --- | --- | | `basico` | `agenda`, `pacientes`, `pos`, `inventario`, `reportes` (sin whatsapp, notificaciones, reservas, recursos, membresías, compras, comisiones, formularios) | | `profesional` | Básico **+** `whatsapp`, `notificaciones`, `reservas_online`, `recursos` | | `enterprise` | Todos los módulos (los 5 base + los 8 gateables) | | `custom` | Lo que diga la clave (si no lista módulos, todos) | Módulos *gateables* (controlables por licencia): `whatsapp`, `notificaciones`, `reservas_online`, `recursos`, `membresias`, `compras`, `comisiones`, `formularios`. ## Aplicación con `modo=bloqueo` * **Módulos no incluidos**: sus secciones desaparecen del menú, sus endpoints responden **403** con un mensaje claro (`Módulo no incluido en tu plan`) y su bloque `activo` de clinic-settings se considera apagado. * **Límites**: se impide crear más allá de `usuarios_max`, `sucursales_max`, `citas_mes_max` (citas del mes en curso) y `almacenamiento_mb` (suma de adjuntos). El error indica el límite y cómo ampliarlo. * Con `modo=aviso` nada se bloquea; los excesos solo quedan advertidos. Las tareas internas (cron, migraciones) pueden saltarse los límites con el contexto `skeen_licencia_skip=True`. ## Formato de la clave de licencia Texto en una línea: ``` SKN1.. ``` * `payload_json` = JSON con claves ordenadas y separadores mínimos: `cliente`, `plan`, `vence`, `emitida`, `modulos`, `limites`. * Firmado con **HMAC-SHA256** y la clave del vendedor. ### Clave del vendedor Debe estar presente en la instancia para poder validar (HMAC es simétrico): 1. `ir.config_parameter 'skeen.licencia.vendor_key'` 2. Variable de entorno `SKEEN_LICENCIA_VENDOR_KEY` 3. Archivo `/root/.skeen-licencias/vendor.key` (permisos `600`) > **Ruta del secreto:** `/root/.skeen-licencias/vendor.key` (fuera del repo, 600). > Nota de seguridad: al ser HMAC simétrico, quien tenga la clave del vendedor > puede forjar licencias; mantenerla fuera de las instancias de cliente en un > despliegue real (aquí vive en un VPS de desarrollo compartido). ### Herramienta del vendedor `/root/skeen-proyecto/infra/licencias/generar_licencia.py` (CLI, Python stdlib): ```bash generar_licencia.py generar --cliente "Spa Bella" --plan profesional --vence 2027-12-31 generar_licencia.py generar --cliente "Spa Bella" --plan custom --vence 2027-12-31 \ --modulos agenda,pacientes,pos,inventario,reportes,compras \ --usuarios-max 10 --sucursales-max 2 --citas-mes-max 500 --almacenamiento-mb 4096 generar_licencia.py verificar --clave "SKN1...." ``` ## Endpoints | Método | Ruta | Acceso | Descripción | | --- | --- | --- | --- | | GET | `/skeen/frontend/v1/licencia` | admin/gerencia | Estado completo + uso vs límites | | PUT | `/skeen/frontend/v1/licencia` | admin | Aplicar/actualizar/quitar licencia | | POST | `/skeen/frontend/v1/licencia/validar` | admin/gerencia | Probar una clave sin aplicarla | `PUT` con `{"clave": "SKN1..."}` verifica firma y vigencia; si es inválida o vencida responde **400** y **no sobrescribe** la licencia previa. `{"clave": ""}` quita la licencia y vuelve a habilitarlo todo. `{"modo": "bloqueo"}` cambia el modo sin re-firmar. ## UI Configuración → pestaña **Licencia** (admin; gerencia también ve el estado): plan y cliente, vencimiento con días restantes, campo para pegar la clave, tabla de uso vs límites con semáforo, tabla de módulos incluidos/no incluidos, avisos de vencimiento (≤ 15 días) y, si no hay licencia, el mensaje *"sin licencia aplicada — todas las funciones habilitadas"*.