Bootstrap EDEN: Fase 0 (arquitetura) e Fase 1 (monorepo + infra)

Fase 0 — descoberta e arquitetura:
- Inventário do projeto, glossário de domínio, arquitetura com bounded
  contexts e topologia de containers, threat model inicial.
- 12 ADRs cobrindo modular monolith, topologia de containers (Postgres
  isolado + eden-core/parceiros/assinante em containers e portas
  distintos), auth/sessões, modelo de permissões, criptografia/segredos,
  contrato first-class, stock ledger, separação billing/finance/fiscal,
  outbox transacional, adapters SaperX e Focus NFe, e identidade
  compartilhada entre as 3 apps.
- 14 subagentes e 7 skills especializados por domínio em .claude/.
- Hooks de segurança (PreToolUse/PostToolUse/Stop) testados via pipe.

Fase 1 — plataforma (em andamento):
- Monorepo pnpm workspaces + Turborepo: apps/{api,worker,core-web,
  reseller-web,subscriber-web} + 9 packages compartilhados.
- apps/api: NestJS mínimo com /health/live e /health/ready (checando
  Postgres real via @eden/database).
- 3 frontends Vite + React + TypeScript + Tailwind, com o favicon
  oficial do EDEN.
- packages/database: migration baseline (node-pg-migrate) criando
  roles/role_permissions/applications/users/user_applications/sessions/
  audit_log — audit log append-only com hash-chain, testado ao vivo
  (UPDATE/DELETE bloqueados pelo trigger).
- compose.yaml implementando a topologia da ADR-0002, validada de ponta
  a ponta: os 6 containers sobem e ficam saudáveis com um único
  `docker compose up`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 08:01:14 -03:00
commit 44510bd019
149 changed files with 13006 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
# ADR-0001: Modular Monolith como estilo arquitetural do backend
## Status
Aceito
## Contexto
O EDEN precisa cobrir 14+ domínios de negócio (CRM, contratos, estoque, financeiro, billing, fiscal, telecom, suporte, RH, etc.) servindo 3 aplicações web distintas. O legado OrçaFácil é um monolito simples (Express + Postgres) sem separação de módulo forte. Microserviços trariam isolamento de falha e escalabilidade independente, mas custam operacionalmente caro (deploy, observabilidade, transação distribuída, N bancos ou schemas) num estágio em que o time e o volume ainda não justificam esse custo — e o Master Prompt (§4.2) explicitamente pede para evitar microserviços prematuros.
## Decisão
Construir `apps/api` como **modular monolith** em NestJS: um processo, módulos por bounded context (ver `docs/architecture.md` §2) com fronteiras de import explícitas (lint/arquitetura impede um módulo importar internals de outro), comunicação entre módulos via serviço de aplicação exposto ou evento de domínio (outbox) — nunca acesso direto a tabela de outro módulo.
Candidatos naturais a extração futura para serviço próprio, se/quando o volume justificar: **Billing** (processamento em lote, alta carga periódica) e **Fiscal** (chamadas externas longas/assíncronas). Nenhuma extração é feita nesta fase.
## Consequências
- Positivo: deploy único mais simples, transação ACID cross-módulo quando necessário (ex.: fechar oferta + criar cadastro), menor custo operacional inicial.
- Positivo: fronteiras de módulo desde o dia 1 tornam uma futura extração mecânica, não uma reescrita.
- Negativo: falha de um módulo (ex.: bug de memória em geração de PDF) pode afetar o processo inteiro — mitigado por `apps/worker` separado para jobs pesados/assíncronos (PDF, billing run, fiscal) desde o início.
- Negativo: todos os módulos compartilham o mesmo pool de conexão de banco — dimensionar pool e monitorar por módulo via métricas/labels.

View File

@@ -0,0 +1,35 @@
# ADR-0002: Topologia de containers — Postgres isolado + 1 container por aplicação web
## Status
Aceito (decisão explícita do operador)
## Contexto
O EDEN tem 3 aplicações web (`core-web`, `reseller-web`, `subscriber-web`) mais a API e um worker assíncrono, além do Postgres 18 exigido pelo Master Prompt (§4.3). O operador determinou explicitamente: o banco de dados deve subir em container separado; `eden-core`, `eden-parceiro` e `eden-assinantes` devem ser containers distintos, cada um em porta própria.
## Decisão
Topologia de containers via Docker Compose (um `compose.yaml` por ambiente — dev/staging/prod usando overrides), com os seguintes serviços:
| Serviço | Container | Porta exposta ao host | Fala com Postgres? |
|---|---|---|---|
| `eden-postgres` | Postgres 18 | Não (produção) / porta alta não-padrão (dev) | — |
| `eden-redis` | Redis (quando houver job real) | Não | — |
| `eden-api` | API principal (NestJS) | `EDEN_API_PORT` (env, default 8080) | Sim — único serviço com credencial de banco |
| `eden-worker` | Jobs assíncronos (BullMQ) | Não | Sim (mesma credencial de app, escopo próprio se possível) |
| `eden-core` | Frontend ERP interno | `EDEN_CORE_PORT` (env, default 3001) | Não — só HTTP para `eden-api` |
| `eden-parceiros` | Frontend portal revenda | `EDEN_PARCEIROS_PORT` (env, default 3002) | Não — só HTTP para `eden-api` |
| `eden-assinante` | Frontend portal assinante | `EDEN_ASSINANTE_PORT` (env, default 3003) | Não — só HTTP para `eden-api` |
Regras adicionais:
1. Rede docker interna dedicada (`eden_net`); só a API tem credencial de Postgres — as 3 apps web nunca recebem `DATABASE_URL`.
2. Portas configuráveis via `.env`, nunca hardcoded no `compose.yaml`, para permitir múltiplas instâncias (dev + staging) na mesma máquina.
3. `healthcheck` obrigatório em cada serviço; `depends_on` usa `condition: service_healthy`, não apenas ordem de start.
4. Volume nomeado `eden_pgdata` para dado do Postgres; nunca bind mount direto de produção sem estratégia de backup validada.
5. Build multi-stage; imagens de produção sem devDependencies/toolchain de build.
6. Backup lógico (Master Prompt §6.15) roda a partir do container do worker (ou de um job dedicado), nunca do host diretamente — mantém a regra de "streaming, nunca materializar em disco local" também em containers.
## Consequências
- Positivo: isolamento de falha por aplicação — um crash no frontend do assinante não derruba o core nem a API.
- Positivo: cada app pode escalar/atualizar independentemente (ex.: deploy do `eden-assinante` sem tocar no `eden-core`).
- Positivo: superfície de acesso ao banco reduzida a um único serviço, simplificando auditoria de acesso a dado.
- Negativo: mais serviços para orquestrar/monitorar do que um único container "tudo junto" — mitigado por healthchecks e observabilidade centralizada (Master Prompt §16).
- A definir na Fase 1: se `eden-core`/`eden-parceiros`/`eden-assinante` são servidos como SPA estática (nginx) ou com SSR — não muda a topologia de containers, só a imagem base de cada um.

View File

@@ -0,0 +1,21 @@
# ADR-0003: Autenticação e gestão de sessão
## Status
Aceito
## Contexto
O legado usa JWT HS256 com payload mínimo (`sub`, `ver`), expiração fixa de 30 dias, sem refresh token, invalidação via `token_version` incremental (derruba todas as sessões de uma vez, nunca uma só). O Master Prompt (§5.3) pede explicitamente uma evolução: access token curto + refresh token rotativo (ou sessão server-side segura), refresh tokens armazenados em hash, histórico de sessões/dispositivos, "encerrar todas as sessões", MFA/TOTP preparado.
## Decisão
- **Access token**: JWT de vida curta (15 min), payload mínimo (`sub`, sessão id), assinado (algoritmo a confirmar em ADR de criptografia geral — HS256 mantém paridade com o legado, RS256 fica em aberto se houver necessidade de verificação por serviço externo).
- **Refresh token**: opaco, alta entropia (≥256 bits), **rotativo a cada uso** (um novo é emitido e o antigo invalidado), armazenado só como hash no banco (nunca em claro) — mesma filosofia do token de link público do legado.
- **Tabela `sessions`** (não só um contador `token_version`): permite listar sessões/dispositivos ativos e revogar individualmente **ou** todas de uma vez (superset da capacidade do legado, que só permitia "todas").
- **Vínculo por aplicação**: uma sessão registra explicitamente para qual aplicação (`core`/`reseller`/`subscriber`) foi emitida — nunca inferir pelo papel do usuário (Master Prompt §5.1).
- **MFA/TOTP**: schema preparado desde a Fase 1 (tabela de fator, secret cifrado), ativação opcional para todos, indicada como obrigatória por política para `super_admin`/`admin` assim que a UI existir.
- Senha: Argon2id (ADR-0005 detalha parâmetros/versionamento), não bcrypt.
## Consequências
- Positivo: revogação granular por sessão/dispositivo, alinhado ao pedido explícito do Master Prompt.
- Positivo: access token de vida curta reduz janela de uso de token vazado sem exigir consulta ao banco a cada request (mesma vantagem de performance que o legado tinha com JWT, mas com menor exposição).
- Negativo: mais complexidade que o modelo legado (rotação de refresh token exige lógica de "replay detection" — se um refresh token já usado for reapresentado, é sinal de token roubado; a sessão inteira deve ser revogada nesse caso).
- Migração: nenhuma — é um sistema novo, não há sessões legadas para migrar.

View File

@@ -0,0 +1,20 @@
# ADR-0004: Modelo de permissões — RBAC + peso hierárquico + escopo de dados
## Status
Aceito
## Contexto
O legado tem um sistema de feature keys com 2 níveis (`view`/`edit`) por papel, mais peso hierárquico (`role weight`) que impede um ator de administrar papel/usuário de peso maior, mais um caso especial hardcoded de "próprio vs. todos" (`ofertas`/`ofertas_others`) repetido manualmente por feature quando necessário. O Master Prompt (§5.2) pede evolução: cada permissão deve expressar recurso/ação (`view`/`create`/`edit`/`delete`/`approve`/`export`/`manage`) e escopo (`own`/`team`/`reseller`/`legal_entity`/`all`) — generalizando o padrão "próprio vs. todos" em vez de repeti-lo campo a campo.
## Decisão
- Preservar o conceito de **role weight** exatamente como no legado (nunca um ator administra papel/usuário de peso maior; `super_admin` sempre no teto, nunca editável).
- Substituir o mapa plano `{feature_key: 'view'|'edit'}` por uma matriz `role_permissions(role, resource, action, scope)` — cada linha concede uma ação sobre um recurso com um escopo. Isso generaliza o caso `ofertas`/`ofertas_others` sem precisar de uma segunda feature key por recurso: o escopo `own` já cobre "só minhas ofertas", `all`/`reseller` cobre "todas"/"da revenda".
- Papel novo nasce sem nenhuma linha (zero acesso) — replica a regra do legado de nunca herdar permissão por padrão.
- `super_admin` continua **hardcoded fora da tabela** (nunca consultado via `role_permissions`), com bypass total mas sempre auditado.
- UI de gerenciamento de papel permite configurar, por menu/módulo, ação e alcance — conforme pedido explícito do Master Prompt §5.2.
## Consequências
- Positivo: elimina a necessidade de duplicar feature key para cada distinção "próprio vs. todos" que aparecer no futuro (o legado já tinha um caso day-1, `ofertas_others` — outros vão aparecer em Contratos, Chamados, etc.).
- Positivo: mapeamento direto do padrão de autorização do legado (`requireFeatureOrSuperAdmin`) para um middleware `requirePermission(resource, action, scopeResolver)` equivalente.
- Negativo: migração de mental model para quem vai configurar papéis (matriz maior que o mapa binário do legado) — mitigado com UI que agrupa por módulo/menu como já era.
- Todo endpoint continua resolvendo escopo no servidor a partir da sessão (nunca aceitar `reseller_id`/`customer_id` do cliente) — reforça Master Prompt §12.

View File

@@ -0,0 +1,20 @@
# ADR-0005: Criptografia e gestão de segredos
## Status
Aceito
## Contexto
O legado usa bcrypt custo 10 para senha, JWT HS256 sem rotação, AES-256-GCM para o único segredo reversível identificado (senha de equipamento Control iD), HMAC-SHA256 para OTP. O Master Prompt (§5.4) pede Argon2id para senha e AES-256-GCM (ou equivalente autenticado) com versionamento de chave para todo segredo operacional reversível (API keys de IA, tokens SaperX, credenciais Control iD, secrets de gateway), com chave raiz nunca no banco.
## Decisão
- **Hash unidirecional** (senha, refresh token, tokens públicos sem necessidade de recuperação): Argon2id, parâmetros iniciais conservadores e revisáveis (memory cost, iterations, parallelism documentados em `packages/auth`), com **versionamento de parâmetro** por hash armazenado (permite aumentar custo no futuro sem invalidar hashes antigos — eles são re-hasheados no próximo login bem-sucedido).
- **Criptografia reversível de campo**: AES-256-GCM, IV de 96 bits aleatório por operação, tag de autenticação verificada na decriptação (falha se adulterado) — mesmo formato de armazenamento do legado (`iv:tag:ciphertext`, base64), reaproveitando padrão já validado em produção pela Handix.
- **Versionamento de chave**: todo campo cifrado grava também qual versão de chave raiz foi usada (`key_version`), permitindo rotação de chave raiz sem re-cifrar tudo de uma vez (re-cifra sob demanda/job de rotação).
- **Chave raiz**: nunca no banco nem na imagem do container — variável de ambiente/secret store, injetada no container em runtime. Rotação de chave raiz é operação registrada e auditada (runbook próprio).
- **OTP**: manter HMAC-SHA256 com segredo de servidor (nunca hash simples — espaço pequeno de 10⁶ valores exige resistência a rainbow table via segredo), TTL curto, uso único, máximo de tentativas com bloqueio — replicar fielmente o padrão do legado (validado em produção).
- **Cartão de crédito**: nunca armazenar CVV; tokenização via gateway/PSP; nenhum cofre de cartão caseiro (Master Prompt §5.4).
## Consequências
- Positivo: Argon2id é hoje o padrão recomendado (OWASP) sobre bcrypt, resistente a ataque por GPU/ASIC.
- Positivo: versionamento de chave/parâmetro evita "big bang" de rotação — rotação é incremental e auditável.
- Negativo: Argon2id é mais pesado computacionalmente que bcrypt custo 10 — dimensionar parâmetros considerando throughput de login esperado (não copiar cegamente defaults de biblioteca sem medir).

View File

@@ -0,0 +1,23 @@
# ADR-0006: Contrato como agregado de primeira classe
## Status
Aceito
## Contexto
No legado, "contrato" não é uma tabela — é a junção em tempo de consulta de `client_registrations` (ativos) com a `quotes` que os originou. Isso funciona para o caso simples de uma oferta = um contrato, mas não suporta amendments, renovações, múltiplas versões, ou itens/partes de contrato como entidades consultáveis. O Master Prompt (§6.6) exige transformar contrato em agregado de primeira classe.
## Decisão
Criar as tabelas: `contracts`, `contract_items`, `contract_parties`, `contract_versions`, `contract_documents`, `contract_amendments`, `contract_renewals`, `contract_status_history`, `contract_assets`, `contract_services`, `contract_billing_rules`.
Estados: `draft → pending_signature → active → suspended/cancelled/terminated/expired → renewed`.
Regras preservadas do legado (nunca perder):
- **`contract_period` (faixa de preço) permanece distinto de `fidelity_period` (permanência efetiva)** — vigência/vencimento/multa sempre calculados pela fidelidade **resolvida** (`fidelity_period` se aprovado, senão `contract_period`), nunca pela faixa de preço bruta.
- Contrato assinado grava **snapshot** dos valores jurídicos/comerciais relevantes no momento da assinatura (via `contract_versions`) — uma alteração futura de produto/preço nunca muda retroativamente um contrato já assinado (invariante nº4 do Master Prompt §24).
- Fluxo de fechamento de oferta → geração de contrato preserva a "trava" equivalente (oferta travada do legado vira, no EDEN, transição de estado do contrato que também impede edição desconforme, exceto por papel com permissão de correção auditada equivalente ao `super_admin` do legado).
## Consequências
- Positivo: permite amendments/renovações/múltiplas partes sem gambiarra de "reabrir a oferta".
- Positivo: relatórios de vencimento/MRR (equivalente ao `GET /contracts/report` do legado) passam a consultar uma tabela real em vez de uma junção calculada, com melhor performance de índice.
- Negativo: mais complexidade de schema/migração do que o legado; mitigado por ser green-field (sem dado legado a migrar automaticamente — Handix decide se há import histórico do OrçaFácil, fora do escopo desta ADR).
- Depende de: Customer 360 (Fase 2) e Commercial/Ofertas (Fase 2) já existirem — ver `docs/architecture.md` §2.

View File

@@ -0,0 +1,20 @@
# ADR-0007: Estoque como ledger de movimentos, nunca saldo editável
## Status
Aceito
## Contexto
O legado não tem módulo de estoque real — produtos têm preço e flags, mas nenhuma tabela de movimento/saldo. O Master Prompt (§6.8, §11.5) exige estoque com ledger de movimentos, ativos serializados (serial/patrimônio/MAC) e rastreabilidade completa warehouse↔cliente↔warehouse.
## Decisão
Modelar: `warehouses`, `warehouse_locations`, `stock_items`, `stock_lots` (quando necessário), `stock_movements` (ledger append-only), `stock_reservations`, `serialized_assets`, `asset_assignments`, `inventory_counts`, `transfers`, `receipts`, `issues`, `returns`, `rma`, `asset_maintenance`.
Saldo de estoque é **sempre calculado** a partir de `stock_movements` (soma de entradas/saídas), nunca uma coluna editável diretamente. Cada `serialized_asset` tem um único estado ativo por vez (nunca dois ativos "ativos" com o mesmo serial/MAC/patrimônio simultaneamente — constraint de banco, não só validação de aplicação).
Fluxo de instalação (fechamento de oferta/contrato com equipamento): reserva → seleção de unidade serializada → vínculo a contrato/cliente → movimento para instalado/comodato → rastreabilidade até devolução/baixa (Master Prompt §6.8).
## Consequências
- Positivo: auditoria completa de estoque (invariante nº7 do Master Prompt §24 — rastrear do warehouse até o cliente e de volta).
- Positivo: elimina classe de bug "saldo dessincronizado" comum em campo editável.
- Negativo: toda operação de estoque precisa passar por um serviço de domínio que grava o movimento — nenhum caminho de escrita direta em "saldo".
- Depende de: Organization (warehouses por unidade legal) já existir.

View File

@@ -0,0 +1,22 @@
# ADR-0008: Separação entre Billing, Financeiro (AR/AP) e Fiscal
## Status
Aceito
## Contexto
O legado não tem billing recorrente nem contas a receber/pagar como módulo — só a oferta/contrato com valores. O Master Prompt (§6.9, §6.10, §6.11) exige três motores distintos e explicitamente adverte: "nunca confundir faturamento, documento fiscal e recebimento: são eventos relacionados, porém distintos."
## Decisão
Três motores com dados e responsabilidades separadas:
1. **Billing** (`billing_accounts`, `billing_cycles`, `subscriptions/services`, `charge_components`, `usage_charges`, `invoices`, `invoice_items`, `invoice_adjustments`, `billing_runs`): responsável por **calcular o que é devido** (mensalidade, pró-rata, implantação, consumo) e gerar a fatura interna (`invoice`). Fechamento de billing run é idempotente e reexecutável com segurança **antes** da consolidação; depois de consolidada, uma invoice não é editada silenciosamente — usa ajuste/nota de crédito/débito ou refaturamento controlado.
2. **Finance/AR-AP**: responsável por **cobrar e receber/pagar** — títulos, parcelas, boletos (provider abstraction), dunning, conciliação, contas a pagar. Um título de AR nasce a partir de uma invoice de billing, mas é uma entidade própria (permite negociação, baixa parcial, estorno, sem tocar no billing).
3. **Fiscal**: responsável por **emitir o documento fiscal** correspondente a um item de billing já classificado (fiscal profile) — nunca hardcoded por tela. Documento fiscal é consequência do item de faturamento, não do clique de um usuário numa tela específica.
Cada camada guarda `external_id`/referência para a anterior, nunca duplica o cálculo.
## Consequências
- Positivo: permite reconciliar valor da origem (billing) até o recebimento (finance) e até o documento fiscal (fiscal) — invariante nº5 e nº6 do Master Prompt §24.
- Positivo: mudança de gateway de cobrança (Finance) não afeta o cálculo de billing; mudança de regra tributária (Fiscal) não afeta o cálculo comercial.
- Negativo: mais tabelas e mais pontos de integração entre módulos do que uma solução monolítica "fatura única" — mitigado por eventos de domínio (`invoice.created`, `payment.received`, `fiscal_document.authorized`) documentados no Master Prompt §9.2.
- Depende de: Contracts (Fase 3) e Inventory/consumo (para usage_charges) parcialmente.

View File

@@ -0,0 +1,18 @@
# ADR-0009: Outbox transacional para eventos de integração
## Status
Aceito
## Contexto
O EDEN precisa publicar eventos de domínio (`lead.created`, `contract.signed`, `invoice.overdue`, etc. — catálogo no Master Prompt §9.2) para consumo por n8n/webhooks/outros módulos, sem perder evento em caso de falha entre o commit da transação de negócio e a publicação. O legado não tem esse problema porque não publica eventos externos.
## Decisão
Toda operação que precise emitir um evento relevante grava a linha do evento na mesma transação SQL da mudança de negócio, numa tabela `outbox_events` (payload normalizado, tipo do evento, status `pending/delivered/failed`, tentativas, correlation id). Um processo separado (`apps/worker`) lê a outbox e entrega (webhook assinado HMAC, ou fila interna para o próprio módulo consumidor), marcando como entregue só após confirmação — com retry/backoff e dead-letter após esgotar tentativas.
Consumidores (internos ou externos via n8n) devem ser idempotentes por `event_id` — reentrega nunca duplica efeito.
## Consequências
- Positivo: garante "at-least-once" delivery sem depender de o processo de aplicação sobreviver após o commit (invariante nº9 do Master Prompt §24 — webhook repetido não duplica efeito, aplicado também no sentido saída).
- Positivo: replay manual de evento é trivial (reprocessar linha da outbox).
- Negativo: exige limpeza/arquivamento periódico da tabela de outbox (retenção configurável) para não crescer indefinidamente.
- Negativo: consumidores precisam de lógica de idempotência — custo replicado em cada integração, mitigado por uma camada compartilhada em `packages/integrations`.

View File

@@ -0,0 +1,19 @@
# ADR-0010: Adapter SaperX isolado
## Status
Aceito
## Contexto
SaperX é o sistema de telecom/billing externo com o qual o EDEN precisa se integrar (circuitos, DIDs, consumo/CDR, faturas). O legado (`eden.md`) não documenta integração automática com sistemas de telecom externos (a única "integração IXC" citada é manual, campo de texto livre) — SaperX é uma integração nova para o EDEN, sem precedente de código a herdar, só requisitos do Master Prompt §6.12.
## Decisão
Módulo `integrations/saperx` como adapter/port isolado (padrão do Master Prompt §13): configuração por ambiente, token criptografado (AES-256-GCM, ver ADR-0005), suporte a IP allowlist se a API exigir, timeout + retry seguro + rate limiting local + circuit breaker, correlation id em toda chamada, logs nunca com token, healthcheck de integração próprio (não derruba o `/health/ready` geral do EDEN por indisponibilidade do SaperX).
Nunca acoplar entidades internas ao payload bruto do SaperX — todo dado externo vira DTO normalizado antes de entrar no domínio, com `external_id` + snapshot de origem quando necessário para auditoria/reconciliação.
Se um endpoint necessário não estiver disponível/documentado no momento da implementação: implementar interface + mock + TODO explícito, nunca inventar resposta.
## Consequências
- Positivo: se a API do SaperX mudar ou a Handix trocar de provider de telecom no futuro, só o adapter muda — domínio (contratos, billing, customer 360) permanece intacto.
- Positivo: consistente com o mesmo padrão usado para Focus NFe e Control iD — um único modelo mental de integração externa no projeto inteiro.
- Risco conhecido: sem acesso à documentação/sandbox do SaperX nesta fase — endpoints exatos e formato de payload serão confirmados na Fase 6; até lá, contrato do adapter fica desenhado a partir dos objetivos de negócio (Master Prompt §6.12), não de um payload real.

View File

@@ -0,0 +1,18 @@
# ADR-0011: Adapter Focus NFe isolado
## Status
Aceito
## Contexto
O EDEN precisa emitir NFCom, NFS-e e recibo de locação via Focus NFe (Master Prompt §6.11). O legado tem toda a camada de **catálogos** fiscais (NCM, CFOP, municípios, cClass NFCom, cTribNac/cTribMun, IBS/CBS) e **configuração fiscal de produto** (`product_fiscal_profiles`) já madura e testada em produção, mas **não tem motor de emissão** (`fiscal_rules` existe só como schema, sem lógica) — é o ponto de partida a herdar, não a integração de emissão em si, que é nova.
## Decisão
- Portar fielmente o modelo de catálogos fiscais e `product_fiscal_profiles` do legado (índice único parcial "um profile ativo por componente de faturamento", proteção contra inativação em massa no upsert, importação automática vs. manual com dry-run obrigatório) — é infraestrutura validada, não reinventar.
- Construir `integrations/focus-nfe` como adapter/port novo: referência única/idempotente por documento, emissão, consulta, cancelamento, webhooks autenticados, persistência do request normalizado (nunca com segredo), retries com backoff, dead-letter/retry manual.
- Emissão fiscal é sempre consequência de um item de billing já classificado por fiscal profile — nunca lógica hardcoded numa tela (mesmo princípio do Master Prompt §6.11).
- `fiscal_rules` (motor de resolução automática de regra fiscal): portar como schema preparado, sem implementar motor de resolução nesta fase — mesma decisão consciente já tomada no legado ("não implementar resolução automática completa agora"), reavaliar na Fase 6.
## Consequências
- Positivo: reaproveita ~19 tabelas de catálogo fiscal já desenhadas e testadas contra fontes reais (Siscomex/IBGE), reduzindo risco de erro em modelagem tributária brasileira (área de alto custo de erro).
- Positivo: separação nítida entre "classificação fiscal do produto" e "documento fiscal emitido" evita duplicar regra tributária em múltiplos lugares (Master Prompt §6.11).
- Negativo: sem motor de resolução automática, a escolha de código fiscal por operação continua dependendo de configuração manual do fiscal profile — aceito como escopo da Fase 6; motor completo fica como trabalho futuro explícito, não "silenciosamente esquecido".

View File

@@ -0,0 +1,18 @@
# ADR-0012: Três aplicações web, identidade e backend compartilhados
## Status
Aceito
## Contexto
O EDEN precisa de 3 frontends (Core interno, Parceiros/Revenda, Assinante) com níveis de acesso e dados completamente diferentes, mas compartilhando o mesmo ecossistema de API e identidade (Master Prompt §7, §8, missão §0). O legado é uma aplicação única (OrçaFácil) sem essa separação — todo controle de acesso é por papel/feature dentro do mesmo frontend.
## Decisão
- **Uma identidade pode ter acesso a uma ou mais aplicações** (`core`/`reseller`/`subscriber`), modelado explicitamente (tabela de vínculo identidade↔aplicação), nunca inferido pelo nome do papel (Master Prompt §5.1) — evita o erro de assumir "todo `reseller_admin` só acessa `reseller`", o que impediria, por exemplo, um funcionário Handix logar como suporte em nome de uma revenda no futuro, se o negócio pedir.
- **Cada aplicação é um frontend React+Vite próprio** (`apps/core-web`, `apps/reseller-web`, `apps/subscriber-web`), cada uma em seu container (ADR-0002), consumindo a mesma API (`apps/api`) via REST versionada (`/api/v1`).
- **Isolamento de dado por escopo, nunca por frontend**: a separação de app não substitui a checagem de escopo no backend (§5.2/ADR-0004) — um usuário `reseller` autenticado contra a API nunca deve conseguir, mesmo manipulando requests, ver dado de outra revenda; isso é reforçado por teste automatizado explícito (Master Prompt §15.2, item 12).
- **Design System único** (`packages/ui`) compartilhado pelas 3 apps, derivado do tema DreamsERP, garantindo consistência visual sem duplicar componente.
## Consequências
- Positivo: onboarding de nova aplicação futura (ex.: app mobile) reaproveita a mesma API/identidade sem redesenho.
- Positivo: bug de isolamento fica mais fácil de testar (mesma API, 3 conjuntos de credenciais de teste, casos invariantes automatizados).
- Negativo: qualquer mudança de contrato de API precisa considerar as 3 apps simultaneamente — versionamento de API (`/api/v1`) e testes de contrato (Master Prompt §15.1) mitigam quebra silenciosa.