Manual Técnico — Módulo Workflow
Documento de referência técnica exclusivo do módulo Workflow (Kanban/Gestão de Tarefas).
Sumário
- Visão Geral
- Modelo de Dados
- Regras de Negócio
- Requisitos Funcionais
- Arquitetura e Estrutura de Código
- Mapa de APIs
- Schemas de Validação (Zod)
- Notificações
- Registro de Tempo (Timesheet)
- Alçadas de Aprovação
- Isolamento por Empresa
- Testes
1. Visão Geral
O módulo Workflow é um sistema de gestão de tarefas estilo Kanban integrado à plataforma NotaNacional. Permite criar quadros (workspaces) com etapas configuráveis, cards (tarefas) com campos financeiros, prioridades, anexos, comentários, registro de tempo e alçadas de aprovação.
Funcionalidades Principais
| Funcionalidade |
Descrição |
| Workspaces |
Quadros Kanban com nome, cor e descrição |
| Membros |
Gestão de membros por workspace (papéis ADMIN/MEMBRO) |
| Etapas |
Colunas configuráveis com posição, cor, flags inicial/final |
| Prioridades |
Níveis customizáveis com cor e ícone |
| Cards |
Tarefas com campos financeiros, responsável, prazo, prioridade |
| Drag-and-Drop |
Movimentação entre etapas via @dnd-kit |
| Anexos |
Upload de arquivos (PDF, imagens, DOC, XLS) com controle de volumetria |
| Comentários |
Comentários com notificação seletiva de membros |
| Encaminhamento |
Copiar cards entre workspaces mantendo rastreabilidade |
| Registro de Tempo |
Lançamento manual ou via cronômetro (1 por usuário) |
| Notificações |
In-app com preferências por tipo de evento e canal |
| Aprovação |
Alçadas por valor que bloqueiam movimentação de cards |
| Isolamento |
Filtro por empresa dentro do tenant |
| PDF |
Exportação de card individual para PDF |
| Histórico |
Rastreamento de mudanças de responsável |
2. Modelo de Dados
Diagrama ER (Mermaid)
erDiagram
Workspace ||--o{ WorkspaceMembro : "membros"
Workspace ||--o{ WorkspaceEtapa : "etapas"
Workspace ||--o{ WorkspacePrioridade : "prioridades"
Workspace ||--o{ WorkspaceCard : "cards"
WorkspaceEtapa ||--o{ WorkspaceCard : "cards na etapa"
WorkspacePrioridade ||--o{ WorkspaceCard : "cards com prioridade"
WorkspaceCard ||--o{ WorkspaceCardAnexo : "anexos"
WorkspaceCard ||--o{ WorkspaceCardComentario : "comentários"
WorkspaceCard ||--o{ WorkspaceCardHistorico : "histórico"
WorkspaceCard ||--o{ WorkspaceRegistroTempo : "registros tempo"
WorkspaceCard ||--o| WorkspaceCard : "encaminhamento (cardOrigemId)"
WorkspaceCard }o--o| Tomador : "clienteId"
WorkspaceCard }o--o| Tomador : "fornecedorId"
WorkspaceCard }o--o| CentroCusto : "centroCustoId"
WorkspaceCard }o--o| Empresa : "empresaId"
Workspace ||--o{ RegraAprovacao : "regra (tipo WORKSPACE)"
RegraAprovacao ||--o{ Aprovador : "aprovadores"
RegraAprovacao ||--o{ SolicitacaoAprovacao : "solicitações"
WorkspaceNotificacao }o--|| WorkspaceCard : "cardId"
WorkspaceNotificacaoPreferencia }o--|| UsuarioAcesso : "usuarioId"
Entidades
Workspace
| Campo |
Tipo |
Restrições |
Descrição |
| id |
CHAR(36) |
PK, UUID |
Identificador |
| prestadorId |
CHAR(36) |
NOT NULL, INDEX |
Multi-tenancy |
| nome |
VARCHAR(100) |
NOT NULL |
Nome do quadro |
| descricao |
VARCHAR(500) |
NULLABLE |
Descrição |
| cor |
VARCHAR(7) |
NOT NULL |
Cor hex (#RRGGBB) |
| ativo |
BOOLEAN |
DEFAULT true |
Soft delete |
| createdAt |
DATETIME |
DEFAULT now() |
Criação |
| updatedAt |
DATETIME |
@updatedAt |
Atualização |
Constraints: @@unique([prestadorId, nome])
WorkspaceMembro
| Campo |
Tipo |
Restrições |
Descrição |
| id |
CHAR(36) |
PK, UUID |
Identificador |
| workspaceId |
CHAR(36) |
FK Workspace |
Workspace |
| prestadorId |
CHAR(36) |
NOT NULL |
Multi-tenancy |
| usuarioAcessoId |
CHAR(36) |
NOT NULL |
Ref UsuarioAcesso |
| papel |
ENUM |
ADMIN, MEMBRO |
Papel no workspace |
| ativo |
BOOLEAN |
DEFAULT true |
Soft delete |
Constraints: @@unique([workspaceId, usuarioAcessoId])
WorkspaceEtapa
| Campo |
Tipo |
Restrições |
Descrição |
| id |
CHAR(36) |
PK, UUID |
Identificador |
| workspaceId |
CHAR(36) |
FK Workspace |
Workspace |
| prestadorId |
CHAR(36) |
NOT NULL |
Multi-tenancy |
| nome |
VARCHAR(50) |
NOT NULL |
Nome da coluna |
| cor |
VARCHAR(7) |
NOT NULL |
Cor hex |
| posicao |
INT |
NOT NULL |
Ordem de exibição |
| isInitial |
BOOLEAN |
DEFAULT false |
Etapa de entrada |
| isFinal |
BOOLEAN |
DEFAULT false |
Etapa de conclusão |
| ativo |
BOOLEAN |
DEFAULT true |
Soft delete |
Constraints: @@unique([workspaceId, nome])
WorkspacePrioridade
| Campo |
Tipo |
Restrições |
Descrição |
| id |
CHAR(36) |
PK, UUID |
Identificador |
| workspaceId |
CHAR(36) |
FK Workspace |
Workspace |
| prestadorId |
CHAR(36) |
NOT NULL |
Multi-tenancy |
| nome |
VARCHAR(50) |
NOT NULL |
Nome (ex: Alta, Média) |
| cor |
VARCHAR(7) |
NOT NULL |
Cor hex |
| icone |
VARCHAR(50) |
NOT NULL |
Ícone Lucide |
| posicao |
INT |
NOT NULL |
Ordem |
| ativo |
BOOLEAN |
DEFAULT true |
Soft delete |
Constraints: @@unique([workspaceId, nome])
WorkspaceCard
| Campo |
Tipo |
Restrições |
Descrição |
| id |
CHAR(36) |
PK, UUID |
Identificador |
| workspaceId |
CHAR(36) |
FK Workspace |
Workspace |
| prestadorId |
CHAR(36) |
NOT NULL |
Multi-tenancy |
| etapaId |
CHAR(36) |
FK WorkspaceEtapa |
Etapa atual |
| prioridadeId |
CHAR(36) |
FK Prioridade, NULLABLE |
Prioridade |
| responsavelId |
CHAR(36) |
NULLABLE |
Membro responsável |
| titulo |
VARCHAR(200) |
NOT NULL |
Título do card |
| descricao |
TEXT |
NULLABLE |
Descrição |
| dataEntrega |
DATE |
NULLABLE |
Prazo |
| posicao |
INT |
NOT NULL |
Ordem dentro da etapa |
| ativo |
BOOLEAN |
DEFAULT true |
Soft delete |
| status |
ENUM |
ABERTO, EM_ANDAMENTO, FINALIZADO, CANCELADO |
Estado do ciclo |
| Campos Financeiros |
|
|
|
| valor |
DECIMAL(12,2) |
NULLABLE |
Valor monetário |
| centroCusto |
VARCHAR(100) |
NULLABLE |
Centro de custo (texto) |
| centroCustoId |
CHAR(36) |
FK CentroCusto, NULLABLE |
Ref Centro Custo |
| entidadeCategoria |
VARCHAR(100) |
NULLABLE |
Categoria |
| vencimento |
DATE |
NULLABLE |
Vencimento financeiro |
| fornecedor |
VARCHAR(200) |
NULLABLE |
Fornecedor (texto) |
| fornecedorId |
CHAR(36) |
FK Tomador, NULLABLE |
Ref Fornecedor |
| numeroDocumento |
VARCHAR(50) |
NULLABLE |
Nº documento/NF |
| dataEmissaoNf |
DATE |
NULLABLE |
Data emissão NF |
| Referências |
|
|
|
| clienteId |
CHAR(36) |
FK Tomador, NULLABLE |
Ref Cliente |
| severidadeId |
VARCHAR(20) |
NULLABLE |
BAIXA/MEDIA/ALTA/CRITICA |
| Encaminhamento |
|
|
|
| cardOrigemId |
CHAR(36) |
FK self, NULLABLE |
Card que originou |
| workspaceOrigemId |
CHAR(36) |
NULLABLE |
Workspace de origem |
| Isolamento |
|
|
|
| empresaId |
CHAR(36) |
FK Empresa, NULLABLE |
Empresa vinculada |
WorkspaceCardAnexo
| Campo |
Tipo |
Restrições |
Descrição |
| id |
CHAR(36) |
PK, UUID |
Identificador |
| cardId |
CHAR(36) |
FK WorkspaceCard |
Card |
| prestadorId |
CHAR(36) |
NOT NULL |
Multi-tenancy |
| url |
VARCHAR(500) |
NOT NULL |
URL do arquivo |
| nomeArquivo |
VARCHAR(255) |
NOT NULL |
Nome do arquivo |
| mimeType |
VARCHAR(100) |
NOT NULL |
Tipo MIME |
| tamanhoBytes |
INT |
NOT NULL |
Tamanho em bytes |
| autorId |
CHAR(36) |
NULLABLE |
Quem fez upload |
| ativo |
BOOLEAN |
DEFAULT true |
Soft delete |
WorkspaceCardComentario
| Campo |
Tipo |
Restrições |
Descrição |
| id |
CHAR(36) |
PK, UUID |
Identificador |
| cardId |
CHAR(36) |
FK WorkspaceCard |
Card |
| prestadorId |
CHAR(36) |
NOT NULL |
Multi-tenancy |
| autorId |
CHAR(36) |
NOT NULL |
Autor |
| conteudo |
TEXT |
NOT NULL |
Texto do comentário |
| ativo |
BOOLEAN |
DEFAULT true |
Soft delete |
WorkspaceCardHistorico
| Campo |
Tipo |
Descrição |
| id |
CHAR(36) |
Identificador |
| cardId |
CHAR(36) |
Card |
| prestadorId |
CHAR(36) |
Multi-tenancy |
| responsavelAnteriorId |
CHAR(36) |
Responsável anterior |
| responsavelNovoId |
CHAR(36) |
Novo responsável |
| autorId |
CHAR(36) |
Quem mudou |
| createdAt |
DATETIME |
Quando ocorreu |
WorkspaceRegistroTempo
| Campo |
Tipo |
Descrição |
| id |
CHAR(36) |
Identificador |
| workspaceId |
CHAR(36) |
Workspace |
| cardId |
CHAR(36) |
Card vinculado |
| prestadorId |
CHAR(36) |
Multi-tenancy |
| usuarioId |
CHAR(36) |
Quem registrou |
| descricao |
VARCHAR(500) |
O que foi feito |
| dataRegistro |
DATE |
Dia do registro |
| horaInicio |
DATETIME |
Início |
| horaFim |
DATETIME |
Fim |
| duracaoMin |
INT |
Duração em minutos |
| origem |
ENUM |
MANUAL ou CRONOMETRO |
| ativo |
BOOLEAN |
Soft delete |
WorkspaceCronometro
| Campo |
Tipo |
Descrição |
| id |
CHAR(36) |
Identificador |
| workspaceId |
CHAR(36) |
Workspace |
| cardId |
CHAR(36) |
Card em foco |
| prestadorId |
CHAR(36) |
Multi-tenancy |
| usuarioId |
CHAR(36) |
Usuário (UNIQUE — 1 por user) |
| status |
ENUM |
EM_ANDAMENTO ou PAUSADO |
| iniciadoEm |
DATETIME |
Quando iniciou |
| pausadoEm |
DATETIME |
Quando pausou (nullable) |
| acumuladoMs |
BIGINT |
Tempo acumulado em ms |
| descricao |
VARCHAR(500) |
Descrição (opcional) |
WorkspaceNotificacao
| Campo |
Tipo |
Descrição |
| id |
CHAR(36) |
Identificador |
| prestadorId |
CHAR(36) |
Multi-tenancy |
| destinatarioId |
CHAR(36) |
Para quem |
| tipo |
VARCHAR(30) |
Tipo do evento |
| cardId |
CHAR(36) |
Card relacionado |
| workspaceId |
CHAR(36) |
Workspace |
| autorId |
CHAR(36) |
Quem disparou (nullable) |
| titulo |
VARCHAR(300) |
Texto da notificação |
| lida |
BOOLEAN |
Se foi lida |
| ativo |
BOOLEAN |
Soft delete |
WorkspaceNotificacaoPreferencia
| Campo |
Tipo |
Descrição |
| id |
CHAR(36) |
Identificador |
| prestadorId |
CHAR(36) |
Multi-tenancy |
| usuarioId |
CHAR(36) |
Usuário |
| tipoEvento |
VARCHAR(30) |
Tipo de evento |
| canalInApp |
BOOLEAN |
Receber in-app |
| canalEmail |
BOOLEAN |
Receber por email |
Constraints: @@unique([prestadorId, usuarioId, tipoEvento])
PrestadorVolumetria (Compartilhado)
| Campo |
Tipo |
Descrição |
| prestadorId |
CHAR(36) |
PK, UNIQUE |
| bytesUtilizados |
BIGINT |
DEFAULT 0 |
| bytesLimite |
BIGINT |
DEFAULT 1GB |
3. Regras de Negócio
3.1 Multi-tenancy
- Toda query filtra por
prestadorId.
- Toda mutação valida que o recurso pertence ao
prestadorId do usuário.
- Nome de workspace é único por prestador.
3.2 Ciclo de Vida do Card
stateDiagram-v2
[*] --> ABERTO : Criação
ABERTO --> EM_ANDAMENTO : Mover para etapa não-inicial
EM_ANDAMENTO --> FINALIZADO : Mover para etapa final ou ação explícita
ABERTO --> CANCELADO : Cancelar
EM_ANDAMENTO --> CANCELADO : Cancelar
FINALIZADO --> [*]
CANCELADO --> [*]
- Status FINALIZADO e CANCELADO são terminais (não podem ser revertidos).
- Status muda automaticamente ao mover card para etapa
isFinal = true.
3.3 Etapas
- Cada workspace deve ter pelo menos 1 etapa com
isInitial = true (obrigatório para encaminhamento).
- Podem existir etapas com
isFinal = true (marcam cards como FINALIZADO ao entrar).
- Posição determina a ordem visual das colunas (pode ser reordenada via drag-and-drop).
- Nome de etapa é único dentro do workspace.
- Inativação de etapa: somente se não houver cards ativos nela (a validar no backend).
3.4 Membros
- Papéis: ADMIN (gerencia workspace, membros, etapas, prioridades) e MEMBRO (opera cards).
- Membro é vinculado via
usuarioAcessoId (tabela UsuarioAcesso).
- Unique constraint: um usuário não pode ser adicionado duas vezes ao mesmo workspace.
3.5 Cards — Campos Financeiros
- Cards possuem campos financeiros opcionais: valor, centroCusto, fornecedor, vencimento, etc.
- Esses campos permitem usar o workflow como fluxo de contas a pagar/aprovação financeira.
- Valor usa
Decimal(12,2) — máscara BRL R$ X.XXX,XX.
- Se
valor > alçada, dispara solicitação de aprovação (ver seção 10).
3.6 Encaminhamento entre Workspaces
- Um card pode ser copiado para outro workspace via
/cards/:id/encaminhar.
- O novo card é criado na etapa inicial (
isInitial = true) do workspace destino.
- O card original permanece inalterado no workspace de origem.
- Campos copiados: titulo, descricao, valor, centroCusto, fornecedor, clienteId, etc.
- Anexos ativos são duplicados (copiados, não referenciados).
- Rastreabilidade:
cardOrigemId e workspaceOrigemId no card novo.
- Pré-requisito: o usuário deve ser membro ativo do workspace destino.
- Pré-requisito: o workspace destino deve ter etapa com
isInitial = true.
3.7 Posicionamento e Reordenação
- Cards dentro de uma etapa têm campo
posicao (1-based).
- Mover um card entre etapas: altera
etapaId + recalcula posição na etapa destino.
- Reordenar dentro da mesma etapa: PATCH em batch com novo array de posições.
- Etapas e prioridades também possuem
posicao reordenável.
3.8 Soft Delete
- Todas as entidades usam
ativo = true/false.
- DELETE na API marca
ativo = false (nunca remove fisicamente).
- Listagens padrão filtram
ativo = true.
3.9 Volumetria de Storage
- Uploads de anexos contam contra
PrestadorVolumetria.bytesUtilizados.
- Limite padrão: 1 GB (
bytesLimite = 1073741824).
- Se ultrapassar, uploads são recusados.
- Ao deletar anexo (soft delete), bytes são devolvidos ao saldo.
3.10 Comentários com Notificação Seletiva
- Ao criar comentário, pode-se enviar
notificarIds: string[] com IDs de membros.
- O sistema cria uma
WorkspaceNotificacao do tipo NOVO_COMENTARIO para cada membro listado.
- Respeita as preferências de canal do destinatário (in-app e/ou email).
3.11 Severidade
- Cards podem ter campo
severidadeId com valores: BAIXA, MEDIA, ALTA, CRITICA.
- É independente de prioridade (prioridade é customizável, severidade é fixa).
4. Requisitos Funcionais
4.1 Workspaces
| ID |
Requisito |
| WF-01 |
CRUD de workspaces (nome, cor, descrição) |
| WF-02 |
Listagem paginada com filtros (busca, ativo/inativo, ordenação) |
| WF-03 |
Nome de workspace único por prestador |
| WF-04 |
Inativação/reativação via soft delete |
| WF-05 |
Exibir contagem de membros e etapas na listagem |
4.2 Membros
| ID |
Requisito |
| WF-06 |
Adicionar membros ao workspace (selecionar de UsuarioAcesso) |
| WF-07 |
Definir papel: ADMIN ou MEMBRO |
| WF-08 |
Alterar papel de membro existente |
| WF-09 |
Remover membro (soft delete) |
| WF-10 |
Unique: um usuário não pode estar 2x no mesmo workspace |
4.3 Etapas (Colunas Kanban)
| ID |
Requisito |
| WF-11 |
CRUD de etapas (nome, cor) |
| WF-12 |
Flags: isInitial (entrada), isFinal (conclusão) |
| WF-13 |
Reordenação de etapas via drag-and-drop |
| WF-14 |
Inativação de etapa |
| WF-15 |
Nome de etapa único dentro do workspace |
4.4 Prioridades
| ID |
Requisito |
| WF-16 |
CRUD de prioridades (nome, cor, ícone) |
| WF-17 |
Reordenação via drag-and-drop |
| WF-18 |
Inativação de prioridade |
| WF-19 |
Nome de prioridade único dentro do workspace |
4.5 Cards (Tarefas)
| ID |
Requisito |
| WF-20 |
Criar card com: título (obrigatório), descrição, responsável, prazo, prioridade, etapa, empresa |
| WF-21 |
Editar card com campos básicos + campos financeiros estendidos |
| WF-22 |
Campos financeiros: valor, centroCusto, centroCustoId, fornecedor, fornecedorId, vencimento, numeroDocumento, dataEmissaoNf, clienteId |
| WF-23 |
Severidade: BAIXA, MEDIA, ALTA, CRITICA |
| WF-24 |
Inativar/reativar card (soft delete) |
| WF-25 |
Mover card entre etapas (altera etapaId + posicao) |
| WF-26 |
Reordenar cards dentro da mesma etapa (batch) |
| WF-27 |
Visualização modo Kanban (agrupado por etapa) |
| WF-28 |
Visualização modo Tabela (listagem paginada com filtros) |
| WF-29 |
Filtros: busca, responsável, prioridade, etapa, empresa, ativo |
| WF-30 |
Exportar card para PDF |
| WF-31 |
Alterar status manualmente: FINALIZADO ou CANCELADO |
| WF-32 |
Número sequencial do card por workspace (card_number) |
4.6 Encaminhamento
| ID |
Requisito |
| WF-33 |
Encaminhar card para outro workspace |
| WF-34 |
Card novo vai para etapa inicial do workspace destino |
| WF-35 |
Copiar anexos ativos do card original |
| WF-36 |
Registrar rastreabilidade (cardOrigemId, workspaceOrigemId) |
| WF-37 |
Validar que usuário é membro ativo do workspace destino |
4.7 Anexos
| ID |
Requisito |
| WF-38 |
Upload de arquivos (PDF, JPEG, PNG, DOC, DOCX, XLS, XLSX) |
| WF-39 |
Tamanho máximo por arquivo: 10 MB |
| WF-40 |
Controle de volumetria por prestador (limite 1 GB padrão) |
| WF-41 |
Listar anexos ativos de um card |
| WF-42 |
Deletar anexo (soft delete, devolve bytes à volumetria) |
4.8 Comentários
| ID |
Requisito |
| WF-43 |
Criar comentário (1 a 5000 caracteres) |
| WF-44 |
Notificação seletiva: enviar notificarIds para notificar membros específicos |
| WF-45 |
Listar comentários ativos de um card |
| WF-46 |
Deletar comentário (soft delete) |
| WF-47 |
Exibir autor (nome + avatar) no comentário |
4.9 Histórico
| ID |
Requisito |
| WF-48 |
Registrar mudança de responsável (anterior → novo, quem mudou, quando) |
| WF-49 |
Listar histórico de atribuições de um card |
5. Arquitetura e Estrutura de Código
Páginas (Frontend)
src/app/(app)/workflow/
├── page.tsx # Lista de workspaces
└── [workspaceId]/
├── page.tsx # Kanban/tabela do workspace
└── registro-tempo/
└── page.tsx # Timesheet do workspace
API Routes (Backend)
src/app/api/workflow/
├── workspaces/
│ ├── route.ts # GET (list) + POST (create)
│ └── [id]/
│ ├── route.ts # GET (detail) + PATCH + DELETE
│ ├── membros/
│ │ ├── route.ts # GET (list) + POST (add)
│ │ └── [membroId]/route.ts # PATCH + DELETE
│ ├── etapas/
│ │ ├── route.ts # GET + POST
│ │ ├── [etapaId]/route.ts # PATCH + DELETE
│ │ └── reorder/route.ts # PATCH (reordenar)
│ ├── prioridades/
│ │ ├── route.ts # GET + POST
│ │ ├── [prioridadeId]/route.ts # PATCH + DELETE
│ │ └── reorder/route.ts # PATCH (reordenar)
│ ├── cards/
│ │ ├── route.ts # GET (kanban/tabela) + POST
│ │ ├── reorder/route.ts # PATCH (reordenar)
│ │ └── [cardId]/
│ │ ├── route.ts # GET + PATCH + DELETE
│ │ ├── mover/route.ts # PATCH (mover entre etapas)
│ │ ├── status/route.ts # PATCH (alterar status)
│ │ ├── encaminhar/route.ts # POST (encaminhar)
│ │ ├── pdf/route.ts # GET (gerar PDF)
│ │ ├── anexos/route.ts # GET + POST + DELETE
│ │ ├── comentarios/route.ts # GET + POST + DELETE
│ │ └── historico/route.ts # GET
│ ├── registro-tempo/
│ │ ├── route.ts # GET + POST
│ │ ├── [registroId]/route.ts # PATCH + DELETE
│ │ ├── relatorio/route.ts # GET (relatório agrupado)
│ │ └── cronometro/
│ │ ├── iniciar/route.ts # POST
│ │ ├── pausar/route.ts # POST
│ │ ├── retomar/route.ts # POST
│ │ ├── parar/route.ts # POST
│ │ └── status/route.ts # GET
│ ├── aprovacao/route.ts # GET + POST (regra + decisão)
│ └── aprovadores/ # GET + POST + PATCH
└── notificacoes/
├── route.ts # GET (listar)
├── contagem/route.ts # GET (count não-lidas)
├── marcar-todas-lidas/route.ts # PATCH
├── preferencias/route.ts # GET + PUT
├── [id]/route.ts # PATCH (marcar lida)
└── cron/verificar-prazos/ # Cron: verificar prazos vencidos
Services (Client-side)
src/services/
├── workflow.ts # Workspaces, membros, etapas, prioridades
├── workflow-cards.ts # Cards, anexos, comentários, encaminhamento, histórico
├── workflow-registro-tempo.ts # Registros de tempo + cronômetro
├── workflow-notificacoes.ts # Notificações + preferências
└── alcadas-aprovacao.ts # Regras, aprovadores, solicitações
Validators (Zod)
src/lib/validators/
├── workflow.ts # Workspace, membros, etapas, prioridades
├── workflow-cards.ts # Cards, status, encaminhamento, anexos, comentários, reorder
├── workflow-registro-tempo.ts # Registros, cronômetro, relatório
├── workflow-notificacoes.ts # Preferências, listagem
└── alcadas-aprovacao.ts # Regras, aprovadores, decisão
6. Mapa de APIs
6.1 Workspaces
| Método |
Rota |
Descrição |
| GET |
/api/workflow/workspaces |
Listar workspaces (page, limit, search, ativo, orderBy, orderDir) |
| POST |
/api/workflow/workspaces |
Criar workspace (nome, cor, descricao) |
| GET |
/api/workflow/workspaces/:id |
Detalhe do workspace |
| PATCH |
/api/workflow/workspaces/:id |
Editar workspace |
| DELETE |
/api/workflow/workspaces/:id |
Inativar workspace |
6.2 Membros
| Método |
Rota |
Descrição |
| GET |
/api/workflow/workspaces/:id/membros |
Listar membros |
| POST |
/api/workflow/workspaces/:id/membros |
Adicionar membro (usuarioAcessoId, papel) |
| PATCH |
/api/workflow/workspaces/:id/membros/:membroId |
Editar papel |
| DELETE |
/api/workflow/workspaces/:id/membros/:membroId |
Remover membro |
6.3 Etapas
| Método |
Rota |
Descrição |
| GET |
/api/workflow/workspaces/:id/etapas |
Listar etapas ordenadas por posição |
| POST |
/api/workflow/workspaces/:id/etapas |
Criar etapa (nome, cor, isInitial, isFinal) |
| PATCH |
/api/workflow/workspaces/:id/etapas/:etapaId |
Editar etapa |
| DELETE |
/api/workflow/workspaces/:id/etapas/:etapaId |
Inativar etapa |
| PATCH |
/api/workflow/workspaces/:id/etapas/reorder |
Reordenar (array de {id, posicao}) |
6.4 Prioridades
| Método |
Rota |
Descrição |
| GET |
/api/workflow/workspaces/:id/prioridades |
Listar prioridades |
| POST |
/api/workflow/workspaces/:id/prioridades |
Criar prioridade (nome, cor, icone) |
| PATCH |
/api/workflow/workspaces/:id/prioridades/:prioridadeId |
Editar prioridade |
| DELETE |
/api/workflow/workspaces/:id/prioridades/:prioridadeId |
Inativar prioridade |
| PATCH |
/api/workflow/workspaces/:id/prioridades/reorder |
Reordenar |
6.5 Cards
| Método |
Rota |
Descrição |
| GET |
/api/workflow/workspaces/:id/cards |
Listar (modo=kanban/tabela, filtros diversos) |
| POST |
/api/workflow/workspaces/:id/cards |
Criar card |
| GET |
/api/workflow/workspaces/:id/cards/:cardId |
Detalhe do card (inclui campos financeiros) |
| PATCH |
/api/workflow/workspaces/:id/cards/:cardId |
Editar card (schema estendido) |
| DELETE |
/api/workflow/workspaces/:id/cards/:cardId |
Inativar card |
| PATCH |
/api/workflow/workspaces/:id/cards/:cardId/mover |
Mover entre etapas (etapaDestinoId, posicao) |
| PATCH |
/api/workflow/workspaces/:id/cards/:cardId/status |
Alterar status (FINALIZADO/CANCELADO) |
| POST |
/api/workflow/workspaces/:id/cards/:cardId/encaminhar |
Encaminhar para outro workspace |
| GET |
/api/workflow/workspaces/:id/cards/:cardId/pdf |
Gerar PDF do card |
| PATCH |
/api/workflow/workspaces/:id/cards/reorder |
Reordenar cards na etapa (array) |
6.6 Anexos
| Método |
Rota |
Descrição |
| GET |
/api/workflow/workspaces/:id/cards/:cardId/anexos |
Listar anexos ativos |
| POST |
/api/workflow/workspaces/:id/cards/:cardId/anexos |
Criar anexo (metadata) ou upload (FormData) |
| DELETE |
/api/workflow/workspaces/:id/cards/:cardId/anexos/:anexoId |
Deletar anexo |
6.7 Comentários
| Método |
Rota |
Descrição |
| GET |
/api/workflow/workspaces/:id/cards/:cardId/comentarios |
Listar comentários |
| POST |
/api/workflow/workspaces/:id/cards/:cardId/comentarios |
Criar comentário (conteudo, notificarIds) |
| DELETE |
/api/workflow/workspaces/:id/cards/:cardId/comentarios/:id |
Deletar comentário |
6.8 Histórico
| Método |
Rota |
Descrição |
| GET |
/api/workflow/workspaces/:id/cards/:cardId/historico |
Listar mudanças de responsável |
6.9 Registro de Tempo
| Método |
Rota |
Descrição |
| GET |
/api/workflow/workspaces/:id/registro-tempo |
Listar registros (filtros: card, usuario, data, paginação) |
| POST |
/api/workflow/workspaces/:id/registro-tempo |
Criar registro manual |
| PATCH |
/api/workflow/workspaces/:id/registro-tempo/:registroId |
Editar registro |
| DELETE |
/api/workflow/workspaces/:id/registro-tempo/:registroId |
Inativar registro |
| GET |
/api/workflow/workspaces/:id/registro-tempo/relatorio |
Relatório agrupado (diário/semanal/mensal) |
| POST |
.../registro-tempo/cronometro/iniciar |
Iniciar cronômetro (cardId, descricao?) |
| POST |
.../registro-tempo/cronometro/pausar |
Pausar cronômetro |
| POST |
.../registro-tempo/cronometro/retomar |
Retomar cronômetro |
| POST |
.../registro-tempo/cronometro/parar |
Parar cronômetro (gera registro) |
| GET |
.../registro-tempo/cronometro/status |
Status do cronômetro ativo |
6.10 Notificações
| Método |
Rota |
Descrição |
| GET |
/api/workflow/notificacoes |
Listar notificações (page, limit, tipo, lida) |
| GET |
/api/workflow/notificacoes/contagem |
Count de não-lidas |
| PATCH |
/api/workflow/notificacoes/marcar-todas-lidas |
Marcar todas como lidas |
| PATCH |
/api/workflow/notificacoes/:id |
Marcar individual como lida |
| GET |
/api/workflow/notificacoes/preferencias |
Buscar preferências |
| PUT |
/api/workflow/notificacoes/preferencias |
Atualizar preferências |
6.11 Aprovação
| Método |
Rota |
Descrição |
| GET |
/api/workflow/workspaces/:id/aprovacao |
Buscar regra e solicitações |
| POST |
/api/workflow/workspaces/:id/aprovacao |
Criar/atualizar regra |
| GET |
/api/workflow/workspaces/:id/aprovadores |
Listar aprovadores |
| POST |
/api/workflow/workspaces/:id/aprovadores |
Criar aprovador |
| PATCH |
/api/workflow/workspaces/:id/aprovadores/:id |
Editar aprovador |
7. Schemas de Validação (Zod)
7.1 Workspace
createWorkspaceSchema: { nome: string(2-100), descricao?: string(max 500), cor: #RRGGBB }
updateWorkspaceSchema: { nome?, descricao?, cor?, ativo? }
7.2 Membros
addMembroSchema: { usuarioAcessoId: UUID, papel: "ADMIN" | "MEMBRO" }
updateMembroSchema: { papel: "ADMIN" | "MEMBRO" }
7.3 Etapas
createEtapaSchema: { nome: string(2-50), cor: #RRGGBB, isInitial?: boolean, isFinal?: boolean }
updateEtapaSchema: { nome?, cor?, isInitial?, isFinal?, ativo? }
reorderEtapasSchema: { etapas: [{id: UUID, posicao: int ≥ 1}] }
7.4 Prioridades
createPrioridadeSchema: { nome: string(2-50), cor: #RRGGBB, icone: string(1-50) }
updatePrioridadeSchema: { nome?, cor?, icone?, ativo? }
reorderPrioridadesSchema: { prioridades: [{id: UUID, posicao: int ≥ 1}] }
7.5 Cards
createCardSchema: {
titulo: string(2-200) REQUIRED,
descricao?: string | null,
responsavelId?: UUID | null,
dataEntrega?: datetime (ISO offset) | null,
prioridadeId?: UUID | null,
etapaId?: UUID | null,
empresaId?: UUID | null
}
updateCardSchemaExtended: {
...updateCardSchema, // titulo?, descricao?, responsavelId?, dataEntrega?, prioridadeId?, etapaId?, ativo?
empresaId?: UUID | null,
valor?: number(0 - 999999999999.99) | null,
centroCusto?: string(max 100) | null,
centroCustoId?: UUID | null,
entidadeCategoria?: string(max 100) | null,
vencimento?: datetime | null,
fornecedor?: string(max 200) | null,
fornecedorId?: UUID | null,
numeroDocumento?: string(max 50) | null,
dataEmissaoNf?: datetime | null,
clienteId?: UUID | null,
severidadeId?: "BAIXA" | "MEDIA" | "ALTA" | "CRITICA" | null,
cardOrigemId?: UUID | null
}
updateStatusSchema: { status: "FINALIZADO" | "CANCELADO" }
moverCardSchema: { etapaDestinoId: UUID, posicao: int ≥ 1 }
reorderCardsSchema: { etapaId: UUID, cards: [{id: UUID, posicao: int ≥ 1}] }
encaminharCardSchema: { workspaceDestinoId: UUID }
7.6 Anexos
createAnexoSchema: { url: URL(max 500), nomeArquivo: string(1-255), mimeType: string(1-100), tamanhoBytes: int > 0 }
uploadArquivoSchema: { mimeType: PDF|JPEG|PNG|DOC|DOCX|XLS|XLSX, tamanhoBytes: int(max 10MB) }
// Constantes: MAX_FILE_SIZE_BYTES = 10485760 (10MB), MAX_LOGO_SIZE_BYTES = 2097152 (2MB)
7.7 Comentários
createComentarioSchema: { conteudo: string(1-5000) }
createComentarioComNotificacaoSchema: { conteudo: string(1-5000), notificarIds: UUID[] (default []) }
7.8 Registro de Tempo
createRegistroTempoSchema: {
cardId: UUID,
descricao: string(3-500),
dataRegistro: "YYYY-MM-DD",
horaInicio: "HH:MM",
horaFim: "HH:MM"
}
updateRegistroTempoSchema: { descricao?, dataRegistro?, horaInicio?, horaFim?, ativo? }
iniciarCronometroSchema: { cardId: UUID, descricao?: string(max 500) }
pararCronometroSchema: { descricao?: string(3-500) }
relatorioQuerySchema: { dataInicio: "YYYY-MM-DD", dataFim: "YYYY-MM-DD", usuarioId?, cardId?, agrupamento: "diario"|"semanal"|"mensal" }
7.9 Notificações
// Tipos de evento: ATRIBUICAO, PRAZO_VENCIMENTO, NOVO_COMENTARIO, MUDANCA_ETAPA, EDICAO_CARD, CRIACAO_CARD
updatePreferenciasSchema: { preferencias: [{ tipoEvento, canalInApp: bool, canalEmail: bool }] (1-6 items) }
listNotificacoesQuerySchema: { page: int ≥ 1, limit: int(1-50), tipo?: TipoEvento, lida: "true"|"false"|"all" }
7.10 Aprovação
createRegraAprovacaoSchema: { valorMinimo: number(0.01 - 9999999999.99) }
createAprovadorSchema: { membroId: UUID, valorAlcada: number(0.01 - 999999999.99) }
decisaoSchema: { decisao: "APROVADO" | "REJEITADO", observacao?: string(max 500) }
listPendentesSchema: { page, perPage, workspaceId?, status, de?, ate?, orderBy, orderDir }
8. Notificações
Tipos de Evento
| Tipo |
Quando dispara |
ATRIBUICAO |
Card é atribuído a um responsável |
PRAZO_VENCIMENTO |
Data de entrega do card está vencendo/venceu (via cron) |
NOVO_COMENTARIO |
Comentário criado com notificarIds preenchido |
MUDANCA_ETAPA |
Card movido entre etapas |
EDICAO_CARD |
Card editado |
CRIACAO_CARD |
Card criado no workspace |
Canais
| Canal |
Descrição |
| IN_APP |
Notificação visível no badge do sino (header) |
| EMAIL |
Envio de e-mail via nodemailer |
Preferências
- Cada usuário pode configurar por tipo de evento quais canais deseja receber.
- Padrão: ambos ativos (
canalInApp: true, canalEmail: true).
- Unique:
[prestadorId, usuarioId, tipoEvento].
Contagem
- Endpoint
/api/workflow/notificacoes/contagem retorna total de não-lidas.
- Usado para exibir badge numérico no header.
Cron — Verificar Prazos
- Rota
/api/workflow/notificacoes/cron/verificar-prazos verifica cards com dataEntrega próxima/vencida.
- Dispara notificação
PRAZO_VENCIMENTO para o responsável.
9. Registro de Tempo (Timesheet)
Lançamento Manual
- Campos: cardId, descrição, dataRegistro, horaInício (HH:MM), horaFim (HH:MM).
- Duração calculada automaticamente em minutos (
duracaoMin).
- Origem marcada como
MANUAL.
- Um registro por vez — sem overlap checking.
Cronômetro
- 1 cronômetro ativo por usuário (
usuarioId é UNIQUE em WorkspaceCronometro).
- Estados:
EM_ANDAMENTO → PAUSADO → EM_ANDAMENTO (loop) → PARADO (gera registro).
stateDiagram-v2
[*] --> EM_ANDAMENTO : Iniciar
EM_ANDAMENTO --> PAUSADO : Pausar
PAUSADO --> EM_ANDAMENTO : Retomar
EM_ANDAMENTO --> [*] : Parar (gera registro)
PAUSADO --> [*] : Parar (gera registro)
- Ao pausar: salva
pausadoEm e soma tempo ao acumuladoMs.
- Ao retomar: limpa
pausadoEm e recomeça contagem.
- Ao parar: calcula tempo total, cria
WorkspaceRegistroTempo com origem CRONOMETRO, deleta o cronômetro.
Relatório
- Endpoint: GET
.../registro-tempo/relatorio?dataInicio&dataFim&agrupamento
- Retorna:
totalMinutos e totalFormatado (ex: "12h 30min")
mediaDiariaMin e mediaDiariaFormatada
porPeriodo[] — agrupado por dia/semana/mês
porResponsavel[] — agrupado por usuário
porCard[] — agrupado por card
10. Alçadas de Aprovação
Conceito
Quando um card com campo valor excede o valor mínimo configurado na regra de aprovação do workspace, o sistema bloqueia a movimentação até que um aprovador com alçada suficiente aprove.
Entidades
| Entidade |
Descrição |
RegraAprovacao |
Regra por workspace com valorMinimo (única por tipo+prestador ativo) |
Aprovador |
Membro com valorAlcada — pode aprovar cards até esse valor |
SolicitacaoAprovacao |
Pedido de aprovação com status PENDENTE/APROVADO/REJEITADO |
Fluxo
sequenceDiagram
participant U as Usuário
participant S as Sistema
participant A as Aprovador
U->>S: Move card com valor > valorMinimo
S->>S: Cria SolicitacaoAprovacao (PENDENTE)
S->>A: Notifica aprovador(es) com alçada suficiente
A->>S: Decide (APROVADO + observação)
S->>S: Atualiza status solicitação
S->>U: Card liberado para movimentação
Regras
- Uma regra ativa por tipo (WORKSPACE/FINANCEIRO) por prestador.
- Aprovador deve ser membro do workspace.
valorAlcada define até quanto o aprovador pode aprovar.
- Se valor do card > alçada do aprovador, outro aprovador com alçada maior deve decidir.
- Decisão:
APROVADO ou REJEITADO com observação opcional (max 500 chars).
- Listagem de pendentes com filtros: status, workspace, período, ordenação.
11. Isolamento por Empresa
Conceito
Dentro de um mesmo tenant (prestadorId), cards podem ser isolados por empresa. Isso permite que prestadores com múltiplas empresas/CNPJs gerenciem workflows separados.
Implementação
WorkspaceCard.empresaId: FK para Empresa.
- Filtro no GET cards:
?empresaId=UUID retorna apenas cards daquela empresa.
- Modelo
Empresa: { id, prestadorId, nome, cnpj } com unique [prestadorId, cnpj].
- Modelo
EmpresaUsuario: vincula usuários a empresas (ou acessoGlobal: true).
- Cards sem
empresaId são visíveis a todos.
12. Testes
Cobertura Existente
src/app/api/workflow/workspaces/__tests__/
├── route.test.ts # CRUD workspaces
├── workspace-access-control.exploration.test.ts # Access control exploratório
└── workspace-access-control.preservation.test.ts # Access control preservação
src/app/api/workflow/workspaces/[id]/cards/__tests__/
└── route.test.ts # CRUD cards
src/app/api/workflow/workspaces/[id]/etapas/__tests__/
└── route.test.ts # CRUD etapas
src/app/api/workflow/workspaces/[id]/etapas/reorder/__tests__/
└── route.test.ts # Reordenação
src/app/api/workflow/workspaces/[id]/prioridades/__tests__/
└── route.test.ts # CRUD prioridades
src/app/api/workflow/workspaces/[id]/prioridades/reorder/__tests__/
└── route.test.ts # Reordenação
src/app/api/workflow/workspaces/[id]/registro-tempo/__tests__/
└── route.test.ts # Registro de tempo
src/app/api/workflow/workspaces/[id]/registro-tempo/cronometro/__tests__/
└── ... # Cronômetro
src/app/api/workflow/workspaces/[id]/aprovacao/__tests__/
└── route.test.ts # Alçadas
src/app/api/workflow/workspaces/[id]/membros/[membroId]/__tests__/
└── ... # Membros
src/app/api/workflow/workspaces/[id]/prioridades/[prioridadeId]/__tests__/
└── ... # Prioridade individual
src/app/api/workflow/workspaces/[id]/etapas/[etapaId]/__tests__/
└── ... # Etapa individual
src/app/api/workflow/workspaces/[id]/__tests__/
└── ... # Workspace individual
Executar Testes do Módulo
# Todos os testes do módulo workflow
npx vitest run --testPathPattern=workflow
# Testes específicos de cards
npx vitest run --testPathPattern=workflow.*cards
# Testes de aprovação
npx vitest run --testPathPattern=workflow.*aprovacao
Documento atualizado em Junho/2026. Manter sincronizado com evolução do módulo.