Skip to content

Feature Flag — modulo_portal_clientes

Módulo: Portal de Clientes — Arquitectura Tipo: Architecture Estado: Implementado Fecha: 2026-05-12


Descripción

modulo_portal_clientes es el flag que controla si el Portal de Clientes está habilitado para un tenant. Opera como submódulo de membresías: solo puede estar activo si modulo_membresias = 1.

El flag afecta tres capas de forma coordinada:

  1. Migraciones — las tablas portal_* no se crean si el flag está en 0
  2. Seeds — las claves portal.* en data_config no se insertan si el flag está en 0
  3. Servicio — el endpoint de configuración de gateway retorna 403 si el portal está deshabilitado

Ubicación del Flag

sql
-- Tabla: sistema (schema public, nivel EMPRESA)
-- Columna: modulos (JSONB)
{
  "modulo_membresias": 1,
  "modulo_portal_clientes": 1   -- 0 = deshabilitado, 1 = habilitado
}

El flag existe en sistema.modulos con default backward-compatible:

  • Si modulo_membresias = 1modulo_portal_clientes se inserta con valor 1
  • Si modulo_membresias = 0modulo_portal_clientes se inserta con valor 0
  • La migración es idempotente (solo inserta si la clave no existe)

Jerarquía de Módulos

modulo_ventas (prerequisito de membresías)
    └─ modulo_ctacte (prerequisito de membresías)
            └─ modulo_membresias
                    └─ modulo_portal_clientes  ← este flag

isPortalClientesEnabled() = isMembresiasEnabled() AND isModuleEnabled('modulo_portal_clientes')

Si membresías está deshabilitado, el portal está deshabilitado sin importar el valor del flag propio.


Guard de Migraciones

Archivo: migrations/MigrationTrait.php

php
protected function isPortalClientesEnabled(): bool
{
    return $this->isMembresiasEnabled() && $this->isModuleEnabled('modulo_portal_clientes');
}

Las migraciones del portal implementan shouldExecute():

php
public function shouldExecute(): bool
{
    return $this->shouldRunOnLevel()
        && $this->isPortalClientesEnabled()
        && !$this->haveTable('portal_users');
}

Si modulo_portal_clientes = 0, las migraciones hacen skip con log de confirmación. Las tablas portal_users, portal_payments, etc. no se crean.


Guard de Servicio

Archivo: Modules/Portal/Application/Services/PortalConfigService.php

El método guardGatewayAccess() verifica dos condiciones en orden:

php
private function guardGatewayAccess(): void
{
    if (!$this->authService->hasPermission('config.gateway')) {
        $this->logger->warning('AccessDenied', [
            'reason' => 'PERMISSION_MISSING',
            'permission' => 'config.gateway',
        ]);
        throw new Forbidden('No tienes permiso para acceder a configuración de gateway');
    }

    if (!$this->authService->isPortalClientesEnabled()) {
        $this->logger->warning('AccessDenied', [
            'reason' => 'PORTAL_DISABLED',
            'module' => 'modulo_portal_clientes',
        ]);
        throw new Forbidden('Portal de clientes no está habilitado. Contacta a administración.');
    }
}

El logging diferenciado (PERMISSION_MISSING vs PORTAL_DISABLED) permite distinguir en auditoría si el acceso fue bloqueado por permisos del usuario o por configuración del módulo.


Degradación Graceful en Frontend ERP

useGatewayConfig() en bautista-app maneja el 403 sin lanzar error:

typescript
// Si el backend retorna 403 (portal deshabilitado o sin permiso):
// → isEnabled: false, config: null
// GatewayConfigSection NO se renderiza (return null)

El operador sin permiso o sobre un tenant con portal deshabilitado simplemente no ve la sección. No hay mensajes de error visibles ni crashes.


Escenarios

Escenariomembresiasportal flagpermisoResultado
Portal habilitado11Config accesible, tablas creadas, seeds insertados
Portal deshabilitado10Config retorna 403 (PORTAL_DISABLED), tablas no creadas
Sin membresías0cualquieraPortal deshabilitado (prerequisito no cumplido)
Sin permiso11noConfig retorna 403 (PERMISSION_MISSING)

Seeds de DataConfig

Las claves portal.gateway.* y portal.recibo.* en data_config solo se insertan si isPortalClientesEnabled() retorna true:

php
// migrations/seeds/tenancy/DataConfig.php
if ($this->isPortalClientesEnabled()) {
    // Inserta portal.gateway.nombre, portal.gateway.api_key, etc.
    // Inserta portal.recibo.cuenta_bancaria, portal.recibo.caja_schema
}

Ver también