| Campo | Valor |
|---|---|
| Plataforma | ConnectCond — Gestão de Condomínios, Cotações e Prestadores |
| Documento | Arquitetura Oficial RBAC Enterprise |
| Versão | 1.0.0 |
| Data | 06 de Agosto de 2026 |
| Ambiente | Produção (Base44 BaaS) |
| Responsável | Equipe de Engenharia ConnectCond |
| Status | ✅ Homologado |
A ConnectCond opera como uma plataforma SaaS multi-tenant onde Administradoras, Condomínios, Prestadores de Serviço e Fornecedores coexistem em um mesmo ambiente. Cada perfil de organização possui necessidades de acesso radicalmente diferentes, e dentro de cada organização, colaboradores precisam de níveis distintos de controle.
A arquitetura RBAC Enterprise foi criada para resolver três problemas fundamentais:
| Princípio | Implementação |
|---|---|
| Princípio do Menor Privilégio | Todo colaborador recebe o conjunto mínimo de permissões necessário para sua função. Overrides só adicionam permissões específicas, nunca concedem acesso irrestrito. |
| Segregação de Funções | Quem convida colaboradores (team:invite) não é necessariamente quem pode alterar permissões (settings:edit). Apenas o Proprietário pode customizar permissões. |
| Auditoria Completa | Toda alteração de permissão é registrada no AuditLog com usuário, ação, entidade, timestamp e metadados. |
| Resolução Determinística | As permissões finais são sempre calculadas pela mesma fórmula: base_permissions + ADD - REMOVE. Não há ambiguidade. |
| Compatibilidade RLS | A arquitetura opera em conjunto com o Row-Level Security nativo da Base44, sem conflito. |
┌─────────────────────────────────────────────────────────────────┐
│ ORGANIZAÇÃO (Tenant) │
│ │
│ ┌──────────────────┐ │
│ │ Perfil da Org │ administration | condominium | │
│ │ (organization_ │ provider | supplier │
│ │ type) │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Role │ Proprietário | Gestor | │
│ │ (Perfil de │ Colaborador | Consulta │
│ │ Acesso) │ │
│ │ │ base_permissions = [ │
│ │ │ "quotations:create", │
│ │ │ "proposals:approve", │
│ │ │ ... │
│ │ │ ] │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Permissões Base │ Conjunto de permissões atômicas │
│ │ (base_perms) │ definidas pelo Perfil de Acesso │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ MemberPermission │ Overrides personalizados pelo │
│ │ (Overrides) │ Proprietário: │
│ │ │ │
│ │ │ ADD → adiciona permissão extra │
│ │ │ REMOVE → subtrai permissão da base │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ PERMISSÕES │ FÓRMULA OFICIAL: │
│ │ FINAIS │ │
│ │ │ base_permissions │
│ │ │ + ADD(MemberPermission) │
│ │ │ - REMOVE(MemberPermission) │
│ │ │ = Permissões Finais │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ APLICAÇÃO │ Frontend (usePermissions.can()) │
│ │ │ Backend (orgContextResolver.hasPermission)│
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Imagine que cada organização na ConnectCond é como um prédio de escritórios:
Finalidade: Define os 4 Perfis de Acesso oficiais da plataforma, cada um com um conjunto base de permissões atômicas. Existem 16 registros no total (4 Perfis × 4 tipos de organização).
Relacionamento: Cada Role pertence a um organization_type e é referenciada por OrganizationMember.role_id.
| Campo | Tipo | Obrigatório | Finalidade | Exemplo |
|---|---|---|---|---|
name | string | Sim | Nome do Perfil de Acesso | "Proprietário" |
organization_type | enum | Sim | Tipo de organização ao qual pertence | "condominium" |
base_permissions | array<string> | Não | Lista de chaves de permissão atômicas da Role base | ["quotations:create", "proposals:approve"] |
description | string | Não | Descrição legível do Perfil e responsabilidades | "Administrador de segurança..." |
is_default | boolean | Não | Indica se é o Perfil padrão para novos convites | true (Gestor) |
is_system | boolean | Não | Perfis de sistema não podem ser excluídos | true (Proprietário) |
Campo de compatibilidade:
permissions(array) — mantido em sincronia combase_permissionspara código legado que ainda referenciarole.permissions. Ambos são atualizados juntos peloseedPermissions.
Finalidade: Catálogo mestre de todas as permissões atômicas da plataforma, organizadas por módulo e segmentadas por tipo de organização.
Relacionamento: Referenciada por Role.base_permissions (por chave) e por MemberPermission.permission_key.
| Campo | Tipo | Obrigatório | Finalidade | Exemplo |
|---|---|---|---|---|
key | string | Sim | Chave única no formato modulo:acao | "quotations:create" |
module | enum | Sim | Módulo da plataforma | "quotations" |
description | string | Não | Descrição legível | "Criar cotações" |
organization_types | array<enum> | Não | Perfis de organização que têm acesso | ["administration", "condominium"] |
Módulos disponíveis: dashboard, team, solicitations, quotations, proposals, orders, contracts, documents, finance, chat, ai, subscription, settings, integrations, calendar, reports, erp, whatsapp, reviews, condominiums, suppliers, providers, notifications.
Finalidade: Armazena as personalizações individuais de permissão aplicadas pelo Proprietário a um colaborador específico, além (ou aquém) do que seu Perfil de Acesso define.
Relacionamento: Pertence a um OrganizationMember (via member_id) e a uma organization_id.
| Campo | Tipo | Obrigatório | Finalidade | Exemplo |
|---|---|---|---|---|
member_id | string | Sim | ID do OrganizationMember ao qual se aplica | "abc-123" |
organization_id | string | Sim | ID da organização (para RLS multi-tenant) | "cond-456" |
permission_key | string | Sim | Chave da permissão | "quotations:approve" |
operation | enum | Sim | ADD (adiciona) ou REMOVE (subtrai) | "ADD" |
granted_by | string | Não | UUID do Proprietário que concedeu/removeu | "user-789" |
granted_at | date-time | Não | Data/hora da alteração | "2026-08-06T14:30:00Z" |
Finalidade: Entidade central que vincula um usuário autenticado a uma organização, definindo seu Perfil de Acesso e status dentro daquela organização.
Relacionamento: Referencia Role (via role_id e role), User (via user_id), e é pai de MemberPermission.
| Campo | Tipo | Obrigatório | Finalidade | Exemplo |
|---|---|---|---|---|
user_id | string | Sim | UUID do usuário (User.id) | "usr-001" |
user_email | string | Não | E-mail do usuário (identificação/visualização) | "joao@email.com" |
full_name | string | Não | Nome completo do colaborador | "João Silva" |
phone | string | Não | Telefone do colaborador | "+5511999999999" |
position | string | Não | Cargo/função descritivo | "Comprador" |
professional_function | string | Não | Função profissional (informativa — sem efeito em autorização) | "Síndico" |
department | enum | Não | Departamento (aplicável à equipe interna ConnectCond) | "comercial" |
organization_id | string | Sim | ID da organização | "cond-456" |
organization_type | enum | Sim | Tipo da organização vinculada | "condominium" |
role | string | Não | Nome do Perfil de Acesso (compatibilidade RLS) | "Proprietário" |
role_id | string | Não | UUID da Role (fonte oficial da verdade) | "role-abc" |
status | enum | Não | Status do vínculo | "active" |
invited_by | string | Não | UUID de quem enviou o convite | "usr-admin" |
invited_by_name | string | Não | Nome de quem enviou o convite | "Maria Souza" |
invited_at | date-time | Não | Data/hora do convite | "2026-08-01T10:00:00Z" |
accepted_at | date-time | Não | Data/hora de aceitação | "2026-08-02T14:00:00Z" |
last_access | date-time | Não | Último acesso à organização | "2026-08-06T09:00:00Z" |
Estratégia Dual-Field:
role(string) erole_id(UUID) são mantidos em sincronia automática.role_idé a fonte oficial da verdade;role(string) é mantido para compatibilidade com as regras RLS existentes que comparam por nome.
professional_functionvsrole: A função profissional (professional_function) é estritamente informativa — exibida na UI, nunca usada em autorização ou RLS. O Perfil de Acesso (role/role_id) é o que determina permissões.
Finalidade: Registra toda ação relevante executada no sistema, com foco em alterações de permissão, convites e mudanças de cargo.
Relacionamento: Referencia User (via user_id e user_email) e qualquer entidade afetada (via entity_name + entity_id).
| Campo | Tipo | Obrigatório | Finalidade | Exemplo |
|---|---|---|---|---|
user_email | string | Sim | E-mail do usuário que executou a ação | "maria@email.com" |
user_id | string | Não | ID do usuário | "usr-001" |
action | enum | Sim | Tipo de ação executada | "create" |
entity_name | string | Não | Nome da entidade afetada | "MemberPermission" |
entity_id | string | Não | ID do registro afetado | "mp-789" |
description | string | Não | Descrição legível da ação | "Permissão quotations:approve (ADD) definida para João Silva." |
ip_address | string | Não | Endereço IP de origem | "200.150.1.1" |
metadata | object | Não | Dados adicionais estruturados | { permission_key, operation, member_id } |
Valores de action: create, update, delete, login, logout, export, view, approve, reject.
RLS do AuditLog: Apenas Super Admins (
role: "admin") podem ler, atualizar ou excluir registros de auditoria. A criação é aberta para que funções de backend possam registrar eventos.
┌──────────────────┐
│ Role │
│──────────────────│
│ id │
│ name │
│ organization_type│
│ base_permissions │
│ is_system │
│ is_default │
└────────┬─────────┘
│
│ role_id (UUID)
│ role (string, compatibilidade)
▼
┌──────────────────┐ ┌──────────────────────┐
│ OrganizationMember│─────────│ MemberPermission │
│──────────────────│ 1 : N │──────────────────────│
│ id │────────▶│ id │
│ user_id │ │ member_id ───────────┘
│ organization_id │ │ organization_id
│ organization_type│ │ permission_key
│ role │ │ operation (ADD/REMOVE)
│ role_id │ │ granted_by
│ status │ │ granted_at
│ professional_ │ └──────────────────────┘
│ function │ │
│ invited_by │ │ permission_key
│ accepted_at │ │ (referência por chave)
└────────┬─────────┘ ▼
│ ┌──────────────────────┐
│ user_id │ Permission │
│ (UUID) │──────────────────────│
▼ │ id │
┌──────────────────┐ │ key (único) │
│ User (Base44) │ │ module │
│──────────────────│ │ description │
│ id │ │ organization_types │
│ email │ └──────────────────────┘
│ role (platform) │
│ organization_ids │
└──────────────────┘
│
│ Toda ação relevante
▼
┌──────────────────┐
│ AuditLog │
│──────────────────│
│ id │
│ user_id │
│ user_email │
│ action │
│ entity_name │
│ entity_id │
│ description │
│ metadata │
└──────────────────┘
| Relacionamento | Cardinalidade | Descrição |
|---|---|---|
| Role → OrganizationMember | 1 : N | Uma Role é referenciada por múltiplos OrganizationMembers. A referência é dupla: role_id (UUID, fonte oficial) + role (string, compatibilidade RLS). |
| OrganizationMember → MemberPermission | 1 : N | Um colaborador pode ter múltiplos overrides de permissão. Cada override é uma operação atômica (uma chave + uma operação). |
| MemberPermission → Permission | N : 1 (por chave) | MemberPermission referencia Permission por permission_key (não por ID). A validade da chave é garantida pelo catálogo. |
| User → OrganizationMember | 1 : N | Um usuário pode pertencer a múltiplas organizações (multi-tenant). User.organization_ids é um cache sincronizado com OrganizationMember. |
| OrganizationMember → AuditLog | 1 : N (indireto) | Toda alteração em OrganizationMember ou MemberPermission gera um registro no AuditLog. |
┌─────────────────────────────────────────────────────────────────┐
│ BANCO DE DADOS BASE44 │
│ │
│ ┌─────────────────────┐ │
│ │ Tabela: Role │ → Armazena os 16 Perfis de Acesso │
│ │ │ (4 roles × 4 org_types) │
│ │ • base_permissions │ e suas permissões base │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ Tabela: Permission │ → Armazena o catálogo de 52 │
│ │ │ permissões atômicas, segmentadas │
│ │ • key (único) │ por organization_types │
│ │ • organization_types│ │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ Tabela: │ → Armazena os overrides │
│ │ MemberPermission │ personalizados (ADD/REMOVE) │
│ │ │ aplicados a cada colaborador │
│ │ • member_id │ │
│ │ • operation │ │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ Tabela: │ → Armazena o VÍNCULO do usuário │
│ │ OrganizationMember │ com cada organização, incluindo: │
│ │ │ │
│ │ • role (string) │ → Compatibilidade RLS │
│ │ • role_id (UUID) │ → Fonte oficial da verdade │
│ │ • professional_ │ → Informativo (sem autorização) │
│ │ function │ │
│ │ • status │ → pending | active | blocked │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ Tabela: AuditLog │ → Armazena a trilha completa de │
│ │ │ auditoria de todas as ações │
│ │ • action │ relevantes (permissões, convites) │
│ │ • entity_name │ │
│ │ • metadata │ │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ Tabela: User │ → Identidade autenticada (Base44) │
│ │ (sistema) │ │
│ │ • organization_ids │ → Cache sincronizado (RLS support) │
│ │ • role (platform) │ → admin | condominio | prestador... │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Os seguintes campos existem apenas para compatibilidade e NÃO são a fonte da verdade:
| Campo | Entidade | Por que existe | Fonte oficial |
|---|---|---|---|
permissions | Role | Código legado referencia role.permissions | base_permissions |
role (string) | OrganizationMember | Regras RLS comparam por nome | role_id (UUID) |
user_email | OrganizationMember | Exibição e fallback | user_id (UUID) |
Regra arquitetural: Toda nova implementação deve usar a fonte oficial. Os campos de compatibilidade são mantidos em sincronia automática e nunca devem ser editados diretamente.
| Perfil de Acesso | Responsabilidade | Quem pode criar | Quem pode alterar permissões | is_system | is_default |
|---|---|---|---|---|---|
| Proprietário | Controle total da organização. Único que pode personalizar permissões de outros colaboradores. Acesso a configurações de segurança, assinatura e integrações. | Super Admin (plataforma) ou auto-criado no registro da organização | ✅ Sim (único) | true | false |
| Gestor | Administrador operacional. Gerencia equipe e operações do dia a dia. Sem acesso a configurações de segurança (assinatura, integrações, ERP, WhatsApp). | Proprietário ou Super Admin | ❌ Não | false | true |
| Colaborador | Usuário operacional. Acesso a criação e edição de registros operacionais (cotações, propostas, documentos). Sem permissões de aprovação, exclusão ou gestão. | Proprietário ou Gestor | ❌ Não | false | false |
| Consulta | Somente leitura. Visualiza informações sem permissão de alteração. Ideal para conselheiros, auditores e consultores. | Proprietário ou Gestor | ❌ Não | false | false |
As seguintes permissões são sempre excluídas dos Perfis Gestor, Colaborador e Consulta (apenas Proprietário as possui por padrão):
| Permissão | Módulo | Descrição |
|---|---|---|
subscription:manage | Assinatura | Gerenciar assinatura e faturamento |
settings:edit | Configurações | Editar configurações da organização |
integrations:configure | Integrações | Configurar integrações |
erp:sync | ERP | Sincronizar dados com ERP |
whatsapp:configure | Configurar WhatsApp |
Nota: O Proprietário pode conceder essas permissões individualmente via MemberPermission (ADD), mas por padrão apenas o Proprietário as possui.
| Chave | Descrição | Perfis que possuem |
|---|---|---|
dashboard:view | Visualizar painel da organização | Proprietário, Gestor, Colaborador, Consulta |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
team:view | Visualizar membros da equipe | Proprietário, Gestor, Colaborador, Consulta |
team:invite | Convidar novos colaboradores | Proprietário, Gestor, Colaborador |
team:manage | Gerenciar cargos e promover membros | Proprietário, Gestor |
team:remove | Remover membros da organização | Proprietário, Gestor |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
solicitations:view | Visualizar solicitações | Proprietário, Gestor, Colaborador, Consulta |
solicitations:create | Criar solicitações | Proprietário, Gestor, Colaborador |
solicitations:edit | Editar solicitações | Proprietário, Gestor, Colaborador |
solicitations:cancel | Cancelar solicitações | Proprietário, Gestor |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
quotations:view | Visualizar cotações | Proprietário, Gestor, Colaborador, Consulta |
quotations:create | Criar cotações | Proprietário, Gestor, Colaborador |
quotations:edit | Editar cotações | Proprietário, Gestor, Colaborador |
quotations:approve | Aprovar propostas de cotação | Proprietário, Gestor |
quotations:cancel | Cancelar cotações | Proprietário, Gestor |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
proposals:view | Visualizar propostas | Proprietário, Gestor, Colaborador, Consulta |
proposals:create | Enviar propostas | Proprietário, Gestor, Colaborador |
proposals:approve | Aprovar/selecionar propostas | Proprietário, Gestor |
proposals:reject | Rejeitar propostas | Proprietário, Gestor |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
orders:view | Visualizar pedidos | Proprietário, Gestor, Colaborador, Consulta |
orders:create | Criar pedidos | Proprietário, Gestor, Colaborador |
orders:edit | Editar pedidos | Proprietário, Gestor, Colaborador |
orders:approve | Aprovar pedidos | Proprietário, Gestor |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
contracts:view | Visualizar contratos | Proprietário, Gestor, Colaborador, Consulta |
contracts:sign | Assinar contratos | Proprietário, Gestor |
contracts:manage | Enviar e gerenciar contratos | Proprietário, Gestor, Colaborador |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
documents:view | Visualizar documentos | Proprietário, Gestor, Colaborador, Consulta |
documents:upload | Enviar documentos | Proprietário, Gestor, Colaborador |
documents:delete | Excluir documentos | Proprietário, Gestor |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
finance:view | Visualizar pagamentos e faturamento | Proprietário, Gestor, Colaborador, Consulta |
finance:create | Registrar pagamentos e notas fiscais | Proprietário, Gestor, Colaborador |
finance:approve | Aprovar/confirmar pagamentos | Proprietário, Gestor |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
chat:view | Visualizar conversas e mensagens | Proprietário, Gestor, Colaborador, Consulta |
chat:send | Enviar mensagens em conversas | Proprietário, Gestor, Colaborador |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
ai:view | Visualizar agentes e assistentes de IA | Proprietário, Gestor, Colaborador, Consulta |
ai:use | Interagir com agentes e assistentes de IA | Proprietário, Gestor, Colaborador |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
subscription:view | Visualizar assinatura e plano | Proprietário, Gestor, Colaborador, Consulta |
subscription:manage | Gerenciar assinatura e faturamento | Proprietário (exclusivo) |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
settings:view | Visualizar configurações da organização | Proprietário, Gestor, Colaborador, Consulta |
settings:edit | Editar configurações da organização | Proprietário (exclusivo) |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
integrations:view | Visualizar integrações | Proprietário, Gestor, Colaborador, Consulta |
integrations:configure | Configurar integrações | Proprietário (exclusivo) |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
calendar:view | Visualizar agenda | Proprietário, Gestor, Colaborador, Consulta |
calendar:manage | Gerenciar agenda | Proprietário, Gestor |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
reports:view | Visualizar relatórios | Proprietário, Gestor, Colaborador, Consulta |
reports:export | Exportar relatórios | Proprietário, Gestor |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
erp:view | Visualizar integração ERP | Proprietário, Gestor, Colaborador, Consulta |
erp:sync | Sincronizar dados com ERP | Proprietário (exclusivo) |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
whatsapp:view | Visualizar configurações do WhatsApp | Proprietário, Gestor, Colaborador, Consulta |
whatsapp:configure | Configurar WhatsApp | Proprietário (exclusivo) |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
reviews:create | Avaliar prestadores/fornecedores | Proprietário, Gestor, Colaborador |
| Chave | Descrição | Perfis que possuem |
|---|---|---|
condominiums:view | Visualizar condomínios gerenciados | Proprietário, Gestor, Colaborador, Consulta |
condominiums:manage | Gerenciar condomínios | Proprietário, Gestor |
Cada permissão do catálogo é segmentada por tipo de organização. Abaixo, os módulos disponíveis para cada Perfil da Organização:
┌──────────────────────────────────────────┐
│ ADMINISTRADORA │
│ │
│ Módulos disponíveis: │
│ ├── dashboard (view) │
│ ├── team (view, invite, │
│ │ manage, remove) │
│ ├── solicitations (view, create, │
│ │ edit, cancel) │
│ ├── quotations (view, create, │
│ │ edit, approve, │
│ │ cancel) │
│ ├── proposals (view, approve, │
│ │ reject) │
│ ├── contracts (view, sign) │
│ ├── documents (view, upload, │
│ │ delete) │
│ ├── finance (view, create, │
│ │ approve) │
│ ├── chat (view, send) │
│ ├── ai (view, use) │
│ ├── subscription (view, manage*) │
│ ├── settings (view, edit*) │
│ ├── integrations (view, configure*) │
│ ├── calendar (view, manage) │
│ ├── reports (view, export) │
│ ├── erp (view, sync*) │
│ ├── whatsapp (view, configure*) │
│ ├── reviews (create) │
│ └── condominiums (view, manage) │
│ │
│ * Apenas Proprietário │
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ CONDOMÍNIO │
│ │
│ Módulos disponíveis: │
│ ├── dashboard (view) │
│ ├── team (view, invite, │
│ │ manage, remove) │
│ ├── solicitations (view, create, │
│ │ edit, cancel) │
│ ├── quotations (view, create, │
│ │ edit, approve, │
│ │ cancel) │
│ ├── proposals (view, approve, │
│ │ reject) │
│ ├── contracts (view, sign) │
│ ├── documents (view, upload, │
│ │ delete) │
│ ├── finance (view, create, │
│ │ approve) │
│ ├── chat (view, send) │
│ ├── ai (view, use) │
│ ├── subscription (view, manage*) │
│ ├── settings (view, edit*) │
│ ├── integrations (view, configure*) │
│ ├── calendar (view, manage) │
│ ├── reports (view, export) │
│ ├── erp (view, sync*) │
│ ├── whatsapp (view, configure*) │
│ └── reviews (create) │
│ │
│ * Apenas Proprietário │
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ PRESTADOR │
│ │
│ Módulos disponíveis: │
│ ├── dashboard (view) │
│ ├── team (view, invite, │
│ │ manage, remove) │
│ ├── quotations (view) │
│ ├── proposals (view, create) │
│ ├── orders (view, create, │
│ │ edit, approve) │
│ ├── contracts (view, manage) │
│ ├── documents (view, upload, │
│ │ delete) │
│ ├── finance (view, create, │
│ │ approve) │
│ ├── chat (view, send) │
│ ├── ai (view, use) │
│ ├── subscription (view, manage*) │
│ ├── settings (view, edit*) │
│ ├── integrations (view, configure*) │
│ ├── calendar (view, manage) │
│ ├── reports (view, export) │
│ ├── erp (view, sync*) │
│ └── whatsapp (view, configure*) │
│ │
│ * Apenas Proprietário │
│ │
│ NOTA: Prestador NÃO tem acesso a: │
│ - solicitations ( módulo de quem cria) │
│ - quotations:create/edit/approve/cancel │
│ - proposals:approve/reject │
│ - reviews (módulo de quem avalia) │
│ - condominiums (módulo da administradora) │
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ FORNECEDOR │
│ │
│ Módulos disponíveis: │
│ ├── dashboard (view) │
│ ├── team (view, invite, │
│ │ manage, remove) │
│ ├── quotations (view) │
│ ├── proposals (view, create) │
│ ├── orders (view, create, │
│ │ edit, approve) │
│ ├── contracts (view, manage) │
│ ├── documents (view, upload, │
│ │ delete) │
│ ├── finance (view, create, │
│ │ approve) │
│ ├── chat (view, send) │
│ ├── ai (view, use) │
│ ├── subscription (view, manage*) │
│ ├── settings (view, edit*) │
│ ├── integrations (view, configure*) │
│ ├── calendar (view, manage) │
│ ├── reports (view, export) │
│ ├── erp (view, sync*) │
│ └── whatsapp (view, configure*) │
│ │
│ * Apenas Proprietário │
│ │
│ NOTA: Fornecedor NÃO tem acesso a: │
│ - solicitations │
│ - quotations:create/edit/approve/cancel │
│ - proposals:approve/reject │
│ - reviews │
│ - condominiums │
└──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ FLUXO DE RESOLUÇÃO │
│ │
│ 1. Requisição chega │
│ ├── Frontend: usePermissions(user, orgType, org) │
│ └── Backend: orgContextResolver.hasPermission( │
│ base44, userId, orgId, permissionKey) │
│ │
│ 2. Verificar Super Admin? │
│ ├── User.role === 'admin' → ✅ Todas as permissões │
│ │ │
│ 3. Verificar Proprietário? │
│ ├── OrganizationMember.role === 'Proprietário' │
│ │ → ✅ Todas as permissões implícitas │
│ │ │
│ 4. Verificar Owner Legado? (apenas frontend) │
│ ├── Sem OrganizationMember mas: │
│ │ org.manager_email === user.email │
│ │ org.created_by_id === user.id │
│ │ → ✅ Todas as permissões (compatibilidade) │
│ │ │
│ 5. RESOLVER PERMISSÕES VIA FÓRMULA HÍBRIDA │
│ │ │
│ │ 5a. Buscar Role (por role_id ou name+org_type) │
│ │ └── base_permissions = Role.base_permissions │
│ │ │
│ │ 5b. Buscar MemberPermission overrides │
│ │ └── WHERE member_id = X AND organization_id = Y │
│ │ │
│ │ 5c. APLICAR FÓRMULA: │
│ │ ┌──────────────────────────────┐ │
│ │ │ Permissões Finais = │ │
│ │ │ base_permissions │ │
│ │ │ + ADD(overrides) │ │
│ │ │ - REMOVE(overrides) │ │
│ │ └──────────────────────────────┘ │
│ │ │
│ │ 5d. Verificar se permissionKey ∈ Permissões Finais │
│ │ ├── Sim → ✅ Permitido │
│ │ └── Não → ❌ Negado │
│ │ │
│ 6. Retornar resultado │
│ ├── Frontend: can('quotations:create') → true/false │
│ └── Backend: hasPermission(...) → true/false │
└─────────────────────────────────────────────────────────────┘
src/hooks/usePermissions.js)// 1. Carrega o vínculo OrganizationMember do usuário na organização
const membership = await base44.entities.OrganizationMember.filter({
organization_id: orgId, user_id: user.id
});
// 2. Carrega a Role (por role_id primeiro, depois por name)
if (membership?.role_id) {
const byId = await base44.entities.Role.filter({ id: membership.role_id });
// ...
}
// 3. Carrega os overrides de MemberPermission
const overrides = await base44.entities.MemberPermission.filter({
member_id: membership.id, organization_id: orgId
});
// 4. Aplica a fórmula híbrida
const finalSet = new Set(role.base_permissions);
for (const override of overrides) {
if (override.operation === 'ADD') finalSet.add(override.permission_key);
if (override.operation === 'REMOVE') finalSet.delete(override.permission_key);
}
// 5. can() verifica hierarquia + permissão resolvida
const can = (key) => {
if (isSuperAdmin || isOwner || isLegacyOwner) return true;
return finalSet.has(key);
};
base44/shared/orgContextResolver.ts)// resolvePermissions() implementa a fórmula oficial:
export async function resolvePermissions(base44, memberId, organizationId) {
// 1. Buscar OrganizationMember
const member = await base44.asServiceRole.entities.OrganizationMember.filter({ id: memberId });
// 2. Buscar Role (por role_id ou name + organization_type)
let role = null;
if (member.role_id) {
role = await base44.asServiceRole.entities.Role.filter({ id: member.role_id });
}
if (!role && member.role) {
role = await base44.asServiceRole.entities.Role.filter({
name: member.role, organization_type: member.organization_type
});
}
// 3. Base permissions da Role
const basePermissions = new Set(role?.base_permissions || role?.permissions || []);
// 4. Buscar MemberPermission overrides
const overrides = await base44.asServiceRole.entities.MemberPermission.filter({
member_id: memberId, organization_id: organizationId
});
// 5. Aplicar fórmula: base + ADD - REMOVE
const finalPermissions = new Set(basePermissions);
for (const override of overrides) {
if (override.operation === 'ADD') finalPermissions.add(override.permission_key);
if (override.operation === 'REMOVE') finalPermissions.delete(override.permission_key);
}
return finalPermissions;
}
| Prioridade | Condição | Resultado |
|---|---|---|
| 1 | User.role === 'admin' | ✅ Todas as permissões (Super Admin bypass) |
| 2 | OrganizationMember.role === 'Proprietário' | ✅ Todas as permissões implícitas |
| 3 | Owner legado (sem membership, mas é dono por e-mail/criador) | ✅ Todas (apenas frontend, compatibilidade) |
| 4 | Demais Perfis | Permissões resolvidas via fórmula híbrida |
┌─────────────────────────────────────────────────────────────────┐
│ FLUXO DE CONVITES │
│ │
│ ┌──────────────┐ │
│ │ Página Equipe │ Usuário clica em "Convidar Colaborador" │
│ │ (Frontend) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ InviteMemberDialog│ Formulário: │
│ │ (Frontend) │ • Nome Completo │
│ │ │ • E-mail │
│ │ │ • Perfil de Acesso (Role) │
│ │ │ • Cargo/Função (informativo) │
│ │ │ • [Opcional] Personalizar Permissões │
│ └──────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ notifyOrganization│ Backend: │
│ │ Invitation │ 1. Valida permissão team:invite │
│ │ (Backend) │ 2. Valida que a Role existe │
│ │ │ 3. Verifica duplicidade (convite/membro)│
│ │ │ 4. Cria OrganizationInvitation │
│ └──────┬───────────┘ │
│ │ │
│ ├──────────────────────────────┐ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ EmailService │ │ manageMember │ │
│ │ (Facade) │ │ Permissions │ │
│ └──────┬───────┘ │ (Backend) │ │
│ │ │ │ │
│ ▼ │ Salva │ │
│ ┌──────────────────┐ │ custom_perms │ │
│ │ EmailProvider │ │ no convite │ │
│ │ Factory │ └──────────────┘ │
│ │ │ │
│ │ EMAIL_PROVIDER → │ │
│ └──────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ ResendProvider │ POST https://api.resend.com/emails │
│ │ (API Resend) │ Authorization: Bearer RESEND_API_KEY │
│ └──────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Resend (Cloud) │ E-mail transacional enviado │
│ └──────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Novo Colaborador│ Recebe e-mail com link do convite │
│ │ (caixa de entrada)│ Link: /register/invite?token=XXX │
│ └──────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ RegisterInvite │ Página de validação do convite │
│ │ (Frontend) │ (validate → Auth → complete) │
│ └──────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ completeInvitation│ Backend: │
│ │ Registration │ 1. Valida token + e-mail autenticado │
│ │ (Backend) │ 2. Cria OrganizationMember (active) │
│ │ │ 3. Sincroniza role_id + role │
│ │ │ 4. Aplica custom_permissions (se houver) │
│ │ │ 5. Marca convite como accepted │
│ │ │ 6. Sincroniza organization_ids (User) │
│ │ │ 7. Atualiza User.role (platform) │
│ │ │ 8. Registra AuditLog │
│ │ │ 9. Retorna redirect_path do dashboard │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
| Etapa | Componente | Responsabilidade |
|---|---|---|
| 1 | InviteMemberDialog (Frontend) | Coleta dados do convidado e Perfil de Acesso. Se o usuário logado for Proprietário, exibe a opção de personalizar permissões. |
| 2 | notifyOrganizationInvitation (Backend) | Valida permissão team:invite, verifica duplicidade, cria OrganizationInvitation com token, expiração e custom_permissions (se houver). |
| 3 | manageMemberPermissions (Backend) | Se o Proprietário personalizou permissões, salva os overrides no campo custom_permissions do convite. |
| 4 | EmailService (Facade) | Camada única de entrada para e-mails. Resolve o provider via EmailProviderFactory e delega o envio. Nunca lança exceções. |
| 5 | EmailProviderFactory | Lê a secret EMAIL_PROVIDER e instancia o provider correto. Atualmente: resend. Para trocar de provider, basta alterar a secret. |
| 6 | ResendProvider | Comunica-se com a API do Resend via fetch nativo. POST para https://api.resend.com/emails com Authorization: Bearer RESEND_API_KEY. |
| 7 | RegisterInvite (Frontend) | Página pública que valida o token do convite. Se válido, direciona o usuário para autenticação na Base44. |
| 8 | completeInvitationRegistration (Backend) | Após autenticação, vincula o usuário à organização: cria OrganizationMember com role_id, aplica custom_permissions como MemberPermission, sincroniza organization_ids e role no User, e registra auditoria. |
| Secret | Descrição |
|---|---|
RESEND_API_KEY | Chave de API do Resend para envio de e-mails transacionais |
EMAIL_FROM | Endereço de e-mail remetente (ex: noreply@connectcond.com.br) |
EMAIL_PROVIDER | Provider ativo (resend) |
A Landing Page da ConnectCond (rota /) continua utilizando exclusivamente o fluxo nativo da Base44.
Características confirmadas:
Diferença do Fluxo de Convites: O fluxo de convites de colaboradores (Seção 10) utiliza o Resend via EmailService. A Landing Page é completamente independente desse fluxo.
InviteMemberDialog.jsxA tela de convite de colaboradores é implementada como um diálogo modal (Dialog) acessível a partir da página Equipe de cada organização.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| Nome Completo | Input de texto | Não | Nome do convidado (para exibição e e-mail) |
| Input de e-mail | Sim | E-mail que receberá o convite. Deve corresponder ao e-mail de autenticação. | |
| Perfil de Acesso | Select dropdown | Sim | Um dos 4 Perfis oficiais (Proprietário, Gestor, Colaborador, Consulta). Cada opção exibe o nome e a descrição do Perfil. |
| Cargo/Função | Input de texto | Não | Função profissional (Síndico, Comprador, Financeiro...). Informativo — não concede permissões. Ajudante de texto abaixo do campo: "Campo informativo — não concede permissões." |
| Personalizar Permissões | Checkbox + Matriz | Condicional | Disponível apenas se o usuário logado for Proprietário ou Super Admin. Quando ativado, exibe a PermissionMatrix com todas as permissões do organization_type, agrupadas por módulo. |
┌─────────────────────────────────────────────────────┐
│ [👤] Convidar colaborador │
│ │
│ O colaborador receberá um convite por e-mail com │
│ um link para concluir o cadastro e acessar a │
│ plataforma. │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Nome completo │ │ E-mail * │ │
│ │ [João Silva ] │ │ [joao@... ] │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ Perfil de Acesso * │
│ ┌─────────────────────────────────────────┐ │
│ │ Gestor │ │
│ │ Administrador operacional. Gerencia... │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Cargo/Função │
│ [Síndico, Comprador, Financeiro...] │
│ Campo informativo — não concede permissões. │
│ │
│ ┌─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐ │
│ │ [☑] 🛡️ Personalizar permissões deste │ │
│ │ colaborador │ │
│ │ Ajuste permissões individuais além do │ │
│ │ Perfil de Acesso selecionado. │ │
│ │ │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ Permissões (scroll) │ │ │
│ │ │ │ │ │
│ │ │ 📋 Equipe │ │ │
│ │ │ ☑ Visualizar membros da equipe │ │ │
│ │ │ ☑ Convidar novos colaboradores [Extra]│ │ │
│ │ │ ☐ Gerenciar cargos [Removida] │ │ │
│ │ │ │ │ │
│ │ │ 📄 Cotações │ │ │
│ │ │ ☑ Visualizar cotações │ │ │
│ │ │ ☑ Criar cotações │ │ │
│ │ │ ... │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘ │
│ │
│ [Cancelar] [Enviar convite] │
└─────────────────────────────────────────────────────┘
Quando o Proprietário ativa "Personalizar permissões", a PermissionMatrix é exibida com as seguintes características:
| Estado Visual | Significado |
|---|---|
| ☑ Checkbox marcado, sem badge | Permissão vem da Role base (base) |
| ☑ Checkbox marcado, badge verde "Extra" | Permissão adicionada via override (ADD) |
| ☐ Checkbox desmarcado, badge vermelho "Removida" | Permissão removida via override (REMOVE) |
| ☐ Checkbox desmarcado, sem badge | Permissão não pertence à Role base e não tem override |
A matriz é segmentada por
organization_type— apenas as permissões aplicáveis ao Perfil da Organização são exibidas.
| Ação | Proprietário | Gestor | Colaborador | Consulta | Super Admin |
|---|---|---|---|---|---|
Convidar colaborador (team:invite) | ✅ | ✅ | ✅ | ❌ | ✅ |
Gerenciar cargos (team:manage) | ✅ | ✅ | ❌ | ❌ | ✅ |
Remover membro (team:remove) | ✅ | ✅ | ❌ | ❌ | ✅ |
| Personalizar permissões (MemberPermission) | ✅ | ❌ | ❌ | ❌ | ✅ |
Criar cotação (quotations:create) | ✅ | ✅ | ✅ | ❌ | ✅ |
Aprovar proposta (proposals:approve) | ✅ | ✅ | ❌ | ❌ | ✅ |
Assinar contrato (contracts:sign) | ✅ | ✅ | ❌ | ❌ | ✅ |
Gerenciar assinatura (subscription:manage) | ✅ | ❌ | ❌ | ❌ | ✅ |
Editar configurações (settings:edit) | ✅ | ❌ | ❌ | ❌ | ✅ |
Configurar integrações (integrations:configure) | ✅ | ❌ | ❌ | ❌ | ✅ |
Sincronizar ERP (erp:sync) | ✅ | ❌ | ❌ | ❌ | ✅ |
Configurar WhatsApp (whatsapp:configure) | ✅ | ❌ | ❌ | ❌ | ✅ |
Excluir documento (documents:delete) | ✅ | ✅ | ❌ | ❌ | ✅ |
Exportar relatório (reports:export) | ✅ | ✅ | ❌ | ❌ | ✅ |
Visualizar (todos os módulos :view) | ✅ | ✅ | ✅ | ✅ | ✅ |
Apenas o Proprietário pode personalizar permissões — Validado no backend manageMemberPermissions (função isProprietario). Retorna HTTP 403 se o solicitante não for Proprietário ou Super Admin.
O Gestor pode gerenciar a equipe mas NUNCA pode alterar permissões — O Gestor tem team:manage (pode alterar cargos), mas a função manageMemberPermissions verifica especificamente role === 'Proprietário', não team:manage.
Todas as alterações de permissão são auditadas — O manageMemberPermissions registra no AuditLog toda criação, atualização e remoção de MemberPermission, com descrição legível e metadados estruturados.
O Super Admin (plataforma) bypassa todas as verificações — User.role === 'admin' tem acesso irrestrito a todas as operações.
Toda implementação parte da premissa de que cada colaborador deve ter apenas as permissões mínimas necessárias para executar sua função:
:view (somente leitura).view, create, edit, send, use, upload), mas nunca manage, approve, delete, configure ou sync.subscription:manage, settings:edit, integrations:configure, erp:sync, whatsapp:configure) são exclusivas do Proprietário por padrão.A arquitetura garante que funções sensíveis sejam separadas:
| Função | Quem pode executar | Quem NÃO pode |
|---|---|---|
| Personalizar permissões | Proprietário | Gestor, Colaborador, Consulta |
| Convidar colaborador | Proprietário, Gestor, Colaborador | Consulta |
| Aprovar pagamentos | Proprietário, Gestor | Colaborador, Consulta |
| Gerenciar assinatura | Proprietário | Todos os demais |
Toda alteração de permissão gera um registro no AuditLog:
| Evento | Action no AuditLog | Entity |
|---|---|---|
| Permissão concedida (ADD) | create | MemberPermission |
| Permissão removida (REMOVE) | create | MemberPermission |
| Override excluído | delete | MemberPermission |
| Colaborador vinculado via convite | create | OrganizationMember |
| Permissões salvas em convite | update | OrganizationInvitation |
Cada registro contém: user_email, user_id, action, entity_name, entity_id, description (legível), metadata (estruturado).
A arquitetura RBAC Enterprise opera em conjunto com o RLS nativo da Base44:
Exemplo: O RLS garante que um colaborador do Condomínio A não veja as cotações do Condomínio B. O RBAC garante que, dentro do Condomínio A, um Colaborador possa criar cotações mas não possa aprová-las.
A autenticação é exclusivamente gerenciada pela Base44:
A governança é enforced no backend, não apenas no frontend:
| Camada | Mecanismo |
|---|---|
| Frontend | usePermissions.can() esconde/bloqueia ações não autorizadas |
| Backend | orgContextResolver.hasPermission() valida antes de executar |
| Backend (convites) | notifyOrganizationInvitation valida team:invite |
| Backend (permissões) | manageMemberPermissions valida role === 'Proprietário' |
| Backend (cargos) | inviteMember valida team:manage para update_role |
Princípio: O frontend é uma camada de UX; a segurança real é sempre validada no backend.
Esta arquitetura foi projetada para ser totalmente compatível com todos os módulos homologados da ConnectCond, sem quebrar funcionalidade existente:
| Módulo | Compatível | Observação |
|---|---|---|
| Landing Page | ✅ | Não utiliza RBAC; rota pública independente |
| Cadastro (Registration) | ✅ | Fluxo nativo de registro não foi alterado |
| Login (Auth) | ✅ | Base44 Auth gerencia autenticação; RBAC gerencia vínculos |
| Checkout | ✅ | Fluxo de pagamento não foi alterado |
| Assinaturas | ✅ | subscription:view disponível para todos; subscription:manage exclusivo do Proprietário |
| IA | ✅ | ai:view e ai:use disponíveis conforme Perfil |
| Chat | ✅ | chat:view e chat:send disponíveis conforme Perfil |
| ✅ | whatsapp:configure exclusivo do Proprietário; infraestrutura inalterada | |
| Stripe | ✅ | Integração de pagamento não foi alterada |
| ERP | ✅ | erp:sync exclusivo do Proprietário; conector inalterado |
| Fluxo de Convites | ✅ | Refatorado para usar role_id + custom_permissions; Resend inalterado |
| Organização | ✅ | Multi-tenant preservado; RLS não foi alterado |
| Todos os módulos homologados | ✅ | Nenhuma regra de negócio homologada foi quebrada |
Campos de compatibilidade ativos:
Role.permissions(sincronizado combase_permissions),OrganizationMember.role(sincronizado comrole_id),OrganizationMember.user_email(mantido para exibição). Todos são atualizados automaticamente — nunca editados diretamente.
┌─────────────────────────────────────────────────────────────────────┐
│ ARQUITETURA RBAC ENTERPRISE │
│ CONNECTCOND — VISÃO COMPLETA │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CAMADA DE IDENTIDADE │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Base44 Auth │───▶│ User Entity │───▶│ organization │ │ │
│ │ │ (Login/Senha)│ │ (id, email, │ │ _ids (cache) │ │ │
│ │ │ │ │ role, ...) │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ user_id │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CAMADA DE VÍNCULO │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ OrganizationMember │ │ │
│ │ │ • user_id ──────────────────────────────│───────────────│ │
│ │ │ • organization_id │ │ │
│ │ │ • organization_type (admin/cond/ │ │ │
│ │ │ provider/supplier) │ │ │
│ │ │ • role (string, compat RLS) ────────────│───────────────│ │
│ │ │ • role_id (UUID, fonte oficial) ────────│───────────────│ │
│ │ │ • professional_function (informativo) │ │ │
│ │ │ • status (pending/active/blocked) │ │ │
│ │ └──────────────┬───────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌───────┴────────┐ │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ Role │ │ MemberPermission │ │ │
│ │ │ │ │ (Overrides) │ │ │
│ │ │ base_ │ │ │ │ │
│ │ │ perms[] │ │ ADD / REMOVE │ │ │
│ │ └────┬─────┘ └────────┬─────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┘ │ │
│ │ │ │ (referência por key) │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ Permission │ Catálogo de 52 permissões atômicas │ │
│ │ │ (key, module, │ segmentadas por organization_types │ │
│ │ │ org_types) │ │ │
│ │ └──────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CAMADA DE RESOLUÇÃO │ │
│ │ │ │
│ │ orgContextResolver.ts (Backend) │ │
│ │ usePermissions.js (Frontend) │ │
│ │ │ │
│ │ FÓRMULA: │ │
│ │ Permissões Finais = base_permissions + ADD - REMOVE │ │
│ │ │ │
│ │ Hierarquia: │ │
│ │ 1. Super Admin → tudo │ │
│ │ 2. Proprietário → tudo │ │
│ │ 3. Owner legado → tudo (compat) │ │
│ │ 4. Demais → fórmula híbrida │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CAMADA DE APLICAÇÃO │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Frontend │ │ Backend │ │ RLS (Base44) │ │ │
│ │ │ can(key) │ │ hasPermission│ │ Isolamento │ │ │
│ │ │ │ │ (key) │ │ multi-tenant │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CAMADA DE AUDITORIA │ │
│ │ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ AuditLog │ Toda alteração registrada │ │
│ │ │ (action, user, │ com descrição e metadados │ │
│ │ │ entity, desc) │ │ │
│ │ └──────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ BANCO DE DADOS │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Role │ │ Permission │ │
│ │──────────────────────│ │──────────────────────│ │
│ │ id (UUID) │ │ id (UUID) │ │
│ │ name (string) │ │ key (string) │ │
│ │ organization_type │ │ module (enum) │ │
│ │ (enum) │ │ description (string)│ │
│ │ base_permissions │ │ organization_types │ │
│ │ (array<string>) │ │ (array<enum>) │ │
│ │ permissions* (compat) │ │ │ │
│ │ description (string) │ │ RLS: read=public │ │
│ │ is_system (boolean) │ │ CUD=admin │ │
│ │ is_default (boolean) │ └──────────────────────┘ │
│ │ │ ▲ │
│ │ RLS: read=public │ │ (key ref) │
│ │ CUD=admin │ │ │
│ └──────────┬───────────┘ │ │
│ │ role_id │ │
│ ▼ │ │
│ ┌──────────────────────┐ ┌───────────┴──────────┐ │
│ │ OrganizationMember │────────│ MemberPermission │ │
│ │──────────────────────│ 1:N │──────────────────────│ │
│ │ id (UUID) │ │ id (UUID) │ │
│ │ user_id (UUID) │ │ member_id (UUID) │ │
│ │ user_email* (compat) │ │ organization_id(UUID)│ │
│ │ full_name (string) │ │ permission_key(string)│ │
│ │ phone (string) │ │ operation (enum) │ │
│ │ position (string) │ │ ADD | REMOVE │ │
│ │ professional_function│ │ granted_by (UUID) │ │
│ │ (string, informat.)│ │ granted_at (datetime)│ │
│ │ department (enum) │ │ │ │
│ │ organization_id │ │ RLS: read=org member │ │
│ │ (UUID) │ │ CUD=admin │ │
│ │ organization_type │ └──────────────────────┘ │
│ │ (enum) │ │
│ │ role (string) │ ┌──────────────────────┐ │
│ │ (compat RLS) │ │ AuditLog │ │
│ │ role_id (UUID) │ │──────────────────────│ │
│ │ (oficial) │ │ id (UUID) │ │
│ │ status (enum) │ │ user_email (string) │ │
│ │ pending/active/ │ │ user_id (UUID) │ │
│ │ blocked/expired │ │ action (enum) │ │
│ │ invited_by (UUID) │ │ entity_name(string) │ │
│ │ invited_by_name │ │ entity_id (UUID) │ │
│ │ invited_at(datetime)│ │ description (string)│ │
│ │ accepted_at(datetime)│ │ ip_address (string) │ │
│ │ last_access(datetime)│ │ metadata (object)│ │
│ │ │ │ │ │
│ │ RLS: read=self/org │ │ RLS: read=admin │ │
│ │ CUD=self/admin │ │ CUD=admin │ │
│ └──────────────────────┘ │ create=open │ │
│ └──────────────────────┘ │
│ │
│ * Campo de compatibilidade (não é fonte da verdade) │
└─────────────────────────────────────────────────────────────────┘
| Campo | Tipo | Origem | Fonte da Verdade? |
|---|---|---|---|
name | string | Definido no catálogo | ✅ Sim |
organization_type | enum | Definido no catálogo | ✅ Sim |
base_permissions | array<string> | Definido no catálogo | ✅ Sim |
permissions | array<string> | Sincronizado com base_permissions | ❌ Compatibilidade |
description | string | Definido no catálogo | ✅ Sim |
is_system | boolean | Definido no catálogo | ✅ Sim |
is_default | boolean | Definido no catálogo | ✅ Sim |
| Campo | Tipo | Origem | Fonte da Verdade? |
|---|---|---|---|
key | string | Definido no catálogo | ✅ Sim |
module | enum | Definido no catálogo | ✅ Sim |
description | string | Definido no catálogo | ✅ Sim |
organization_types | array<enum> | Definido no catálogo | ✅ Sim |
| Campo | Tipo | Origem | Fonte da Verdade? |
|---|---|---|---|
member_id | string (UUID) | Referência a OrganizationMember | ✅ Sim |
organization_id | string (UUID) | Referência à organização | ✅ Sim |
permission_key | string | Referência a Permission.key | ✅ Sim |
operation | enum (ADD/REMOVE) | Definido pelo Proprietário | ✅ Sim |
granted_by | string (UUID) | User.id do Proprietário | ✅ Sim |
granted_at | date-time | Timestamp da alteração | ✅ Sim |
| Campo | Tipo | Origem | Fonte da Verdade? |
|---|---|---|---|
user_id | string (UUID) | User.id | ✅ Sim |
user_email | string | User.email | ❌ Compatibilidade |
full_name | string | Formulário de convite | ✅ Sim |
position | string | Formulário de convite | ✅ Sim |
professional_function | string | Formulário de convite | ✅ Informativo |
organization_id | string (UUID) | ID da organização | ✅ Sim |
organization_type | enum | Tipo da organização | ✅ Sim |
role | string | Sincronizado com role_id | ❌ Compatibilidade |
role_id | string (UUID) | Role.id | ✅ Sim |
status | enum | Ciclo de vida do vínculo | ✅ Sim |
| Campo | Tipo | Origem | Fonte da Verdade? |
|---|---|---|---|
user_email | string | User.email | ✅ Sim |
user_id | string (UUID) | User.id | ✅ Sim |
action | enum | Tipo da ação | ✅ Sim |
entity_name | string | Entidade afetada | ✅ Sim |
entity_id | string (UUID) | ID do registro afetado | ✅ Sim |
description | string | Texto legível | ✅ Sim |
metadata | object | Dados estruturados | ✅ Sim |
Cenário: Maria Souza registra a Administradora "CondAdmin Ltda" na plataforma.
Registros criados:
1. Administration
{ id: "admin-001", name: "CondAdmin Ltda", ... }
2. User (via Base44 Auth)
{ id: "usr-maria", email: "maria@condadmin.com.br", role: "administradora" }
3. OrganizationMember
{
id: "member-001",
user_id: "usr-maria",
organization_id: "admin-001",
organization_type: "administration",
role: "Proprietário",
role_id: "role-prop-admin", ← Role "Proprietário" + org_type "administration"
status: "active",
professional_function: "Diretor"
}
Permissões recebidas: Todas as permissões do organization_type: "administration" (implícitas — Proprietário tem tudo).
Resolução:
Hierarquia → Proprietário → ✅ Todas as permissões implícitas
(MembroPermission overrides não são consultados — Proprietário tem tudo)
Cenário: Maria (Proprietária) convida João Silva como Gestor da Administradora.
Registros criados:
1. OrganizationInvitation
{
id: "inv-001",
organization_id: "admin-001",
email: "joao@condadmin.com.br",
role: "Gestor",
token: "abc-token-123",
status: "pending",
custom_permissions: []
}
2. (E-mail enviado via Resend com link /register/invite?token=abc-token-123)
3. OrganizationMember (criado quando João aceita o convite)
{
id: "member-002",
user_id: "usr-joao",
organization_id: "admin-001",
organization_type: "administration",
role: "Gestor",
role_id: "role-gestor-admin", ← Role "Gestor" + org_type "administration"
status: "active",
professional_function: "Gerente"
}
Permissões recebidas: base_permissions da Role "Gestor" + organization_type: "administration", exceto as 5 permissões de segurança exclusivas do Proprietário.
Resolução:
base_permissions (Gestor, administration) = [
"dashboard:view", "team:view", "team:invite", "team:manage", "team:remove",
"solicitations:view", "solicitations:create", "solicitations:edit", "solicitations:cancel",
"quotations:view", "quotations:create", "quotations:edit", "quotations:approve", "quotations:cancel",
"proposals:view", "proposals:approve", "proposals:reject",
"contracts:view", "contracts:sign",
"documents:view", "documents:upload", "documents:delete",
"finance:view", "finance:create", "finance:approve",
"chat:view", "chat:send",
"ai:view", "ai:use",
"subscription:view",
"settings:view",
"integrations:view",
"calendar:view", "calendar:manage",
"reports:view", "reports:export",
"erp:view",
"whatsapp:view",
"reviews:create",
"condominiums:view", "condominiums:manage"
]
+ ADD: (nenhum override)
- REMOVE: (nenhum override)
= Permissões Finais (sem subscription:manage, settings:edit, integrations:configure, erp:sync, whatsapp:configure)
Cenário: Maria (Proprietária) convida Carlos como Colaborador, mas personaliza suas permissões para também poder aprovar propostas (proposals:approve), que normalmente o Colaborador não tem.
Registros criados:
1. OrganizationInvitation
{
id: "inv-002",
organization_id: "admin-001",
email: "carlos@condadmin.com.br",
role: "Colaborador",
token: "def-token-456",
status: "pending",
custom_permissions: [
{ permission_key: "proposals:approve", operation: "ADD" }
]
}
2. (E-mail enviado via Resend)
3. OrganizationMember (criado quando Carlos aceita)
{
id: "member-003",
user_id: "usr-carlos",
organization_id: "admin-001",
organization_type: "administration",
role: "Colaborador",
role_id: "role-colab-admin",
status: "active",
professional_function: "Assistente"
}
4. MemberPermission (criado a partir do custom_permissions do convite)
{
id: "mp-001",
member_id: "member-003",
organization_id: "admin-001",
permission_key: "proposals:approve",
operation: "ADD",
granted_by: "usr-maria",
granted_at: "2026-08-06T14:30:00Z"
}
Resolução:
base_permissions (Colaborador, administration) = [
"dashboard:view", "team:view", "team:invite",
"solicitations:view", "solicitations:create", "solicitations:edit",
"quotations:view", "quotations:create", "quotations:edit",
"proposals:view", "proposals:create",
"contracts:view", "contracts:manage",
"documents:view", "documents:upload",
"finance:view", "finance:create",
"chat:view", "chat:send",
"ai:view", "ai:use",
"subscription:view", "settings:view", "integrations:view",
"calendar:view",
"reports:view",
"erp:view", "whatsapp:view",
"reviews:create",
"condominiums:view"
]
+ ADD: "proposals:approve" ← do MemberPermission
- REMOVE: (nenhum)
= Permissões Finais:
(...todas as base...) + "proposals:approve"
Carlos agora PODE aprovar propostas, mesmo sendo Colaborador.
Cenário: Maria (Proprietária) convida Ana como Consulta (somente leitura), mas remove explicitamente a permissão finance:view (que o Consulta normalmente tem).
Registros criados:
1. OrganizationInvitation
{
custom_permissions: [
{ permission_key: "finance:view", operation: "REMOVE" }
]
}
2. OrganizationMember
{ role: "Consulta", role_id: "role-cons-admin", ... }
3. MemberPermission
{
permission_key: "finance:view",
operation: "REMOVE",
...
}
Resolução:
base_permissions (Consulta, administration) = [
"dashboard:view", "team:view",
"solicitations:view", "quotations:view", "proposals:view",
"contracts:view", "documents:view", "finance:view", ← está na base
"chat:view", "ai:view",
"subscription:view", "settings:view", "integrations:view",
"calendar:view", "reports:view",
"erp:view", "whatsapp:view",
"condominiums:view"
]
+ ADD: (nenhum)
- REMOVE: "finance:view" ← do MemberPermission
= Permissões Finais:
(...todas as base...) - "finance:view"
Ana NÃO pode visualizar o módulo Financeiro, mesmo sendo Consulta
(que normalmente teria finance:view).
| Versão | Data | Principais Decisões | Justificativa |
|---|---|---|---|
| 1.0.0 | 06/08/2026 | Arquitetura RBAC Enterprise homologada | Evolução do modelo RBAC simples para um modelo híbrido com Perfil de Acesso + Overrides personalizados |
| ID | Decisão | Justificativa |
|---|---|---|
| DA-001 | Desacoplar Role de Professional Function | A função profissional (Síndico, Comprador) é informativa; o Perfil de Acesso (Proprietário, Gestor) determina permissões. São conceitos independentes. |
| DA-002 | Usar MemberPermission como entidade dedicada | Overrides personalizados em tabela dedicada são mais auditáveis, idempotentes e consultáveis do que arrays embutidos em OrganizationMember. |
| DA-003 | Dual-Field Strategy (role + role_id) | role_id (UUID) é a fonte oficial; role (string) é mantido para compatibilidade com regras RLS existentes que comparam por nome. Sincronização automática. |
| DA-004 | Resolução centralizada em orgContextResolver.ts | Toda resolução de permissão no backend passa por um único módulo, garantindo consistência e facilitando manutenção. |
| DA-005 | Apenas Proprietário pode personalizar permissões | Princípio do Menor Privilégio aplicado à própria governança — nem o Gestor pode escalonar privilégios. |
| DA-006 | Base44 Auth é única responsável por identidade | A RBAC Enterprise não cria identidades; apenas gerencia vínculos e permissões para identidades já autenticadas pela Base44. |
| DA-007 | Fórmula aditiva/subtrativa | base_permissions + ADD - REMOVE é determinística, idempotente e fácil de auditar. |
| DA-008 | Segmentação por organization_types | Cada permissão declara quais Perfis de Organização a utilizam, evitando que um Prestador veja permissões de Condomínio. |
| DA-009 | 16 Roles (4 × 4) em vez de Roles genéricas | Roles específicas por organization_type permitem que as base_permissions sejam precisamente calibradas para cada contexto. |
| DA-010 | EmailProviderFactory para e-mails transacionais | O fluxo de convites usa Resend via factory pattern, permitindo troca de provider sem alterar código de negócio. |
Esta versão (1.0.0) foi homologada em 06 de Agosto de 2026 após:
seedPermissionsorgContextResolver.ts) implementado e testadousePermissions.js) evoluído com fórmula híbridarole_id + custom_permissionsEsta passa a ser a Arquitetura Oficial Homologada da ConnectCond para controle de acesso baseado em papéis (RBAC Enterprise).
Permissões Finais = base_permissions + ADD - REMOVE — sempre previsível e auditável.Este documento serve como referência oficial para:
Nota de Fidelidade: Este documento reflete a implementação real do ambiente de produção. Caso discrepâncias sejam identificadas entre este documento e o comportamento observado do sistema, o código-fonte em produção deve ser considerado a fonte definitiva, e este documento deve ser atualizado para refletir a realidade.
ConnectCond © 2026 — Arquitetura RBAC Enterprise v1.0.0 — Documento Oficial Homologado