Files
hefesto/docs/ARQUITETURA-TECNICA.md
bigtux d8ca580acb HEFESTO v1.0 - Sistema de Controle Orçamentário para Facilities
- Backend NestJS com 12 módulos
- Frontend React com dashboard e gestão
- Manuais técnico e de negócios (MD + PDF)
- Workflow de aprovação com alçadas
- RBAC com 6 perfis de acesso
2026-02-09 14:53:01 -03:00

1105 lines
47 KiB
Markdown

# HEFESTO — Arquitetura Técnica
## Sistema de Controle Orçamentário para Facilities
**Versão:** 1.0
**Data:** 2026-02-09
**Stack:** NestJS · React · PostgreSQL
---
## 1. Visão Geral da Arquitetura
```
┌─────────────────────────────────────────────────────────────────────┐
│ CLIENTE (Browser) │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ React + Vite (SPA) │ │
│ │ ┌──────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌────────┐ │ │
│ │ │ Auth │ │Dashboard │ │Demandas │ │Propostas│ │Relat. │ │ │
│ │ └──────┘ └──────────┘ └──────────┘ └─────────┘ └────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐│ │
│ │ │Orçamento │ │Fornec. │ │Workflow │ │Ordens de Serviço ││ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────────────┘│ │
│ └───────────────────────────────────────────────────────────────┘ │
└────────────────────────────┬────────────────────────────────────────┘
│ HTTPS / REST + JWT
┌────────────────────────────▼────────────────────────────────────────┐
│ API GATEWAY (NestJS) │
│ ┌────────┐ ┌──────┐ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Auth │ │Users │ │ Locais │ │Categorias│ │ Fornecedores │ │
│ └────────┘ └──────┘ └──────────┘ └──────────┘ └───────────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │Demandas │ │Propostas │ │Orçamento │ │Workflow │ │ OCR │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
│ ┌───────────────┐ ┌──────────┐ ┌──────────────────────────────┐ │
│ │Ordens Serviço │ │Dashboard │ │ Relatórios │ │
│ └───────────────┘ └──────────┘ └──────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Guards (JWT + RBAC) │ Interceptors │ Pipes │ Audit Logger │ │
│ └─────────────────────────────────────────────────────────────┘ │
└────────────────────────────┬────────────────────────────────────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ PostgreSQL │ │ Redis │ │ Storage │
│ (dados) │ │ (cache/ │ │ (S3/MinIO │
│ │ │ filas) │ │ PDFs) │
└────────────┘ └────────────┘ └────────────┘
┌────▼─────┐
│ OpenAI │
│GPT-4o- │
│ mini │
└──────────┘
```
---
## 2. Modelo de Dados Completo
### 2.1 Diagrama Entidade-Relacionamento (textual)
```
perfis ──1:N──> usuarios
locais ──1:N──> demandas
centros_custo ──1:N──> demandas
centros_custo ──1:N──> orcamento_planejado
categorias ──1:N──> demandas
categorias ──1:N──> orcamento_planejado
fornecedores ──1:N──> certidoes
fornecedores ──1:N──> propostas
demandas ──1:N──> itens_linha
demandas ──1:N──> propostas
demandas ──1:1──> workflow_aprovacao
demandas ──1:1──> ordens_servico
propostas ──1:N──> versoes_proposta
demandas ──1:1──> comparativo_propostas
ordens_servico ──1:1──> avaliacoes
matriz_alcada ──> centros_custo, perfis
```
### 2.2 Tabelas
#### `perfis`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK, DEFAULT gen_random_uuid() |
| nome | VARCHAR(50) | NOT NULL, UNIQUE |
| descricao | TEXT | |
| permissoes | JSONB | NOT NULL, DEFAULT '{}' |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
> Valores seed: `solicitante`, `gestor_facilities`, `aprovador_financeiro`, `diretoria`, `fornecedor`, `administrador`
#### `usuarios`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| nome | VARCHAR(200) | NOT NULL |
| email | VARCHAR(255) | NOT NULL, UNIQUE |
| senha_hash | VARCHAR(255) | NOT NULL |
| perfil_id | UUID | FK → perfis.id, NOT NULL |
| ativo | BOOLEAN | DEFAULT TRUE |
| ultimo_acesso | TIMESTAMPTZ | |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `locais`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| nome | VARCHAR(200) | NOT NULL |
| endereco | TEXT | |
| centro_custo_id | UUID | FK → centros_custo.id, NOT NULL |
| responsavel_id | UUID | FK → usuarios.id |
| ativo | BOOLEAN | DEFAULT TRUE |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `centros_custo`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| codigo | VARCHAR(20) | NOT NULL, UNIQUE |
| nome | VARCHAR(200) | NOT NULL |
| responsavel_id | UUID | FK → usuarios.id |
| ativo | BOOLEAN | DEFAULT TRUE |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `categorias`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| nome | VARCHAR(200) | NOT NULL |
| subcategoria | VARCHAR(200) | |
| criticidade_padrao | VARCHAR(20) | CHECK (criticidade_padrao IN ('baixa','media','alta','critica')) |
| sla_dias | INTEGER | DEFAULT 30 |
| categoria_pai_id | UUID | FK → categorias.id (auto-ref) |
| ativo | BOOLEAN | DEFAULT TRUE |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `fornecedores`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| tipo_pessoa | VARCHAR(10) | NOT NULL, CHECK (tipo_pessoa IN ('PF','PJ')) |
| cpf_cnpj | VARCHAR(18) | NOT NULL, UNIQUE |
| razao_social | VARCHAR(300) | NOT NULL |
| nome_fantasia | VARCHAR(300) | |
| email | VARCHAR(255) | |
| telefone | VARCHAR(20) | |
| endereco | TEXT | |
| categorias_atendidas | UUID[] | (array de FK → categorias.id) |
| rating | NUMERIC(2,1) | DEFAULT 3.0, CHECK (rating >= 1 AND rating <= 5) |
| usuario_id | UUID | FK → usuarios.id (login do portal) |
| ativo | BOOLEAN | DEFAULT TRUE |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `certidoes`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| fornecedor_id | UUID | FK → fornecedores.id, NOT NULL |
| tipo | VARCHAR(100) | NOT NULL |
| numero | VARCHAR(100) | |
| data_emissao | DATE | NOT NULL |
| data_validade | DATE | NOT NULL |
| arquivo_url | TEXT | |
| status | VARCHAR(20) | CHECK (status IN ('vigente','vencida','pendente')) |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `orcamento_planejado`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| ano | INTEGER | NOT NULL |
| mes | INTEGER | NOT NULL, CHECK (mes BETWEEN 1 AND 12) |
| centro_custo_id | UUID | FK → centros_custo.id, NOT NULL |
| categoria_id | UUID | FK → categorias.id, NOT NULL |
| valor_planejado | NUMERIC(15,2) | NOT NULL |
| valor_comprometido | NUMERIC(15,2) | DEFAULT 0 |
| valor_realizado | NUMERIC(15,2) | DEFAULT 0 |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
| UNIQUE(ano, mes, centro_custo_id, categoria_id) | | |
#### `demandas`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| numero | SERIAL | UNIQUE (código sequencial) |
| titulo | VARCHAR(300) | NOT NULL |
| descricao | TEXT | |
| local_id | UUID | FK → locais.id, NOT NULL |
| centro_custo_id | UUID | FK → centros_custo.id, NOT NULL |
| categoria_id | UUID | FK → categorias.id, NOT NULL |
| criticidade | VARCHAR(20) | NOT NULL, CHECK (... IN ('baixa','media','alta','critica')) |
| data_desejada | DATE | |
| status | VARCHAR(30) | NOT NULL, DEFAULT 'rascunho' |
| solicitante_id | UUID | FK → usuarios.id, NOT NULL |
| gestor_id | UUID | FK → usuarios.id |
| documentos | JSONB | DEFAULT '[]' |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
> **Status possíveis:** `rascunho`, `aberta`, `em_escopo`, `em_cotacao`, `propostas_recebidas`, `em_comparacao`, `em_aprovacao`, `aprovada`, `os_emitida`, `em_execucao`, `concluida`, `cancelada`
#### `itens_linha`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| demanda_id | UUID | FK → demandas.id, NOT NULL |
| descricao | VARCHAR(300) | NOT NULL |
| tipo | VARCHAR(50) | CHECK (tipo IN ('mao_de_obra','material','equipamento','outros')) |
| quantidade | NUMERIC(10,2) | |
| unidade | VARCHAR(30) | |
| observacoes | TEXT | |
| ordem | INTEGER | DEFAULT 0 |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `propostas`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| demanda_id | UUID | FK → demandas.id, NOT NULL |
| fornecedor_id | UUID | FK → fornecedores.id, NOT NULL |
| versao_atual | INTEGER | DEFAULT 1 |
| valor_bruto | NUMERIC(15,2) | |
| valor_liquido | NUMERIC(15,2) | |
| impostos | JSONB | DEFAULT '{}' (iss, inss, pcc) |
| condicao_pagamento | TEXT | |
| prazo_execucao_dias | INTEGER | |
| data_entrega_estimada | DATE | |
| match_escopo_pct | NUMERIC(5,2) | |
| confiabilidade_ocr | NUMERIC(5,2) | |
| selecionada | BOOLEAN | DEFAULT FALSE |
| status | VARCHAR(20) | DEFAULT 'recebida' |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `versoes_proposta`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| proposta_id | UUID | FK → propostas.id, NOT NULL |
| versao | INTEGER | NOT NULL |
| arquivo_url | TEXT | NOT NULL |
| arquivo_nome | VARCHAR(300) | |
| dados_extraidos | JSONB | (resultado bruto do OCR) |
| dados_parseados | JSONB | (resultado estruturado) |
| confiabilidade | NUMERIC(5,2) | |
| uploaded_by | UUID | FK → usuarios.id |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `comparativo_propostas`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| demanda_id | UUID | FK → demandas.id, NOT NULL, UNIQUE |
| propostas_ids | UUID[] | NOT NULL |
| benchmark_id | UUID | FK → propostas.id |
| dados_comparativo | JSONB | NOT NULL |
| anotacoes | JSONB | DEFAULT '[]' |
| gerado_em | TIMESTAMPTZ | DEFAULT NOW() |
| atualizado_em | TIMESTAMPTZ | DEFAULT NOW() |
#### `workflow_aprovacao`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| demanda_id | UUID | FK → demandas.id, NOT NULL |
| proposta_id | UUID | FK → propostas.id, NOT NULL |
| valor_total | NUMERIC(15,2) | NOT NULL |
| status | VARCHAR(30) | NOT NULL, DEFAULT 'pendente' |
| etapa_atual | INTEGER | DEFAULT 1 |
| etapas | JSONB | NOT NULL (array de etapas) |
| emergencial | BOOLEAN | DEFAULT FALSE |
| justificativa_emergencial | TEXT | |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
> **Status:** `pendente`, `em_andamento`, `aprovado`, `reprovado`, `aprovado_com_ressalva`, `cancelado`
> **Estrutura de `etapas` (JSONB):**
> ```json
> [
> {
> "ordem": 1,
> "perfil": "gestor_facilities",
> "aprovador_id": "uuid",
> "status": "aprovado",
> "data_acao": "2026-01-15T10:00:00Z",
> "observacao": "OK",
> "ressalva": false
> },
> {
> "ordem": 2,
> "perfil": "aprovador_financeiro",
> "aprovador_id": null,
> "status": "pendente",
> "data_acao": null,
> "observacao": null,
> "ressalva": false
> }
> ]
> ```
#### `matriz_alcada`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| centro_custo_id | UUID | FK → centros_custo.id |
| valor_minimo | NUMERIC(15,2) | NOT NULL |
| valor_maximo | NUMERIC(15,2) | NOT NULL |
| perfil_aprovador | VARCHAR(50) | NOT NULL |
| ordem_sequencial | INTEGER | DEFAULT 1 |
| ativo | BOOLEAN | DEFAULT TRUE |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `ordens_servico`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| numero | SERIAL | UNIQUE |
| demanda_id | UUID | FK → demandas.id, NOT NULL, UNIQUE |
| proposta_id | UUID | FK → propostas.id, NOT NULL |
| fornecedor_id | UUID | FK → fornecedores.id, NOT NULL |
| valor | NUMERIC(15,2) | NOT NULL |
| status | VARCHAR(30) | DEFAULT 'emitida' |
| data_emissao | TIMESTAMPTZ | DEFAULT NOW() |
| data_inicio | DATE | |
| data_conclusao | DATE | |
| observacoes | TEXT | |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
| updated_at | TIMESTAMPTZ | DEFAULT NOW() |
> **Status:** `emitida`, `em_execucao`, `concluida`, `cancelada`
#### `avaliacoes`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| ordem_servico_id | UUID | FK → ordens_servico.id, NOT NULL, UNIQUE |
| fornecedor_id | UUID | FK → fornecedores.id, NOT NULL |
| avaliador_id | UUID | FK → usuarios.id, NOT NULL |
| nota | NUMERIC(2,1) | NOT NULL, CHECK (nota >= 1 AND nota <= 5) |
| comentario | TEXT | |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
#### `audit_log`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| usuario_id | UUID | FK → usuarios.id |
| acao | VARCHAR(50) | NOT NULL |
| entidade | VARCHAR(50) | NOT NULL |
| entidade_id | UUID | |
| dados_antes | JSONB | |
| dados_depois | JSONB | |
| ip | VARCHAR(45) | |
| user_agent | TEXT | |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
> **Índice:** `CREATE INDEX idx_audit_log_entidade ON audit_log(entidade, entidade_id);`
> **Índice:** `CREATE INDEX idx_audit_log_usuario ON audit_log(usuario_id, created_at DESC);`
#### `alertas`
| Coluna | Tipo | Restrições |
|--------|------|-----------|
| id | UUID | PK |
| usuario_id | UUID | FK → usuarios.id, NOT NULL |
| tipo | VARCHAR(50) | NOT NULL |
| titulo | VARCHAR(300) | NOT NULL |
| mensagem | TEXT | |
| entidade | VARCHAR(50) | |
| entidade_id | UUID | |
| lido | BOOLEAN | DEFAULT FALSE |
| created_at | TIMESTAMPTZ | DEFAULT NOW() |
> **Tipos de alerta:** `estouro_orcamento`, `sla_vencendo`, `proposta_pendente`, `aprovacao_pendente`, `certidao_vencendo`, `os_atrasada`
---
## 3. Módulos NestJS
### 3.1 Estrutura de Pastas
```
src/
├── main.ts
├── app.module.ts
├── common/
│ ├── decorators/ # @Roles(), @CurrentUser(), @Audit()
│ ├── guards/ # JwtAuthGuard, RolesGuard
│ ├── interceptors/ # AuditInterceptor, TransformInterceptor
│ ├── pipes/ # ParseUUIDPipe, ValidationPipe
│ ├── filters/ # HttpExceptionFilter
│ ├── dto/ # PaginationDto, FilterDto
│ └── interfaces/ # ICurrentUser, IPaginatedResult
├── config/
│ ├── database.config.ts
│ ├── jwt.config.ts
│ ├── storage.config.ts
│ └── openai.config.ts
├── modules/
│ ├── auth/
│ │ ├── auth.module.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.service.ts
│ │ ├── strategies/ # JwtStrategy, LocalStrategy
│ │ ├── dto/ # LoginDto, RegisterDto, RefreshTokenDto
│ │ └── guards/
│ ├── users/
│ │ ├── users.module.ts
│ │ ├── users.controller.ts
│ │ ├── users.service.ts
│ │ ├── entities/ # User, Perfil
│ │ └── dto/
│ ├── locais/
│ │ ├── locais.module.ts
│ │ ├── locais.controller.ts
│ │ ├── locais.service.ts
│ │ ├── entities/ # Local, CentroCusto
│ │ └── dto/
│ ├── categorias/
│ │ ├── categorias.module.ts
│ │ ├── categorias.controller.ts
│ │ ├── categorias.service.ts
│ │ ├── entities/ # Categoria
│ │ └── dto/
│ ├── fornecedores/
│ │ ├── fornecedores.module.ts
│ │ ├── fornecedores.controller.ts
│ │ ├── fornecedores.service.ts
│ │ ├── entities/ # Fornecedor, Certidao
│ │ └── dto/
│ ├── demandas/
│ │ ├── demandas.module.ts
│ │ ├── demandas.controller.ts
│ │ ├── demandas.service.ts
│ │ ├── entities/ # Demanda, ItemLinha
│ │ └── dto/
│ ├── propostas/
│ │ ├── propostas.module.ts
│ │ ├── propostas.controller.ts
│ │ ├── propostas.service.ts
│ │ ├── entities/ # Proposta, VersaoProposta, ComparativoPropostas
│ │ └── dto/
│ ├── orcamento/
│ │ ├── orcamento.module.ts
│ │ ├── orcamento.controller.ts
│ │ ├── orcamento.service.ts
│ │ ├── entities/ # OrcamentoPlanejado
│ │ └── dto/
│ ├── workflow/
│ │ ├── workflow.module.ts
│ │ ├── workflow.controller.ts
│ │ ├── workflow.service.ts
│ │ ├── entities/ # WorkflowAprovacao, MatrizAlcada
│ │ └── dto/
│ ├── ordens-servico/
│ │ ├── ordens-servico.module.ts
│ │ ├── ordens-servico.controller.ts
│ │ ├── ordens-servico.service.ts
│ │ ├── entities/ # OrdemServico, Avaliacao
│ │ └── dto/
│ ├── ocr/
│ │ ├── ocr.module.ts
│ │ ├── ocr.service.ts
│ │ ├── ocr.processor.ts # Bull queue processor
│ │ ├── prompts/ # System prompts para GPT-4o-mini
│ │ └── interfaces/
│ ├── dashboard/
│ │ ├── dashboard.module.ts
│ │ ├── dashboard.controller.ts
│ │ └── dashboard.service.ts
│ └── relatorios/
│ ├── relatorios.module.ts
│ ├── relatorios.controller.ts
│ ├── relatorios.service.ts
│ └── templates/ # Templates PDF/Excel
└── database/
├── migrations/
└── seeds/
```
### 3.2 Dependências entre Módulos
```
┌──────────┐
│ Auth │
└────┬─────┘
│ JWT/Guards
┌──────────────┼──────────────────┐
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌──────────┐
│ Users │ │Dashboard │ │Relatórios│
└────────┘ └──────────┘ └──────────┘
│ agrega │ lê
┌──────────────────┼──────────────┐ │
▼ ▼ ▼ ▼ ▼
┌────────┐┌────────┐┌──────────┐┌──────────┐
│ Locais ││Categ. ││Demandas ││Orçamento │
└────────┘└────────┘└─────┬────┘└──────────┘
┌───────────┼───────────┐
▼ ▼ ▼
┌──────────┐┌──────────┐┌──────────┐
│Propostas ││Workflow ││ Fornec. │
└─────┬────┘└────┬─────┘└──────────┘
│ │
▼ ▼
┌────────┐┌──────────────┐
│ OCR ││Ordens Serviço│
└────────┘└──────────────┘
```
---
## 4. API REST Completa
### 4.1 Autenticação (`/api/auth`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| POST | `/api/auth/login` | Login (email + senha) → JWT | Público |
| POST | `/api/auth/refresh` | Renovar token | Autenticado |
| POST | `/api/auth/logout` | Invalidar refresh token | Autenticado |
| GET | `/api/auth/me` | Dados do usuário logado | Autenticado |
| POST | `/api/auth/forgot-password` | Solicitar reset de senha | Público |
| POST | `/api/auth/reset-password` | Redefinir senha com token | Público |
### 4.2 Usuários (`/api/users`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| GET | `/api/users` | Listar usuários (paginado) | Admin |
| GET | `/api/users/:id` | Detalhe do usuário | Admin |
| POST | `/api/users` | Criar usuário | Admin |
| PATCH | `/api/users/:id` | Atualizar usuário | Admin |
| DELETE | `/api/users/:id` | Desativar usuário (soft) | Admin |
| GET | `/api/perfis` | Listar perfis | Admin |
| POST | `/api/perfis` | Criar perfil | Admin |
| PATCH | `/api/perfis/:id` | Atualizar permissões | Admin |
### 4.3 Locais e Centros de Custo (`/api/locais`, `/api/centros-custo`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| GET | `/api/locais` | Listar locais | Autenticado |
| GET | `/api/locais/:id` | Detalhe do local | Autenticado |
| POST | `/api/locais` | Criar local | Admin, Gestor |
| PATCH | `/api/locais/:id` | Atualizar local | Admin, Gestor |
| DELETE | `/api/locais/:id` | Desativar local | Admin |
| GET | `/api/centros-custo` | Listar centros de custo | Autenticado |
| GET | `/api/centros-custo/:id` | Detalhe | Autenticado |
| POST | `/api/centros-custo` | Criar CC | Admin |
| PATCH | `/api/centros-custo/:id` | Atualizar CC | Admin |
### 4.4 Categorias (`/api/categorias`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| GET | `/api/categorias` | Listar (árvore ou flat) | Autenticado |
| GET | `/api/categorias/:id` | Detalhe | Autenticado |
| POST | `/api/categorias` | Criar categoria | Admin |
| PATCH | `/api/categorias/:id` | Atualizar | Admin |
| DELETE | `/api/categorias/:id` | Desativar | Admin |
### 4.5 Fornecedores (`/api/fornecedores`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| GET | `/api/fornecedores` | Listar (paginado, filtros) | Autenticado |
| GET | `/api/fornecedores/:id` | Detalhe + certidões | Autenticado |
| POST | `/api/fornecedores` | Cadastrar fornecedor | Admin, Gestor |
| PATCH | `/api/fornecedores/:id` | Atualizar | Admin, Gestor |
| DELETE | `/api/fornecedores/:id` | Desativar | Admin |
| GET | `/api/fornecedores/:id/certidoes` | Listar certidões | Autenticado |
| POST | `/api/fornecedores/:id/certidoes` | Upload certidão | Fornecedor, Admin |
| PATCH | `/api/fornecedores/:id/certidoes/:certId` | Atualizar certidão | Admin |
| GET | `/api/fornecedores/:id/avaliacoes` | Histórico de avaliações | Autenticado |
### 4.6 Demandas (`/api/demandas`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| GET | `/api/demandas` | Listar (paginado, filtros por status/CC/local/categoria) | Autenticado |
| GET | `/api/demandas/:id` | Detalhe completo | Autenticado |
| POST | `/api/demandas` | Criar demanda (rascunho) | Solicitante, Gestor |
| PATCH | `/api/demandas/:id` | Atualizar demanda | Solicitante, Gestor |
| POST | `/api/demandas/:id/publicar` | Publicar (rascunho → aberta) | Solicitante, Gestor |
| POST | `/api/demandas/:id/cancelar` | Cancelar demanda | Gestor, Admin |
| POST | `/api/demandas/:id/documentos` | Upload documento | Solicitante, Gestor |
| DELETE | `/api/demandas/:id/documentos/:docId` | Remover documento | Solicitante, Gestor |
| GET | `/api/demandas/:id/itens-linha` | Listar itens de linha | Autenticado |
| POST | `/api/demandas/:id/itens-linha` | Adicionar item de linha | Gestor |
| PATCH | `/api/demandas/:id/itens-linha/:itemId` | Atualizar item | Gestor |
| DELETE | `/api/demandas/:id/itens-linha/:itemId` | Remover item | Gestor |
| POST | `/api/demandas/:id/enviar-cotacao` | Enviar p/ cotação (validações) | Gestor |
| GET | `/api/demandas/:id/timeline` | Timeline de eventos | Autenticado |
### 4.7 Propostas (`/api/propostas`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| GET | `/api/demandas/:demandaId/propostas` | Listar propostas da demanda | Autenticado |
| GET | `/api/propostas/:id` | Detalhe da proposta | Autenticado |
| POST | `/api/demandas/:demandaId/propostas` | Upload proposta (PDF) | Fornecedor |
| POST | `/api/propostas/:id/nova-versao` | Nova versão do PDF | Fornecedor |
| GET | `/api/propostas/:id/versoes` | Listar versões | Autenticado |
| GET | `/api/propostas/:id/versoes/:versaoId` | Detalhe da versão + dados OCR | Autenticado |
| POST | `/api/propostas/:id/selecionar` | Selecionar proposta vencedora | Gestor |
| PATCH | `/api/propostas/:id/dados-extraidos` | Corrigir dados do OCR | Gestor |
| GET | `/api/demandas/:demandaId/comparativo` | Painel comparativo | Autenticado |
| POST | `/api/demandas/:demandaId/comparativo/gerar` | Gerar/atualizar comparativo | Gestor |
| POST | `/api/demandas/:demandaId/comparativo/anotacoes` | Adicionar anotação | Autenticado |
### 4.8 Orçamento (`/api/orcamento`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| GET | `/api/orcamento` | Listar planejamento (filtros: ano, mês, CC, categoria) | Financeiro, Admin |
| GET | `/api/orcamento/:id` | Detalhe | Financeiro, Admin |
| POST | `/api/orcamento` | Criar linha orçamentária | Admin |
| PATCH | `/api/orcamento/:id` | Atualizar planejado | Admin |
| POST | `/api/orcamento/importar` | Importar planilha | Admin |
| GET | `/api/orcamento/resumo` | Resumo planejado x comprometido x realizado | Financeiro, Diretoria |
| GET | `/api/orcamento/verificar` | Verificar disponibilidade (CC, categoria, valor) | Sistema |
### 4.9 Workflow de Aprovação (`/api/workflow`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| POST | `/api/demandas/:demandaId/workflow/iniciar` | Iniciar workflow | Gestor |
| GET | `/api/workflow/:id` | Status do workflow | Autenticado |
| POST | `/api/workflow/:id/aprovar` | Aprovar etapa atual | Aprovador da etapa |
| POST | `/api/workflow/:id/reprovar` | Reprovar (com justificativa) | Aprovador da etapa |
| POST | `/api/workflow/:id/aprovar-com-ressalva` | Aprovar com ressalva | Aprovador da etapa |
| GET | `/api/workflow/pendentes` | Minhas aprovações pendentes | Autenticado |
| GET | `/api/matriz-alcada` | Listar regras de alçada | Admin |
| POST | `/api/matriz-alcada` | Criar regra | Admin |
| PATCH | `/api/matriz-alcada/:id` | Atualizar regra | Admin |
| DELETE | `/api/matriz-alcada/:id` | Desativar regra | Admin |
### 4.10 Ordens de Serviço (`/api/ordens-servico`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| GET | `/api/ordens-servico` | Listar OS | Autenticado |
| GET | `/api/ordens-servico/:id` | Detalhe | Autenticado |
| POST | `/api/ordens-servico/:id/iniciar` | Marcar início execução | Gestor |
| POST | `/api/ordens-servico/:id/concluir` | Marcar conclusão | Gestor |
| POST | `/api/ordens-servico/:id/cancelar` | Cancelar OS | Admin |
| POST | `/api/ordens-servico/:id/avaliacao` | Avaliar fornecedor | Gestor, Solicitante |
### 4.11 Dashboard (`/api/dashboard`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| GET | `/api/dashboard/indicadores` | Cards principais | Autenticado |
| GET | `/api/dashboard/demandas-por-status` | Gráfico por status | Autenticado |
| GET | `/api/dashboard/demandas-por-categoria` | Gráfico por categoria | Autenticado |
| GET | `/api/dashboard/consumo-orcamento` | Plan. x Comprom. x Realiz. | Financeiro, Diretoria |
| GET | `/api/dashboard/propostas-por-fornecedor` | Gráfico fornecedores | Gestor |
| GET | `/api/dashboard/alertas` | Alertas do usuário | Autenticado |
| PATCH | `/api/dashboard/alertas/:id/ler` | Marcar alerta como lido | Autenticado |
### 4.12 Relatórios (`/api/relatorios`)
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| GET | `/api/relatorios/consumo-orcamentario` | Por CC e categoria | Financeiro, Diretoria |
| GET | `/api/relatorios/saving` | Saving gerado | Financeiro, Diretoria |
| GET | `/api/relatorios/historico-decisoes` | Trilha de auditoria | Admin |
| GET | `/api/relatorios/lead-time` | Lead time por status | Gestor, Admin |
| GET | `/api/relatorios/gargalos` | Gargalos de aprovação | Admin |
| GET | `/api/relatorios/exportar/:tipo` | Exportar PDF ou Excel | Autenticado |
### 4.13 OCR (`/api/ocr`) — interno
| Método | Rota | Descrição | Acesso |
|--------|------|-----------|--------|
| POST | `/api/ocr/extrair` | Disparar extração de PDF | Sistema/Gestor |
| GET | `/api/ocr/status/:jobId` | Status do job de extração | Sistema/Gestor |
---
## 5. Workflow de Aprovação — Máquina de Estados
### 5.1 Diagrama de Estados da Demanda
```
┌──────────┐
│ RASCUNHO │
└────┬─────┘
│ publicar (valida: itens_linha, CC, local)
┌──────────┐
│ ABERTA │
└────┬─────┘
│ gestor assume
┌──────────┐
│ EM_ESCOPO│ ← define itens de linha
└────┬─────┘
│ enviar cotação (valida: ≥1 item)
┌───────────┐
│ EM_COTAÇÃO│ ← fornecedores enviam propostas
└────┬──────┘
│ todas propostas recebidas
┌─────────────────┐
│PROPOSTAS_RECEB. │
└────────┬────────┘
│ gestor inicia comparação
┌─────────────┐
│EM_COMPARAÇÃO│ ← painel comparativo, OCR
└──────┬──────┘
│ seleciona proposta → inicia workflow
┌─────────────┐
│EM_APROVAÇÃO │ ← fluxo de alçadas
└──────┬──────┘
┌────┼────┐
▼ │ ▼
┌──────┐ │ ┌──────────┐
│REPROV│ │ │APROVADA │
└──────┘ │ └────┬─────┘
│ │ gera OS automática
│ ▼
│ ┌──────────┐
│ │OS_EMITIDA│
│ └────┬─────┘
│ │
│ ▼
│ ┌────────────┐
│ │EM_EXECUÇÃO │
│ └─────┬──────┘
│ │
│ ▼
│ ┌──────────┐
│ │CONCLUÍDA │
│ └──────────┘
(cancelar em qualquer ponto)
┌──────────┐
│CANCELADA │
└──────────┘
```
### 5.2 Fluxo de Alçadas (Workflow de Aprovação)
```
Proposta Selecionada (valor R$)
┌────────────────────────┐
│ Consultar matriz_alcada│
│ (CC + valor) │
└────────┬───────────────┘
Valor ≤ alçada do Gestor?
├── SIM → Aprovação automática pelo Gestor ──→ APROVADO
└── NÃO → Fluxo Sequencial:
┌─────────────────────┐
(1) │ Gestor de Facilities│──→ Aprova? ──→ Próxima etapa
└─────────────────────┘ │
Reprova? ──→ REPROVADO
┌─────────────────────┐
(2) │ Aprovador Financeiro│──→ Aprova? ──→ Próxima etapa
└─────────────────────┘ │
Reprova? ──→ REPROVADO
┌─────────────────────┐
(3) │ Diretoria │──→ Aprova? ──→ APROVADO
└─────────────────────┘ │
Reprova? ──→ REPROVADO
```
### 5.3 Exceções Emergenciais
1. Demanda marcada como `criticidade = 'critica'` pode ser sinalizada como **emergencial**
2. Workflow emergencial exige `justificativa_emergencial` obrigatória
3. Permite pular etapas intermediárias, indo direto ao aprovador de maior alçada
4. Gera alerta automático para todos os aprovadores pulados
5. Registra em `audit_log` com flag `emergencial = true`
### 5.4 Aprovação com Ressalva
- Qualquer aprovador pode aprovar com ressalva (`aprovado_com_ressalva`)
- Exige campo `observacao` obrigatório
- O workflow continua normalmente, mas a OS gerada carrega flag de ressalva
- Visível no relatório de histórico de decisões
---
## 6. OCR/IA — Estratégia de Extração Inteligente
### 6.1 Pipeline de Processamento
```
PDF Upload
┌──────────────────┐
│ 1. Armazenar S3 │ (MinIO / S3-compatible)
└────────┬─────────┘
┌──────────────────┐
│ 2. Fila Bull/ │ (Redis queue: "ocr-extraction")
│ BullMQ │
└────────┬─────────┘
│ Worker processa
┌──────────────────────────────────────┐
│ 3. Extrair texto do PDF │
│ - pdf-parse (texto nativo) │
│ - Tesseract.js (se escaneado) │
└────────┬─────────────────────────────┘
┌──────────────────────────────────────┐
│ 4. Enviar para GPT-4o-mini │
│ System prompt estruturado: │
│ - Extrair: valor_bruto, │
│ valor_liquido, impostos, │
│ condicao_pagamento, │
│ prazo_execucao, data_entrega │
│ - Mapear itens ↔ itens_linha │
│ - Retornar JSON tipado │
│ - Indicar confiança (0-100%) │
└────────┬─────────────────────────────┘
┌──────────────────────────────────────┐
│ 5. Salvar em versoes_proposta │
│ - dados_extraidos (raw GPT) │
│ - dados_parseados (estruturado) │
│ - confiabilidade │
└────────┬─────────────────────────────┘
┌──────────────────────────────────────┐
│ 6. Calcular match_escopo_pct │
│ Itens mapeados / Total itens │
└──────────────────────────────────────┘
```
### 6.2 System Prompt para GPT-4o-mini
```
Você é um extrator de dados de propostas comerciais de serviços de Facilities.
Dado o texto de uma proposta PDF, extraia os seguintes campos em JSON:
{
"valor_bruto": number | null,
"valor_liquido": number | null,
"impostos": {
"iss": number | null,
"inss": number | null,
"pcc": number | null,
"outros": number | null
},
"condicao_pagamento": string | null,
"prazo_execucao_dias": number | null,
"data_entrega_estimada": string | null, // ISO 8601
"itens": [
{
"descricao": string,
"valor_unitario": number | null,
"quantidade": number | null,
"valor_total": number | null,
"unidade": string | null
}
],
"confianca_geral": number // 0 a 100
}
Regras:
- Se o campo não for encontrado, retorne null
- Valores monetários em BRL, sem símbolo
- confianca_geral indica % de certeza da extração
- Retorne SOMENTE o JSON, sem explicações
```
### 6.3 Match de Escopo
O serviço compara `itens` retornados pelo GPT com `itens_linha` da demanda usando similaridade textual (Levenshtein / embeddings simples). Resultado: `match_escopo_pct` na proposta.
### 6.4 Tratamento de Falhas
- **Confiabilidade < 60%**: alerta ao Gestor para revisão manual
- **PDF escaneado com baixa qualidade**: fallback para Tesseract + retry GPT
- **Timeout GPT**: retry com backoff exponencial (3 tentativas)
- **Todos os erros logados** em `audit_log`
---
## 7. Segurança
### 7.1 RBAC — Controle de Acesso por Perfil
```typescript
// Decorator customizado
@Roles('administrador', 'gestor_facilities')
@UseGuards(JwtAuthGuard, RolesGuard)
```
**Matriz de Permissões:**
| Recurso | Solicitante | Gestor | Financeiro | Diretoria | Fornecedor | Admin |
|---------|:-----------:|:------:|:----------:|:---------:|:----------:|:-----:|
| Criar demanda | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
| Definir escopo | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
| Upload proposta | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| Comparar propostas | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Selecionar proposta | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
| Aprovar workflow | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Ver orçamento | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Editar orçamento | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Cadastros base | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Relatórios | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Avaliar fornecedor | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
### 7.2 Autenticação
- **JWT** com access token (15 min) + refresh token (7 dias)
- Refresh tokens armazenados em banco (revogáveis)
- Bcrypt para hash de senhas (salt rounds: 12)
- Rate limiting: 5 tentativas de login / 15 min por IP
### 7.3 LGPD
- Dados pessoais de fornecedores (CPF/CNPJ) criptografados em repouso (AES-256)
- Endpoint `DELETE /api/users/:id/dados-pessoais` para anonimização
- Consentimento registrado no cadastro de fornecedores
- Logs de acesso a dados sensíveis em `audit_log`
- Retenção de logs: 5 anos (configurável)
### 7.4 Audit Log
Todo `CUD` (Create, Update, Delete) é interceptado pelo `AuditInterceptor`:
```typescript
@Injectable()
export class AuditInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const req = context.switchToHttp().getRequest();
const before = /* snapshot antes */;
return next.handle().pipe(
tap(result => {
this.auditService.log({
usuario_id: req.user.id,
acao: req.method,
entidade: /* extraído da rota */,
entidade_id: req.params.id,
dados_antes: before,
dados_depois: result,
ip: req.ip,
user_agent: req.headers['user-agent'],
});
}),
);
}
}
```
---
## 8. Infraestrutura e Deploy
### 8.1 Ambientes
| Ambiente | Descrição |
|----------|-----------|
| `development` | Local, Docker Compose |
| `staging` | Pré-produção |
| `production` | Produção |
### 8.2 Docker Compose (dev)
```yaml
services:
api:
build: ./backend
ports: ["3000:3000"]
depends_on: [postgres, redis]
environment:
DATABASE_URL: postgresql://hefesto:hefesto@postgres:5432/hefesto
REDIS_URL: redis://redis:6379
JWT_SECRET: ${JWT_SECRET}
OPENAI_API_KEY: ${OPENAI_API_KEY}
S3_ENDPOINT: http://minio:9000
frontend:
build: ./frontend
ports: ["5173:5173"]
postgres:
image: postgres:16-alpine
volumes: ["pgdata:/var/lib/postgresql/data"]
environment:
POSTGRES_DB: hefesto
POSTGRES_USER: hefesto
POSTGRES_PASSWORD: hefesto
redis:
image: redis:7-alpine
minio:
image: minio/minio
command: server /data --console-address ":9001"
ports: ["9000:9000", "9001:9001"]
volumes:
pgdata:
```
### 8.3 Tecnologias e Bibliotecas
| Camada | Tecnologia |
|--------|-----------|
| Backend | NestJS 10+, TypeScript 5+ |
| ORM | TypeORM (migrations + entities) |
| Validação | class-validator, class-transformer |
| Filas | BullMQ + Redis |
| Autenticação | @nestjs/passport, passport-jwt |
| Upload | Multer + S3 SDK |
| PDF parse | pdf-parse, Tesseract.js |
| IA | OpenAI SDK (GPT-4o-mini) |
| Frontend | React 18+, Vite 5+ |
| UI Kit | shadcn/ui + Tailwind CSS |
| State | TanStack Query (React Query) |
| Gráficos | Recharts |
| Tabelas | TanStack Table |
| Exportação | jsPDF, ExcelJS |
| Testes | Jest (back), Vitest (front) |
---
## 9. Índices do Banco de Dados
```sql
-- Performance queries frequentes
CREATE INDEX idx_demandas_status ON demandas(status);
CREATE INDEX idx_demandas_solicitante ON demandas(solicitante_id);
CREATE INDEX idx_demandas_cc ON demandas(centro_custo_id);
CREATE INDEX idx_demandas_local ON demandas(local_id);
CREATE INDEX idx_demandas_created ON demandas(created_at DESC);
CREATE INDEX idx_propostas_demanda ON propostas(demanda_id);
CREATE INDEX idx_propostas_fornecedor ON propostas(fornecedor_id);
CREATE INDEX idx_workflow_status ON workflow_aprovacao(status);
CREATE INDEX idx_workflow_demanda ON workflow_aprovacao(demanda_id);
CREATE INDEX idx_os_status ON ordens_servico(status);
CREATE INDEX idx_os_fornecedor ON ordens_servico(fornecedor_id);
CREATE INDEX idx_orcamento_periodo ON orcamento_planejado(ano, mes);
CREATE INDEX idx_orcamento_cc ON orcamento_planejado(centro_custo_id);
CREATE INDEX idx_alertas_usuario ON alertas(usuario_id, lido, created_at DESC);
CREATE INDEX idx_certidoes_fornecedor ON certidoes(fornecedor_id);
CREATE INDEX idx_certidoes_validade ON certidoes(data_validade);
CREATE INDEX idx_audit_log_entidade ON audit_log(entidade, entidade_id);
CREATE INDEX idx_audit_log_usuario ON audit_log(usuario_id, created_at DESC);
```
---
## 10. Regras de Negócio Críticas
1. **Demanda sem itens de linha não pode ser publicada** — validação no `POST /demandas/:id/publicar`
2. **Demanda sem CC e Local não pode ser publicada** — validação no `POST /demandas/:id/publicar`
3. **Fornecedor com certidão vencida não pode receber OS** — verificação no momento da geração da OS
4. **Alerta automático quando proposta > 20% do orçamento planejado** — trigger no `OrcamentoService.verificar()`
5. **OS gerada automaticamente após aprovação final** — event listener no `WorkflowService`
6. **Rating do fornecedor atualizado após avaliação** — média ponderada recalculada
7. **Valor comprometido atualizado ao emitir OS**`OrcamentoService.comprometer()`
8. **Valor realizado atualizado ao concluir OS**`OrcamentoService.realizar()`
9. **Soft delete em todas as entidades cadastrais** — campo `ativo` ao invés de DELETE físico
---
*Documento gerado para o projeto HEFESTO — v1.0*