Overview
O Invite é o mecanismo de token para três fluxos:
type=MEMBER -> adiciona um usuário ao BackOffice ou a uma organização via Membership.
type=DRIVER -> vincula um usuário como motorista de uma empresa via DriverCompany.
type=ACCOUNT_ACTIVATION -> ativa uma conta criada sem senha.
A entidade, o token, o lifecycle e as validações de estado são compartilhados. O que muda é o efeito do aceite e os dados exigidos por cada tipo.
Tipos e escopos
| Tipo | organizationId | roleId | Efeito do aceite |
|---|
MEMBER interno | null | Role INTERNAL | Cria Membership interna ACTIVE; Profile deriva access:bko |
MEMBER de organização | preenchido | Role ENVIRONMENT ou ORGANIZATION | Cria Membership ACTIVE na organização; Profile deriva access:ops |
DRIVER | preenchido | null | Ativa vínculo DriverCompany; Profile deriva access:driver |
ACCOUNT_ACTIVATION | null | null | Define senha e ativa a conta referenciada |
Origens do Invite
| Origem | Tipo | Quem dispara |
|---|
| BackOffice cria Organization | MEMBER (owner, organizationId = nova org) | Operador BackOffice |
| BackOffice convida membro interno | MEMBER | Operador BackOffice |
| OPS convida colega | MEMBER | Membro OPS com create:member |
| OPS convida motorista | DRIVER | Membro OPS com create:driver |
| Conta criada sem senha | ACCOUNT_ACTIVATION | Fluxo que originou a conta |
O Invite carrega dois vínculos com User:
invitedByUserId — identifica o emissor ou responsável pelo token.
invitedUserId — identifica o usuário convidado ou ativado. Em MEMBER e DRIVER, pode ser resolvido no aceite. Em ACCOUNT_ACTIVATION, já aponta para a conta pendente.
Lifecycle
O Invite não possui campo status. O estado é derivado: acceptedAt -> accepted, rejectedAt -> rejected, revokedAt -> revoked, expiresAt no passado -> expired, senão -> pending. A expiração padrão é de 7 dias.
Capacidades por superfície
As ações de preview, accept e reject são públicas e operam sobre o token. Resend e revoke exigem autenticação; em OPS, também exigem escopo de organização.
| Ação | OPS | BackOffice | Driver | Account Activation |
|---|
| Preview | Sim | Sim | Sim | Sim |
| Accept | Sim | Sim | Sim | Sim |
| Reject | Sim | Sim | Sim | Sim |
| Resend | Sim | Sim | — | Sim |
| Revoke | Sim | Sim | — | Sim |
Cada ação valida se o convite pertence ao tipo esperado e se o token ainda pode ser usado.
Dados do aceite
| Tipo | Dados |
|---|
MEMBER | { name, password } |
DRIVER | { name, password, licenseNumber, licenseCategory, licenseExpiresAt } |
ACCOUNT_ACTIVATION | { password } |
name e password são obrigatórios em MEMBER e DRIVER, mas name é ignorado quando o User já existe. Para DRIVER, os campos de habilitação são ignorados quando o User já possui um Driver.
Polymorphic Accept
O aceite tem fases compartilhadas seguidas de um branch por type:
Quando o Invite tem email, o aceite também marca o email do User como verificado.
Invite Preview
Antes de aceitar, o cliente exibe informações contextuais sobre o Invite. O preview é público e valida se o token ainda pode ser usado antes de retornar dados enriquecidos conforme o tipo.
MEMBER:
{
"email": "convidado@email.com",
"phoneIso": "BR",
"phoneDdi": "55",
"phoneNumber": "11999998888",
"organization": { "name": "Transportes XYZ" },
"role": { "name": "Operador", "description": "Acesso operacional" }
}
DRIVER:
{
"email": "motorista@email.com",
"phoneIso": "BR",
"phoneDdi": "55",
"phoneNumber": "11999998888",
"organization": { "name": "Transportes XYZ" },
"requiresLicense": true
}
ACCOUNT_ACTIVATION:
{
"email": "passageiro@email.com",
"phoneIso": "BR",
"phoneDdi": "55",
"phoneNumber": "11999998888",
"name": "Maria Silva"
}
requiresLicense indica se o User já possui um Driver. Quando false, o cliente não precisa solicitar dados de habilitação no aceite.
Flow — BackOffice creates Organization (MEMBER)
Flow — OPS invites colleague (MEMBER)
Flow — Driver invite (DRIVER)
Flow — Account activation (ACCOUNT_ACTIVATION)
Reject vs. Revoke
| Ação | Quem | Efeito | Pré-condição |
|---|
| Reject | Convidado | Preenche rejectedAt | Não aceito, não rejeitado, não revogado |
| Revoke | Emissor ou responsável autorizado | Preenche revokedAt | Não aceito, não revogado |
Rejeitar e revogar não emitem eventos — apenas marcam o timestamp correspondente.
Resend
Um Invite pendente pode ser reenviado. O reenvio regenera o token e recalcula expiresAt para mais 7 dias — o link anterior passa a ser inválido — e emite invite.resent. Invites já aceitos ou revogados não podem ser reenviados.
Events
| Evento | Quando |
|---|
invite.created | Na criação do Invite |
invite.resent | No reenvio do Invite |
invite.accepted | No aceite do Invite |
invite.created e invite.resent disparam notificações com o link por WhatsApp e também por email quando email está preenchido.
Validations
On Creation — comum
| Validação | Erro |
|---|
| Emissor ou responsável existe | user.not_found |
Organization existe (se organizationId informado) | organization.not_found |
| Invite pendente para mesmo telefone + org + type | invite.already_pending |
On Creation — type=MEMBER
| Validação | Erro |
|---|
roleId informado | invite.missing_role |
| Role atribuível ao escopo do convite | role.organization_mismatch |
| Usuário ainda não possui essa role no escopo do convite | invite.user_already_member |
On Creation — type=DRIVER
| Validação | Erro |
|---|
| Organization possui Company | organization.invalid_type |
| User existente sem vínculo DriverCompany ativo nesta company | driver.vinculum_already_exists |
On Creation — type=ACCOUNT_ACTIVATION
| Validação | Erro |
|---|
invitedUserId informado | user.not_found |
| User está pendente de ativação | user.invalid_status |
| User ainda não possui senha | user.password_already_defined |
On Acceptance
| Validação | Erro |
|---|
| Token existe | invite.not_found |
| Não expirado | invite.expired |
| Não revogado | invite.revoked |
| Não rejeitado | invite.rejected |
| Não aceito | invite.already_accepted |
type corresponde à ação | invite.invalid_type |
type=MEMBER: convite possui roleId | invite.missing_role |
type=DRIVER: licença/habilitação vencida | driver.expired_license |
Effects of Accept
type=MEMBER -> Membership
| Campo | Descrição |
|---|
userId | Usuário que aceitou |
organizationId | Organização do convite; null apenas em convite interno |
roleId | Role atribuída |
Membership interna ACTIVE faz o Profile derivar access:bko. Membership ACTIVE em organização faz o Profile derivar access:ops. Um usuário pode acumular múltiplas Memberships na mesma organização, mas vínculos REVOKED não concedem acesso.
type=DRIVER -> Driver (se novo) + DriverCompany
| Entidade | Quando | Campos |
|---|
Driver | Apenas se o User ainda não tinha Driver | userId, licenseNumber, licenseCategory, licenseExpiresAt |
DriverCompany | Sempre | driverId, companyId (derivado de Organization -> Company) |
Vínculo ativo de motorista faz o Profile derivar access:driver. Driver não cria Membership — motorista é pessoal operacional, não membro administrativo da organização.
type=ACCOUNT_ACTIVATION -> conta ativa
| Campo | Descrição |
|---|
password | Senha definida pelo usuário |
status | Conta passa a ACTIVE |
emailVerified | Marcado quando o Invite possui email |
Passageiros podem entrar por auto-cadastro ou por ativação de conta via Invite, quando a conta foi criada sem senha.