Ir para o conteúdo

Log de Decisões de Arquitetura (ADR)

O que é: registro das decisões técnicas e de produto importantes — o porquê por trás do sistema. Em uma due diligence técnica (ex.: venda da empresa), este é um dos documentos mais valiosos: mostra que o sistema foi construído com intenção, não por acaso.

Como manter: ao tomar uma decisão relevante (nova integração, mudança de arquitetura, regra de negócio estrutural, trade-off de segurança), adicione uma entrada aqui com Contexto → Decisão → Consequências.


ADR-0001 — Acesso a dados por RPC SECURITY DEFINER + guards

Contexto: o service-role do Supabase não bypassa RLS; precisávamos de um padrão seguro e consistente de acesso multi-tenant. Decisão: toda leitura/escrita passa por funções Postgres SECURITY DEFINER chamadas via .rpc(). O clinic_id/org_id vem sempre do servidor (guards requireClinicUser/requireOrgAdmin/requirePatient), nunca do corpo da requisição. Consequências: isolamento forte por tenant; lógica de negócio centralizada no banco; tipos do Supabase ficam desatualizados (ignoreBuildErrors: true — esperado).

ADR-0002 — Isolamento multi-tenant (rede × unidade × paciente)

Contexto: uma plataforma para franqueadora (311 unidades) + clínicas independentes, com dados sensíveis de saúde. Decisão: organizations.type (franqueadora | independente) é o único switch entre rede e solo. Todo dado é escopado por clinic_id (unidade) e organization_id (rede). Franqueado/staff só enxergam as unidades deles. O app clínico é idêntico para solo e rede; a franqueadora é camada aditiva. Consequências: regra dura de isolamento; relatórios de rede são agregações read-only; nunca vazar dados entre unidades.

ADR-0003 — Migrations aplicadas no banco (Management API)

Contexto: evolução rápida do schema em produção. Decisão: migrations via Supabase Management API, registradas em public.schema_migrations. A pasta supabase/migrations/ tem um subconjunto; o restante vive no banco (introspectar com pg_get_functiondef). Consequências: agilidade; risco de divergência schema↔repo — sempre introspectar antes de regravar RPCs.

ADR-0004 — Anexos clínicos no CDN com URL assinada (Bunny + Token Auth)

Contexto: imagem clínica (radiografia/foto) é dado sensível de saúde (LGPD art. 11); não pode ficar em URL pública. Decisão: arquivos no Bunny.net (compressão webp + thumbnail), servidos por URL assinada de curta duração (Token Authentication ligado na pull zone). Soft-delete para conciliar retenção (CRO ~10 anos) × direito de apagamento. Consequências: imagem clínica privada por padrão; arquivo nunca no Postgres; bunny_token_key é segredo crítico.

ADR-0005 — Odontograma por FACE (mapa clínico, não silhueta 3D)

Contexto: marcação por face (mesial/distal/oclusal/vestibular/lingual) é padrão clínico/pericial. Decisão: o odontograma clínico usa o mapa de 5 faces quadrado (convenção que o dentista conhece), não a silhueta 3D (que fica só no seletor de orçamento). jsonb retrocompatível (dados antigos de dente inteiro seguem válidos). Consequências: UX familiar ao dentista; sem migração de dados; snapshots guardam o histórico.

ADR-0006 — Lead Score event-driven

Contexto: milhares de pacientes/dia na rede; precisamos priorizar leads quentes sem recalcular tudo. Decisão: eventos de engajamento (append-only em lead_events) + score incremental denormalizado em queue_entries (O(1) por evento, com dedup "once"). Nunca recalcula a rede. Consequências: escalável; pontos hardcoded na RPC (tunáveis depois).

ADR-0007 — Painel do Dono com gate financeiro_dono

Contexto: o módulo financeiro hoje é liberado também para o operador de caixa. Decisão: o P&L/DRE da unidade fica atrás de um módulo novo financeiro_dono (só dono/gerente). Regime de caixa. Custo (repasse do dentista + material) × despesa serão separados numa evolução. Consequências: o caixa não vê o lucro do dono; consolidação na rede sem duplicar.

ADR-0008 — Agenda: bloqueio, com avaliação em pool compartilhado

Contexto: sistemática Coife de agenda. Decisão: consultas/procedimentos = bloqueio (não agenda um em cima do outro). Avaliação = livre em qualquer horário de funcionamento; quando o cliente chega entra num pool que todos os dentistas veem e o primeiro livre puxa (reusa o corredor/fila). Consequências: disciplina na agenda + flexibilidade na avaliação.

ADR-0009 — Convênios como dimensão do orçamento (TISS)

Contexto: a rede quer atender convênio; não existe API REST aberta por operadora — o padrão é TISS (XML). Decisão: convênio é uma dimensão do orçamento (payer_type = particular | convenio), não um módulo paralelo. Coparticipação reusa patient_charges. Odonto usa Guia de Consulta + GTO. Começar pelos padrões TISS da operadora parceira (Odontomaxi), crescer organizado. Consequências: um só funil de vendas; TISS implementado uma vez serve todas as operadoras.

ADR-0010 — Receita digital via Memed (não assinatura ICP própria)

Contexto: receita digital exige assinatura; exigir certificado ICP-Brasil por dentista é fricção alta. Decisão: integrar a Memed (que já resolve a assinatura via certificado em nuvem). Backend pega o token do prescritor; frontend carrega o módulo. Lembrete de alergia vindo da nossa anamnese (diferencial). Chaves de produção guardadas; ambiente em homologação até a Memed validar. Consequências: menos fricção e menos lock-in jurídico; dependência de um parceiro externo (mitigada pela abstração).

ADR-0011 — WhatsApp via WhatsJá

Contexto: instância de WhatsApp por clínica é operação pesada. Decisão: suprimir o módulo de instância por clínica por ora; WhatsApp virá pela integração com o WhatsJá. Consequências: menos atrito de setup; dependência do WhatsJá para os envios.