# DDL Specification: core_rbac Schema

## Identificacion

| Campo | Valor |
|-------|-------|
| **Schema** | core_rbac |
| **Modulo** | MGN-003 Roles/RBAC |
| **Version** | 1.0 |
| **Fecha** | 2025-12-05 |
| **Estado** | Ready |

---

## Diagrama ER

```mermaid
erDiagram
    roles ||--o{ role_permissions : has
    roles ||--o{ user_roles : assigned_to
    permissions ||--o{ role_permissions : granted_to
    users ||--o{ user_roles : has
    tenants ||--o{ roles : owns

    roles {
        uuid id PK
        uuid tenant_id FK
        string name
        string slug
        string description
        boolean is_built_in
        boolean is_active
        timestamp created_at
        timestamp updated_at
        uuid created_by FK
        uuid updated_by FK
        timestamp deleted_at
        uuid deleted_by FK
    }

    permissions {
        uuid id PK
        string code UK
        string name
        string description
        string module
        string parent_code FK
        boolean is_deprecated
        int sort_order
        timestamp created_at
    }

    role_permissions {
        uuid id PK
        uuid role_id FK
        uuid permission_id FK
        timestamp created_at
        uuid created_by FK
    }

    user_roles {
        uuid id PK
        uuid user_id FK
        uuid role_id FK
        timestamp assigned_at
        uuid assigned_by FK
        timestamp expires_at
    }
```

---

## Tablas

### 1. roles

Almacena los roles del sistema, tanto built-in como personalizados por tenant.

```sql
CREATE TABLE core_rbac.roles (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES core_tenants.tenants(id),
    name VARCHAR(50) NOT NULL,
    slug VARCHAR(50) NOT NULL,
    description VARCHAR(500),
    is_built_in BOOLEAN NOT NULL DEFAULT false,
    is_active BOOLEAN NOT NULL DEFAULT true,
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES core_users.users(id),
    updated_by UUID REFERENCES core_users.users(id),
    deleted_at TIMESTAMPTZ,
    deleted_by UUID REFERENCES core_users.users(id),

    CONSTRAINT uq_roles_tenant_name UNIQUE (tenant_id, name) WHERE deleted_at IS NULL,
    CONSTRAINT uq_roles_tenant_slug UNIQUE (tenant_id, slug) WHERE deleted_at IS NULL,
    CONSTRAINT chk_roles_name_length CHECK (char_length(name) >= 3),
    CONSTRAINT chk_roles_slug_format CHECK (slug ~ '^[a-z0-9_-]+$')
);

-- Indices
CREATE INDEX idx_roles_tenant_id ON core_rbac.roles(tenant_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_roles_slug ON core_rbac.roles(slug) WHERE deleted_at IS NULL;
CREATE INDEX idx_roles_is_built_in ON core_rbac.roles(is_built_in);
CREATE INDEX idx_roles_created_at ON core_rbac.roles(created_at DESC);

-- Trigger para updated_at
CREATE TRIGGER trg_roles_updated_at
    BEFORE UPDATE ON core_rbac.roles
    FOR EACH ROW
    EXECUTE FUNCTION update_updated_at_column();

-- Comentarios
COMMENT ON TABLE core_rbac.roles IS 'Roles del sistema para RBAC';
COMMENT ON COLUMN core_rbac.roles.is_built_in IS 'true para roles del sistema (admin, user, etc.)';
COMMENT ON COLUMN core_rbac.roles.slug IS 'Identificador URL-friendly, unico por tenant';
```

---

### 2. permissions

Catalogo maestro de permisos del sistema. Son globales (no por tenant).

```sql
CREATE TABLE core_rbac.permissions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    code VARCHAR(100) NOT NULL UNIQUE,
    name VARCHAR(100) NOT NULL,
    description VARCHAR(500),
    module VARCHAR(50) NOT NULL,
    parent_code VARCHAR(100) REFERENCES core_rbac.permissions(code),
    is_deprecated BOOLEAN NOT NULL DEFAULT false,
    sort_order INT NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT chk_permissions_code_format CHECK (code ~ '^[a-z]+:[a-z_]+(:?:[a-z_]+)?$|^[a-z]+:\*$')
);

-- Indices
CREATE INDEX idx_permissions_module ON core_rbac.permissions(module);
CREATE INDEX idx_permissions_parent ON core_rbac.permissions(parent_code);
CREATE INDEX idx_permissions_deprecated ON core_rbac.permissions(is_deprecated);

-- Comentarios
COMMENT ON TABLE core_rbac.permissions IS 'Catalogo de permisos del sistema';
COMMENT ON COLUMN core_rbac.permissions.code IS 'Formato: modulo:accion o modulo:recurso:accion';
COMMENT ON COLUMN core_rbac.permissions.parent_code IS 'Para permisos wildcard (users:* -> users:read)';
```

---

### 3. role_permissions

Tabla de union entre roles y permisos (M:N).

```sql
CREATE TABLE core_rbac.role_permissions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    role_id UUID NOT NULL REFERENCES core_rbac.roles(id) ON DELETE CASCADE,
    permission_id UUID NOT NULL REFERENCES core_rbac.permissions(id) ON DELETE CASCADE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES core_users.users(id),

    CONSTRAINT uq_role_permissions UNIQUE (role_id, permission_id)
);

-- Indices
CREATE INDEX idx_role_permissions_role ON core_rbac.role_permissions(role_id);
CREATE INDEX idx_role_permissions_permission ON core_rbac.role_permissions(permission_id);

-- Comentarios
COMMENT ON TABLE core_rbac.role_permissions IS 'Asignacion de permisos a roles';
```

---

### 4. user_roles

Tabla de union entre usuarios y roles (M:N).

```sql
CREATE TABLE core_rbac.user_roles (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES core_users.users(id) ON DELETE CASCADE,
    role_id UUID NOT NULL REFERENCES core_rbac.roles(id) ON DELETE CASCADE,
    assigned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    assigned_by UUID REFERENCES core_users.users(id),
    expires_at TIMESTAMPTZ,

    CONSTRAINT uq_user_roles UNIQUE (user_id, role_id)
);

-- Indices
CREATE INDEX idx_user_roles_user ON core_rbac.user_roles(user_id);
CREATE INDEX idx_user_roles_role ON core_rbac.user_roles(role_id);
CREATE INDEX idx_user_roles_expires ON core_rbac.user_roles(expires_at) WHERE expires_at IS NOT NULL;

-- Comentarios
COMMENT ON TABLE core_rbac.user_roles IS 'Asignacion de roles a usuarios';
COMMENT ON COLUMN core_rbac.user_roles.expires_at IS 'Roles temporales con fecha de expiracion';
```

---

## Data Seed: Roles Built-in

```sql
-- Roles del sistema (se crean para cada tenant nuevo)
-- Ref: Odoo base_groups.xml - group_user, group_portal, group_public, group_system, group_erp_manager
INSERT INTO core_rbac.roles (tenant_id, name, slug, description, is_built_in) VALUES
    (:tenant_id, 'Super Administrador', 'super_admin', 'Acceso total al sistema (Ref: Odoo group_system)', true),
    (:tenant_id, 'Propietario', 'tenant_owner', 'Propietario de la cuenta, gestiona billing y usuarios (MGN-015)', true),
    (:tenant_id, 'Administrador', 'admin', 'Gestion completa del tenant (Ref: Odoo group_erp_manager)', true),
    (:tenant_id, 'Gerente', 'manager', 'Supervision operativa y reportes', true),
    (:tenant_id, 'Usuario', 'user', 'Acceso basico al sistema (Ref: Odoo group_user)', true),
    (:tenant_id, 'Agente WhatsApp', 'whatsapp_agent', 'Atiende conversaciones de WhatsApp (MGN-017)', true),
    (:tenant_id, 'Usuario Portal', 'portal_user', 'Usuario externo con acceso limitado (Ref: Odoo group_portal)', true),
    (:tenant_id, 'Invitado', 'guest', 'Acceso de solo lectura limitado (Ref: Odoo group_public)', true);
```

---

## Data Seed: Permisos del Sistema

```sql
-- Permisos de Auth (MGN-001)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('auth:sessions:read', 'Ver sesiones', 'Ver sesiones activas del usuario', 'auth', 10),
    ('auth:sessions:revoke', 'Revocar sesiones', 'Cerrar sesiones activas', 'auth', 20);

-- Permisos de Users (MGN-002)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('users:*', 'Todos los permisos de usuarios', 'Acceso completo a gestion de usuarios', 'users', 0),
    ('users:read', 'Leer usuarios', 'Ver listado y detalle de usuarios', 'users', 10),
    ('users:create', 'Crear usuarios', 'Crear nuevos usuarios', 'users', 20),
    ('users:update', 'Actualizar usuarios', 'Modificar datos de usuarios', 'users', 30),
    ('users:delete', 'Eliminar usuarios', 'Eliminar usuarios (soft delete)', 'users', 40),
    ('users:activate', 'Activar/Desactivar', 'Cambiar estado de usuarios', 'users', 50),
    ('users:export', 'Exportar usuarios', 'Exportar lista de usuarios a CSV', 'users', 60),
    ('users:import', 'Importar usuarios', 'Importar usuarios desde CSV', 'users', 70);

-- Establecer parent para wildcards
UPDATE core_rbac.permissions SET parent_code = 'users:*' WHERE module = 'users' AND code != 'users:*';

-- Permisos de Roles (MGN-003)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('roles:*', 'Todos los permisos de roles', 'Acceso completo a gestion de roles', 'roles', 0),
    ('roles:read', 'Leer roles', 'Ver listado y detalle de roles', 'roles', 10),
    ('roles:create', 'Crear roles', 'Crear nuevos roles personalizados', 'roles', 20),
    ('roles:update', 'Actualizar roles', 'Modificar roles existentes', 'roles', 30),
    ('roles:delete', 'Eliminar roles', 'Eliminar roles personalizados', 'roles', 40),
    ('roles:assign', 'Asignar roles', 'Asignar roles a usuarios', 'roles', 50),
    ('permissions:read', 'Ver permisos', 'Ver catalogo de permisos', 'roles', 60);

UPDATE core_rbac.permissions SET parent_code = 'roles:*' WHERE module = 'roles' AND code NOT IN ('roles:*', 'permissions:read');

-- Permisos de Tenants (MGN-004)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('tenants:read', 'Ver tenant', 'Ver configuracion del tenant', 'tenants', 10),
    ('tenants:update', 'Actualizar tenant', 'Modificar configuracion del tenant', 'tenants', 20),
    ('tenants:billing', 'Facturacion', 'Gestionar facturacion del tenant', 'tenants', 30);

-- Permisos de Settings (MGN-006)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('settings:read', 'Ver configuracion', 'Ver configuracion del sistema', 'settings', 10),
    ('settings:update', 'Modificar configuracion', 'Cambiar configuracion del sistema', 'settings', 20);

-- Permisos de Audit (MGN-007)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('audit:read', 'Ver auditoria', 'Ver logs de auditoria', 'audit', 10),
    ('audit:export', 'Exportar auditoria', 'Exportar logs de auditoria', 'audit', 20);

-- Permisos de Reports (MGN-009)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('reports:*', 'Todos los permisos de reportes', 'Acceso completo a reportes', 'reports', 0),
    ('reports:read', 'Ver reportes', 'Ver reportes del sistema', 'reports', 10),
    ('reports:create', 'Crear reportes', 'Crear reportes personalizados', 'reports', 20),
    ('reports:export', 'Exportar reportes', 'Exportar reportes a PDF/Excel', 'reports', 30),
    ('reports:schedule', 'Programar reportes', 'Programar envio de reportes', 'reports', 40);

UPDATE core_rbac.permissions SET parent_code = 'reports:*' WHERE module = 'reports' AND code != 'reports:*';

-- Permisos de Financial (MGN-010)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('financial:*', 'Todos los permisos financieros', 'Acceso completo a modulo financiero', 'financial', 0),
    ('financial:accounts:read', 'Ver cuentas', 'Ver plan de cuentas', 'financial', 10),
    ('financial:accounts:manage', 'Gestionar cuentas', 'Administrar plan de cuentas', 'financial', 20),
    ('financial:transactions:read', 'Ver transacciones', 'Ver movimientos contables', 'financial', 30),
    ('financial:transactions:create', 'Crear transacciones', 'Registrar movimientos', 'financial', 40),
    ('financial:transactions:approve', 'Aprobar transacciones', 'Aprobar movimientos pendientes', 'financial', 50),
    ('financial:reports:read', 'Reportes financieros', 'Ver reportes financieros', 'financial', 60);

UPDATE core_rbac.permissions SET parent_code = 'financial:*' WHERE module = 'financial' AND code != 'financial:*';

-- Permisos de Inventory (MGN-011)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('inventory:*', 'Todos los permisos de inventario', 'Acceso completo a inventario', 'inventory', 0),
    ('inventory:products:read', 'Ver productos', 'Ver catalogo de productos', 'inventory', 10),
    ('inventory:products:create', 'Crear productos', 'Agregar productos al catalogo', 'inventory', 20),
    ('inventory:products:update', 'Actualizar productos', 'Modificar productos', 'inventory', 30),
    ('inventory:products:delete', 'Eliminar productos', 'Eliminar productos del catalogo', 'inventory', 40),
    ('inventory:stock:read', 'Ver stock', 'Ver niveles de inventario', 'inventory', 50),
    ('inventory:stock:adjust', 'Ajustar stock', 'Realizar ajustes de inventario', 'inventory', 60),
    ('inventory:movements:read', 'Ver movimientos', 'Ver historial de movimientos', 'inventory', 70),
    ('inventory:movements:create', 'Crear movimientos', 'Registrar entradas/salidas', 'inventory', 80);

UPDATE core_rbac.permissions SET parent_code = 'inventory:*' WHERE module = 'inventory' AND code != 'inventory:*';

-- Permisos de Portal (para usuarios externos) - Ref: Odoo portal module
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('portal:profile:read', 'Ver perfil propio', 'Ver datos del propio perfil', 'portal', 10),
    ('portal:profile:update', 'Actualizar perfil propio', 'Modificar datos del propio perfil', 'portal', 20),
    ('portal:documents:read', 'Ver documentos propios', 'Ver documentos asociados al usuario', 'portal', 30),
    ('portal:orders:read', 'Ver ordenes propias', 'Ver historial de ordenes', 'portal', 40),
    ('portal:invoices:read', 'Ver facturas propias', 'Ver facturas emitidas', 'portal', 50),
    ('portal:tickets:read', 'Ver tickets propios', 'Ver tickets de soporte', 'portal', 60),
    ('portal:tickets:create', 'Crear tickets', 'Crear nuevos tickets de soporte', 'portal', 70);

-- Permisos de Billing (MGN-015)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('billing:*', 'Todos los permisos de billing', 'Acceso completo a facturacion SaaS', 'billing', 0),
    ('billing:subscription:read', 'Ver suscripcion', 'Ver plan y suscripcion actual', 'billing', 10),
    ('billing:subscription:manage', 'Gestionar suscripcion', 'Cambiar plan, cancelar, reactivar', 'billing', 20),
    ('billing:seats:manage', 'Gestionar asientos', 'Agregar/remover usuarios del plan', 'billing', 30),
    ('billing:payment_methods:read', 'Ver metodos de pago', 'Ver tarjetas y metodos guardados', 'billing', 40),
    ('billing:payment_methods:manage', 'Gestionar metodos de pago', 'Agregar/eliminar metodos de pago', 'billing', 50),
    ('billing:invoices:read', 'Ver facturas SaaS', 'Ver historial de facturas de la plataforma', 'billing', 60),
    ('billing:invoices:download', 'Descargar facturas', 'Descargar PDF/CFDI de facturas', 'billing', 70),
    ('billing:usage:read', 'Ver uso', 'Ver metricas de consumo del plan', 'billing', 80);

UPDATE core_rbac.permissions SET parent_code = 'billing:*' WHERE module = 'billing' AND code != 'billing:*';

-- Permisos de Payment Integrations (MGN-016)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('payments:*', 'Todos los permisos de pagos', 'Acceso completo a integraciones de pago', 'payments', 0),
    ('payments:providers:read', 'Ver proveedores', 'Ver integraciones de pago configuradas', 'payments', 10),
    ('payments:providers:configure', 'Configurar proveedores', 'Conectar MercadoPago, Clip, Stripe', 'payments', 20),
    ('payments:terminals:read', 'Ver terminales', 'Ver terminales registradas', 'payments', 30),
    ('payments:terminals:manage', 'Gestionar terminales', 'Agregar/configurar terminales', 'payments', 40),
    ('payments:transactions:read', 'Ver transacciones', 'Ver historial de cobros', 'payments', 50),
    ('payments:transactions:process', 'Procesar pagos', 'Cobrar en terminal/gateway', 'payments', 60),
    ('payments:transactions:refund', 'Reembolsar', 'Procesar devoluciones', 'payments', 70),
    ('payments:reconciliation:read', 'Ver conciliacion', 'Ver estado de conciliacion', 'payments', 80),
    ('payments:reconciliation:manage', 'Gestionar conciliacion', 'Conciliar transacciones', 'payments', 90);

UPDATE core_rbac.permissions SET parent_code = 'payments:*' WHERE module = 'payments' AND code != 'payments:*';

-- Permisos de WhatsApp (MGN-017)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('whatsapp:*', 'Todos los permisos de WhatsApp', 'Acceso completo a WhatsApp Business', 'whatsapp', 0),
    ('whatsapp:accounts:read', 'Ver cuentas', 'Ver cuentas de WhatsApp conectadas', 'whatsapp', 10),
    ('whatsapp:accounts:configure', 'Configurar cuentas', 'Conectar/desconectar cuentas WA', 'whatsapp', 20),
    ('whatsapp:templates:read', 'Ver templates', 'Ver plantillas de mensajes', 'whatsapp', 30),
    ('whatsapp:templates:manage', 'Gestionar templates', 'Crear/editar plantillas', 'whatsapp', 40),
    ('whatsapp:conversations:read', 'Ver conversaciones', 'Ver inbox de mensajes', 'whatsapp', 50),
    ('whatsapp:conversations:reply', 'Responder mensajes', 'Enviar mensajes en conversaciones', 'whatsapp', 60),
    ('whatsapp:conversations:assign', 'Asignar conversaciones', 'Asignar a otros agentes', 'whatsapp', 70),
    ('whatsapp:bulk:send', 'Envio masivo', 'Enviar campanas de marketing', 'whatsapp', 80),
    ('whatsapp:chatbot:read', 'Ver chatbot', 'Ver flujos de chatbot', 'whatsapp', 90),
    ('whatsapp:chatbot:manage', 'Gestionar chatbot', 'Crear/editar flujos', 'whatsapp', 100),
    ('whatsapp:analytics:read', 'Ver analiticas', 'Ver metricas de WhatsApp', 'whatsapp', 110);

UPDATE core_rbac.permissions SET parent_code = 'whatsapp:*' WHERE module = 'whatsapp' AND code != 'whatsapp:*';

-- Permisos de AI Agents (MGN-018)
INSERT INTO core_rbac.permissions (code, name, description, module, sort_order) VALUES
    ('ai_agents:*', 'Todos los permisos de IA', 'Acceso completo a agentes de IA', 'ai_agents', 0),
    ('ai_agents:agents:read', 'Ver agentes', 'Ver agentes configurados', 'ai_agents', 10),
    ('ai_agents:agents:manage', 'Gestionar agentes', 'Crear/editar/eliminar agentes', 'ai_agents', 20),
    ('ai_agents:kb:read', 'Ver bases de conocimiento', 'Ver KBs del tenant', 'ai_agents', 30),
    ('ai_agents:kb:manage', 'Gestionar KB', 'Crear/editar KBs y documentos', 'ai_agents', 40),
    ('ai_agents:kb:upload', 'Subir documentos', 'Agregar documentos a KB', 'ai_agents', 50),
    ('ai_agents:tools:read', 'Ver herramientas', 'Ver tools disponibles', 'ai_agents', 60),
    ('ai_agents:tools:manage', 'Gestionar herramientas', 'Configurar tools para agentes', 'ai_agents', 70),
    ('ai_agents:conversations:read', 'Ver conversaciones IA', 'Ver historial de chats con IA', 'ai_agents', 80),
    ('ai_agents:feedback:read', 'Ver feedback', 'Ver calificaciones de respuestas', 'ai_agents', 90),
    ('ai_agents:feedback:manage', 'Gestionar feedback', 'Corregir/entrenar agente', 'ai_agents', 100),
    ('ai_agents:analytics:read', 'Ver analiticas IA', 'Ver metricas de agentes', 'ai_agents', 110),
    ('ai_agents:usage:read', 'Ver consumo tokens', 'Ver uso de tokens de IA', 'ai_agents', 120);

UPDATE core_rbac.permissions SET parent_code = 'ai_agents:*' WHERE module = 'ai_agents' AND code != 'ai_agents:*';
```

---

## Data Seed: Permisos por Rol Built-in

```sql
-- Funcion helper para asignar permisos
CREATE OR REPLACE FUNCTION core_rbac.assign_permissions_to_role(
    p_tenant_id UUID,
    p_role_slug VARCHAR,
    p_permission_codes TEXT[]
) RETURNS void AS $$
DECLARE
    v_role_id UUID;
    v_permission_id UUID;
    v_code TEXT;
BEGIN
    SELECT id INTO v_role_id FROM core_rbac.roles
    WHERE tenant_id = p_tenant_id AND slug = p_role_slug;

    FOREACH v_code IN ARRAY p_permission_codes LOOP
        SELECT id INTO v_permission_id FROM core_rbac.permissions WHERE code = v_code;
        IF v_permission_id IS NOT NULL THEN
            INSERT INTO core_rbac.role_permissions (role_id, permission_id)
            VALUES (v_role_id, v_permission_id)
            ON CONFLICT DO NOTHING;
        END IF;
    END LOOP;
END;
$$ LANGUAGE plpgsql;

-- Super Admin: Todos los permisos (wildcard)
SELECT core_rbac.assign_permissions_to_role(:tenant_id, 'super_admin', ARRAY[
    'users:*', 'roles:*', 'permissions:read', 'tenants:read', 'tenants:update', 'tenants:billing',
    'settings:read', 'settings:update', 'audit:read', 'audit:export',
    'reports:*', 'financial:*', 'inventory:*',
    'billing:*', 'payments:*', 'whatsapp:*', 'ai_agents:*'
]);

-- Tenant Owner: Propietario de cuenta (MGN-015)
SELECT core_rbac.assign_permissions_to_role(:tenant_id, 'tenant_owner', ARRAY[
    'users:*', 'roles:read', 'roles:assign',
    'tenants:read', 'tenants:update', 'tenants:billing',
    'billing:*',
    'payments:providers:read', 'payments:providers:configure',
    'whatsapp:accounts:read', 'whatsapp:accounts:configure', 'whatsapp:analytics:read',
    'ai_agents:agents:read', 'ai_agents:usage:read', 'ai_agents:analytics:read'
]);

-- Admin: Gestion del tenant
SELECT core_rbac.assign_permissions_to_role(:tenant_id, 'admin', ARRAY[
    'users:*', 'roles:read', 'roles:create', 'roles:update', 'roles:delete', 'roles:assign',
    'permissions:read', 'tenants:read', 'tenants:update',
    'settings:read', 'settings:update', 'audit:read',
    'reports:read', 'reports:export',
    'payments:*', 'whatsapp:*', 'ai_agents:*'
]);

-- Manager: Supervision
SELECT core_rbac.assign_permissions_to_role(:tenant_id, 'manager', ARRAY[
    'users:read', 'roles:read', 'permissions:read',
    'audit:read', 'reports:read', 'reports:export',
    'financial:accounts:read', 'financial:transactions:read', 'financial:reports:read',
    'inventory:products:read', 'inventory:stock:read', 'inventory:movements:read',
    'payments:transactions:read', 'payments:reconciliation:read',
    'whatsapp:conversations:read', 'whatsapp:analytics:read',
    'ai_agents:conversations:read', 'ai_agents:feedback:read', 'ai_agents:analytics:read'
]);

-- User: Acceso basico
SELECT core_rbac.assign_permissions_to_role(:tenant_id, 'user', ARRAY[
    'reports:read',
    'payments:transactions:read'
]);

-- WhatsApp Agent: Atiende conversaciones (MGN-017)
SELECT core_rbac.assign_permissions_to_role(:tenant_id, 'whatsapp_agent', ARRAY[
    'whatsapp:conversations:read', 'whatsapp:conversations:reply',
    'whatsapp:templates:read'
]);

-- Portal User: Acceso limitado para usuarios externos (Ref: Odoo group_portal)
SELECT core_rbac.assign_permissions_to_role(:tenant_id, 'portal_user', ARRAY[
    'portal:profile:read', 'portal:profile:update',
    'portal:documents:read', 'portal:orders:read',
    'portal:invoices:read', 'portal:tickets:read', 'portal:tickets:create'
]);

-- Guest: Solo lectura minima (Ref: Odoo group_public)
SELECT core_rbac.assign_permissions_to_role(:tenant_id, 'guest', ARRAY[
    -- Sin permisos especificos, solo acceso a dashboard publico
]);
```

---

## Row Level Security (RLS)

```sql
-- Habilitar RLS
ALTER TABLE core_rbac.roles ENABLE ROW LEVEL SECURITY;
ALTER TABLE core_rbac.role_permissions ENABLE ROW LEVEL SECURITY;
ALTER TABLE core_rbac.user_roles ENABLE ROW LEVEL SECURITY;

-- Politicas para roles
CREATE POLICY roles_tenant_isolation ON core_rbac.roles
    FOR ALL
    USING (tenant_id = current_setting('app.tenant_id')::uuid);

CREATE POLICY roles_select_built_in ON core_rbac.roles
    FOR SELECT
    USING (is_built_in = true);

-- Politicas para role_permissions (via rol)
CREATE POLICY role_permissions_tenant_isolation ON core_rbac.role_permissions
    FOR ALL
    USING (
        role_id IN (
            SELECT id FROM core_rbac.roles
            WHERE tenant_id = current_setting('app.tenant_id')::uuid
        )
    );

-- Politicas para user_roles (via usuario)
CREATE POLICY user_roles_tenant_isolation ON core_rbac.user_roles
    FOR ALL
    USING (
        user_id IN (
            SELECT id FROM core_users.users
            WHERE tenant_id = current_setting('app.tenant_id')::uuid
        )
    );
```

---

## Funciones de Utilidad

### Verificar Permiso de Usuario

```sql
CREATE OR REPLACE FUNCTION core_rbac.user_has_permission(
    p_user_id UUID,
    p_permission_code VARCHAR
) RETURNS BOOLEAN AS $$
DECLARE
    v_has_permission BOOLEAN;
    v_module VARCHAR;
BEGIN
    -- Verificar permiso directo
    SELECT EXISTS (
        SELECT 1
        FROM core_rbac.user_roles ur
        JOIN core_rbac.role_permissions rp ON rp.role_id = ur.role_id
        JOIN core_rbac.permissions p ON p.id = rp.permission_id
        WHERE ur.user_id = p_user_id
        AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
        AND p.code = p_permission_code
    ) INTO v_has_permission;

    IF v_has_permission THEN
        RETURN true;
    END IF;

    -- Verificar wildcard del modulo
    v_module := split_part(p_permission_code, ':', 1);

    SELECT EXISTS (
        SELECT 1
        FROM core_rbac.user_roles ur
        JOIN core_rbac.role_permissions rp ON rp.role_id = ur.role_id
        JOIN core_rbac.permissions p ON p.id = rp.permission_id
        WHERE ur.user_id = p_user_id
        AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
        AND p.code = v_module || ':*'
    ) INTO v_has_permission;

    RETURN v_has_permission;
END;
$$ LANGUAGE plpgsql STABLE;
```

### Obtener Permisos Efectivos de Usuario

```sql
CREATE OR REPLACE FUNCTION core_rbac.get_user_permissions(
    p_user_id UUID
) RETURNS TABLE (permission_code VARCHAR, source_role VARCHAR) AS $$
BEGIN
    RETURN QUERY
    SELECT DISTINCT p.code, r.name
    FROM core_rbac.user_roles ur
    JOIN core_rbac.roles r ON r.id = ur.role_id
    JOIN core_rbac.role_permissions rp ON rp.role_id = r.id
    JOIN core_rbac.permissions p ON p.id = rp.permission_id
    WHERE ur.user_id = p_user_id
    AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
    AND r.is_active = true
    AND p.is_deprecated = false
    ORDER BY p.code;
END;
$$ LANGUAGE plpgsql STABLE;
```

---

## Vistas

### Vista: Roles con Conteo de Usuarios

```sql
CREATE VIEW core_rbac.vw_roles_summary AS
SELECT
    r.id,
    r.tenant_id,
    r.name,
    r.slug,
    r.description,
    r.is_built_in,
    r.is_active,
    COUNT(DISTINCT ur.user_id) AS users_count,
    COUNT(DISTINCT rp.permission_id) AS permissions_count,
    r.created_at
FROM core_rbac.roles r
LEFT JOIN core_rbac.user_roles ur ON ur.role_id = r.id
LEFT JOIN core_rbac.role_permissions rp ON rp.role_id = r.id
WHERE r.deleted_at IS NULL
GROUP BY r.id;
```

### Vista: Permisos Agrupados por Modulo

```sql
CREATE VIEW core_rbac.vw_permissions_by_module AS
SELECT
    module,
    jsonb_agg(
        jsonb_build_object(
            'id', id,
            'code', code,
            'name', name,
            'description', description,
            'isDeprecated', is_deprecated
        ) ORDER BY sort_order
    ) AS permissions
FROM core_rbac.permissions
WHERE is_deprecated = false
GROUP BY module
ORDER BY module;
```

---

## Resumen de Tablas

| Tabla | Columnas | Descripcion |
|-------|----------|-------------|
| roles | 12 | Roles del sistema (built-in y custom) |
| permissions | 9 | Catalogo maestro de permisos |
| role_permissions | 5 | Union M:N roles-permisos |
| user_roles | 6 | Union M:N usuarios-roles |

**Total: 4 tablas, 32 columnas**

---

## Resumen de Roles Built-in

| Rol | Slug | Descripcion |
|-----|------|-------------|
| Super Administrador | super_admin | Acceso total al sistema |
| Propietario | tenant_owner | Gestiona billing y usuarios (MGN-015) |
| Administrador | admin | Gestion completa del tenant |
| Gerente | manager | Supervision operativa y reportes |
| Usuario | user | Acceso basico al sistema |
| Agente WhatsApp | whatsapp_agent | Atiende conversaciones (MGN-017) |
| Usuario Portal | portal_user | Usuario externo con acceso limitado |
| Invitado | guest | Solo lectura limitada |

---

## Resumen de Permisos por Modulo

| Modulo | Permisos | Descripcion |
|--------|----------|-------------|
| auth | 2 | Sesiones y tokens |
| users | 8 | Gestion de usuarios |
| roles | 7 | Roles y permisos |
| tenants | 3 | Configuracion tenant |
| settings | 2 | Configuracion sistema |
| audit | 2 | Logs de auditoria |
| reports | 5 | Reportes |
| financial | 7 | Modulo financiero |
| inventory | 9 | Inventario |
| portal | 7 | Usuarios externos |
| billing | 9 | Billing SaaS (MGN-015) |
| payments | 10 | Pagos POS (MGN-016) |
| whatsapp | 12 | WhatsApp Business (MGN-017) |
| ai_agents | 13 | AI Agents (MGN-018) |

**Total: 96 permisos**

---

## Historial

| Version | Fecha | Autor | Cambios |
|---------|-------|-------|---------|
| 1.0 | 2025-12-05 | System | Creacion inicial |
| 1.1 | 2025-12-05 | System | Agregar rol portal_user y permisos portal:* (Ref: Odoo group_portal) |
| 1.2 | 2025-12-05 | System | Roles: tenant_owner, whatsapp_agent. Permisos: billing:*, payments:*, whatsapp:*, ai_agents:* (MGN-015/016/017/018) |
