> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devmob.app.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Invitation & Activation Flow

> Ciclo de vida do Invite: membro, motorista ou ativação de conta.

## 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](/data-modelling/tenant/membership).
* `type=DRIVER` -> vincula um usuário como motorista de uma empresa via [DriverCompany](/data-modelling/fleet/driver-company).
* `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     |

<Info>
  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.
</Info>

## Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> Pending: Invite criado
    Pending --> Accepted: Token aceito (acceptedAt)
    Pending --> Rejected: Convidado rejeita (rejectedAt)
    Pending --> Revoked: Emissor revoga (revokedAt)
    Pending --> Expired: expiresAt no passado
    Accepted --> [*]
    Rejected --> [*]
    Revoked --> [*]
    Expired --> [*]
```

<Info>
  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**.
</Info>

## 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`:

```mermaid theme={null}
flowchart TD
    START[accept token] --> VALIDATE[Token aceitável<br/>existe, não expirado,<br/>não revogado, não rejeitado,<br/>não aceito]
    VALIDATE --> RESOLVE[User resolvido pelo token<br/>ou por telefone/email]
    RESOLVE --> BRANCH{invite.type}

    BRANCH -->|MEMBER| MEMBER[Membership ACTIVE criada<br/>userId, organizationId, roleId]
    MEMBER --> MEMBER_PROFILE[Profile passa a derivar<br/>access:bko ou access:ops]

    BRANCH -->|DRIVER| DRIVER_CHECK{User já tem Driver?}
    DRIVER_CHECK -->|Não| CREATE_DRIVER[Driver recebe habilitação do aceite]
    DRIVER_CHECK -->|Sim| REUSE_DRIVER[Driver existente reutilizado]
    CREATE_DRIVER --> RESOLVE_COMPANY[Company derivada da Organization]
    REUSE_DRIVER --> RESOLVE_COMPANY
    RESOLVE_COMPANY --> DRIVER_COMPANY[Vínculo DriverCompany ativo]
    DRIVER_COMPANY --> DRIVER_PROFILE[Profile passa a derivar<br/>access:driver]

    BRANCH -->|ACCOUNT_ACTIVATION| ACTIVATE[Senha definida<br/>conta ativa]

    MEMBER_PROFILE --> ACCEPTED[Invite aceito]
    DRIVER_PROFILE --> ACCEPTED
    ACTIVATE --> ACCEPTED
    ACCEPTED --> END[Fim]
```

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:**

```json theme={null}
{
  "email": "convidado@email.com",
  "phoneIso": "BR",
  "phoneDdi": "55",
  "phoneNumber": "11999998888",
  "organization": { "name": "Transportes XYZ" },
  "role": { "name": "Operador", "description": "Acesso operacional" }
}
```

**DRIVER:**

```json theme={null}
{
  "email": "motorista@email.com",
  "phoneIso": "BR",
  "phoneDdi": "55",
  "phoneNumber": "11999998888",
  "organization": { "name": "Transportes XYZ" },
  "requiresLicense": true
}
```

**ACCOUNT\_ACTIVATION:**

```json theme={null}
{
  "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)

```mermaid theme={null}
sequenceDiagram
    participant BackOffice as BackOffice Operator
    participant API as DEVMOB API
    participant N as Notifications
    participant U as Invited Owner

    BackOffice->>API: Cria Organization com owner
    Note over API: Organization fica pronta com habilitações,<br/>address e Role Admin ENVIRONMENT
    Note over API: Invite MEMBER do owner fica disponível
    API-->>N: invite.created -> notificação com link
    API-->>BackOffice: Organization criada

    Note over U: Owner recebe a notificação
    U->>API: Aceita Invite com token, name e password
    Note over API: Membership ACTIVE criada na organização
    Note over API: Profile passa a derivar access:ops
    Note over API: Invite fica aceito e vinculado ao owner
    API-->>U: Conta pronta
```

## Flow — OPS invites colleague (MEMBER)

```mermaid theme={null}
sequenceDiagram
    participant OPS as OPS Member
    participant API as DEVMOB API
    participant N as Notifications
    participant U as Invited User

    OPS->>API: Convida membro com contato e role
    Note over API: type=MEMBER<br/>organizationId = org do operador<br/>invitedByUserId = OPS autenticado
    Note over API: Role atribuível à organização,<br/>sem convite pendente e sem Membership ACTIVE equivalente
    Note over API: Invite MEMBER fica disponível
    API-->>N: invite.created -> notificação com link
    API-->>OPS: Invite criado

    Note over U: Recebe a notificação
    U->>API: Aceita Invite com token, name e password
    Note over API: User identificado por telefone/email<br/>ou cadastrado no aceite
    Note over API: Membership ACTIVE criada na organização
    Note over API: Profile passa a derivar access:ops
    Note over API: Invite fica aceito
    API-->>U: Conta pronta
```

## Flow — Driver invite (DRIVER)

```mermaid theme={null}
sequenceDiagram
    participant OPS as OPS Operator
    participant API as DEVMOB API
    participant N as Notifications
    participant U as Invited Driver

    OPS->>API: Convida motorista com contato
    Note over API: type=DRIVER<br/>organizationId = org do operador<br/>roleId = null
    Note over API: Organização possui Company,<br/>sem convite pendente nem vínculo ativo
    Note over API: Invite DRIVER fica disponível
    API-->>N: invite.created -> notificação com link
    API-->>OPS: Invite criado

    Note over U: Recebe a notificação
    U->>API: Aceita Invite com token, dados de conta e habilitação
    Note over API: User identificado por telefone/email<br/>ou cadastrado no aceite
    alt User não tem Driver
        Note over API: Driver recebe habilitação do aceite
    else User já tem Driver
        Note over API: Driver existente é reutilizado
    end
    Note over API: Vínculo DriverCompany fica ativo
    Note over API: Profile passa a derivar access:driver
    Note over API: Invite fica aceito
    API-->>U: Motorista vinculado
```

## Flow — Account activation (ACCOUNT\_ACTIVATION)

```mermaid theme={null}
sequenceDiagram
    participant U as Pending User
    participant API as DEVMOB API
    participant N as Notifications

    Note over API: User criado sem senha<br/>status PENDING
    Note over API: Invite ACCOUNT_ACTIVATION fica disponível
    API-->>N: invite.created -> notificação com link de ativação

    Note over U: Usuário recebe a notificação
    U->>API: Aceita Invite com token e password
    Note over API: Senha definida
    Note over API: Conta fica ACTIVE
    Note over API: Invite fica aceito
    API-->>U: Conta pronta
```

## 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`](/events/tenant/invite-resent). Invites já aceitos ou revogados não podem ser reenviados.

## Events

| Evento                                              | Quando               |
| --------------------------------------------------- | -------------------- |
| [`invite.created`](/events/tenant/invite-created)   | Na criação do Invite |
| [`invite.resent`](/events/tenant/invite-resent)     | No reenvio do Invite |
| [`invite.accepted`](/events/tenant/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 |

<Tip>
  Passageiros podem entrar por auto-cadastro ou por ativação de conta via Invite, quando a conta foi criada sem senha.
</Tip>
