-- ============================================================================ -- Cria tabelas do prontuário clínico -- ---------------------------------------------------------------------------- -- Núcleo do prontuário: notas clínicas (anamnese, evolução, plano), com -- versionamento (audit trail) e templates (SOAP/DAP/BIRP/livre). -- -- Decisões (sessão de modelagem 2026-05-20): -- • Tabela única `clinical_notes` discriminada por `note_type` (não 1 tabela -- por tipo). Templates customizáveis exigem flexibilidade. -- • `content_text` (livre) + `content_structured` (jsonb) coexistem na mesma -- row — UI prioriza conforme template; busca/edit rápido sempre tem text. -- • Versionamento via snapshot completo (não diff) em `clinical_note_versions` -- — restore trivial e audit visualization friendly. Trigger de versionamento -- criado em migration separada. -- • Instrumentos de avaliação (GAD-7, PHQ-9, etc) ficam pra Fase 2. -- • RLS: owner-only (terapeuta responsável). Sem clinic-wide read — CFP exige -- sigilo entre profissionais. Policies em migration separada. -- ============================================================================ BEGIN; -- ────────────────────────────────────────────────────────────────────────── -- 1. clinical_notes — núcleo do prontuário -- ────────────────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS public.clinical_notes ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id uuid NOT NULL, owner_id uuid NOT NULL, -- terapeuta responsável patient_id uuid NOT NULL REFERENCES public.patients(id) ON DELETE RESTRICT, session_event_id uuid REFERENCES public.agenda_eventos(id) ON DELETE SET NULL, note_type text NOT NULL, template_id uuid, -- FK adicionada após criar templates title text, content_text text, content_structured jsonb, pinned boolean DEFAULT false NOT NULL, is_draft boolean DEFAULT false NOT NULL, created_at timestamp with time zone DEFAULT now() NOT NULL, updated_at timestamp with time zone DEFAULT now() NOT NULL, created_by uuid NOT NULL, updated_by uuid, deleted_at timestamp with time zone, deleted_by uuid, CONSTRAINT clinical_notes_note_type_check CHECK (note_type IN ( 'anamnese', 'evolucao_sessao', 'plano_terapeutico', 'observacao_livre', 'resumo_caso' )), CONSTRAINT clinical_notes_content_present_check CHECK ( content_text IS NOT NULL OR content_structured IS NOT NULL ) ); COMMENT ON TABLE public.clinical_notes IS 'Notas clínicas do prontuário (anamnese, evolução de sessão, plano, observações). Owner-only via RLS — CFP exige sigilo.'; COMMENT ON COLUMN public.clinical_notes.session_event_id IS 'Sessão associada (quando aplicável). Anamnese/plano/resumo podem ter NULL.'; COMMENT ON COLUMN public.clinical_notes.content_text IS 'Conteúdo em texto livre (sempre disponível pra busca/edit rápido).'; COMMENT ON COLUMN public.clinical_notes.content_structured IS 'Conteúdo em formato estruturado quando há template ativo (jsonb dos campos preenchidos).'; CREATE INDEX IF NOT EXISTS idx_clinical_notes_patient_recent ON public.clinical_notes (tenant_id, patient_id, created_at DESC) WHERE deleted_at IS NULL; CREATE INDEX IF NOT EXISTS idx_clinical_notes_owner ON public.clinical_notes (owner_id) WHERE deleted_at IS NULL; CREATE INDEX IF NOT EXISTS idx_clinical_notes_session ON public.clinical_notes (session_event_id) WHERE session_event_id IS NOT NULL AND deleted_at IS NULL; CREATE INDEX IF NOT EXISTS idx_clinical_notes_type ON public.clinical_notes (tenant_id, patient_id, note_type) WHERE deleted_at IS NULL; CREATE INDEX IF NOT EXISTS idx_clinical_notes_pinned ON public.clinical_notes (tenant_id, patient_id) WHERE pinned = true AND deleted_at IS NULL; -- ────────────────────────────────────────────────────────────────────────── -- 2. clinical_note_versions — audit trail (snapshot completo) -- ────────────────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS public.clinical_note_versions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), note_id uuid NOT NULL REFERENCES public.clinical_notes(id) ON DELETE CASCADE, tenant_id uuid NOT NULL, version_number integer NOT NULL, title text, content_text text, content_structured jsonb, change_reason text, -- 'criacao' | 'edicao' | livre created_at timestamp with time zone DEFAULT now() NOT NULL, created_by uuid NOT NULL, CONSTRAINT clinical_note_versions_unique UNIQUE (note_id, version_number) ); COMMENT ON TABLE public.clinical_note_versions IS 'Snapshot completo de cada versão de clinical_notes. Criado via trigger AFTER INSERT OR UPDATE.'; CREATE INDEX IF NOT EXISTS idx_clinical_note_versions_recent ON public.clinical_note_versions (note_id, version_number DESC); CREATE INDEX IF NOT EXISTS idx_clinical_note_versions_audit ON public.clinical_note_versions (created_by, created_at DESC); -- ────────────────────────────────────────────────────────────────────────── -- 3. clinical_note_templates — templates SOAP/DAP/BIRP/anamnese padrão -- ────────────────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS public.clinical_note_templates ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id uuid, -- NULL = template global do sistema owner_id uuid, -- NULL = template do tenant inteiro key text NOT NULL, -- 'soap', 'dap', 'birp', 'anamnese_padrao', ... name text NOT NULL, note_type text NOT NULL, description text, structure jsonb NOT NULL, -- [{key, label, type, required, hint}] is_system boolean DEFAULT false NOT NULL, is_global boolean DEFAULT false NOT NULL, active boolean DEFAULT true NOT NULL, created_at timestamp with time zone DEFAULT now() NOT NULL, updated_at timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT clinical_note_templates_note_type_check CHECK (note_type IN ( 'anamnese', 'evolucao_sessao', 'plano_terapeutico', 'observacao_livre', 'resumo_caso' )), CONSTRAINT clinical_note_templates_scope_check CHECK ( -- Sistema: ambos NULL e is_system=true -- Tenant-wide: tenant_id presente, owner_id NULL -- Owner: ambos presentes (is_system = true AND tenant_id IS NULL AND owner_id IS NULL) OR (is_system = false AND tenant_id IS NOT NULL) ) ); COMMENT ON TABLE public.clinical_note_templates IS 'Templates de notas clínicas. Escopo: sistema (is_system, sem tenant), tenant-wide (tenant_id sem owner), owner (ambos).'; CREATE INDEX IF NOT EXISTS idx_clinical_note_templates_active ON public.clinical_note_templates (note_type) WHERE active = true; CREATE INDEX IF NOT EXISTS idx_clinical_note_templates_tenant ON public.clinical_note_templates (tenant_id, note_type) WHERE tenant_id IS NOT NULL AND active = true; CREATE INDEX IF NOT EXISTS idx_clinical_note_templates_owner ON public.clinical_note_templates (owner_id, note_type) WHERE owner_id IS NOT NULL AND active = true; -- ────────────────────────────────────────────────────────────────────────── -- 4. FK de clinical_notes.template_id (criada agora que templates existe) -- ────────────────────────────────────────────────────────────────────────── ALTER TABLE public.clinical_notes ADD CONSTRAINT clinical_notes_template_fkey FOREIGN KEY (template_id) REFERENCES public.clinical_note_templates(id) ON DELETE SET NULL; COMMIT;