Files
agenciapsilmno/database-novo/schema/04_tables/crm_conversas_whatsapp.sql
T
Leonardo dba595fd2d db: migration session_default_notes_field + schema regenerado
Migration 20260511000001 adiciona campo 'notes' (Observacao, textarea,
sort_order=30) como campo extra default no commitment determinado 'Sessao'.
Antes Sessao era a unica excecao entre os nativos — Leitura/Supervisao/
Aula/Analise ja tinham. Padroniza pra que a Observacao da sessao siga o
mesmo mecanismo de extra_fields dos outros, e o frontend remova a textarea
hardcoded do AgendaEventDialog (proximo commit).

Backfill: insere 'notes' em TODOS os commitments Sessao ja existentes
(idempotente). Forward-fix: substitui a funcao seed_determined_commitments
incluindo o bloco de Sessao + 'notes' pra novos tenants.

Schema regenerado via db.cjs schema-export pra refletir o estado pos-
migration. agenciapsi-db-dashboard.html regenerado pelo
generate-dashboard.cjs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:56:59 -03:00

253 lines
13 KiB
SQL

-- Tables: CRM Conversas (WhatsApp)
-- Gerado automaticamente em 2026-05-11T16:53:50.930Z
-- Total: 16
CREATE TABLE public.conversation_assignments (
tenant_id uuid NOT NULL,
thread_key text NOT NULL,
patient_id uuid,
contact_number text,
assigned_to uuid,
assigned_by uuid NOT NULL,
assigned_at timestamp with time zone DEFAULT now() NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL
);
CREATE TABLE public.conversation_autoreply_log (
id bigint NOT NULL,
tenant_id uuid NOT NULL,
thread_key text NOT NULL,
sent_at timestamp with time zone DEFAULT now() NOT NULL,
message_id uuid
);
CREATE TABLE public.conversation_autoreply_settings (
tenant_id uuid NOT NULL,
enabled boolean DEFAULT false NOT NULL,
message text DEFAULT 'Olá! Nosso horário de atendimento acabou. Retornaremos sua mensagem assim que possível. Obrigado!'::text NOT NULL,
cooldown_minutes integer DEFAULT 180 NOT NULL,
schedule_mode text DEFAULT 'agenda'::text NOT NULL,
business_hours jsonb DEFAULT '[]'::jsonb NOT NULL,
custom_window jsonb DEFAULT '[]'::jsonb NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT conversation_autoreply_settings_cooldown_minutes_check CHECK (((cooldown_minutes >= 0) AND (cooldown_minutes <= 43200))),
CONSTRAINT conversation_autoreply_settings_message_check CHECK (((length(message) > 0) AND (length(message) <= 2000))),
CONSTRAINT conversation_autoreply_settings_schedule_mode_check CHECK ((schedule_mode = ANY (ARRAY['agenda'::text, 'business_hours'::text, 'custom'::text])))
);
CREATE TABLE public.conversation_bot_sessions (
id uuid DEFAULT gen_random_uuid() NOT NULL,
tenant_id uuid NOT NULL,
thread_key text NOT NULL,
contact_number text,
current_step integer DEFAULT 0 NOT NULL,
collected_data jsonb DEFAULT '{}'::jsonb NOT NULL,
status text DEFAULT 'active'::text NOT NULL,
started_at timestamp with time zone DEFAULT now() NOT NULL,
last_advance_at timestamp with time zone DEFAULT now() NOT NULL,
completed_at timestamp with time zone,
abandoned_at timestamp with time zone,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT conversation_bot_sessions_status_check CHECK ((status = ANY (ARRAY['active'::text, 'completed'::text, 'abandoned_idle'::text, 'abandoned_manual'::text, 'opted_out'::text])))
);
CREATE TABLE public.conversation_bots (
tenant_id uuid NOT NULL,
enabled boolean DEFAULT false NOT NULL,
greeting_message text DEFAULT 'Olá! 👋 Sou o assistente virtual. Vou te fazer algumas perguntas rápidas pra a equipe preparar seu atendimento.'::text NOT NULL,
closing_message text DEFAULT 'Obrigado! Recebemos suas informações e a equipe entrará em contato em breve. 💙'::text NOT NULL,
steps jsonb DEFAULT jsonb_build_array(jsonb_build_object('prompt', 'Qual seu nome completo?', 'variable', 'nome_completo', 'type', 'text'), jsonb_build_object('prompt', 'O que te levou a buscar atendimento? Pode me contar brevemente.', 'variable', 'motivo', 'type', 'text'), jsonb_build_object('prompt', 'Prefere atendimento online ou presencial?', 'variable', 'modalidade', 'type', 'text'), jsonb_build_object('prompt', 'Qual o melhor dia e horário pra você? (Ex: terça à tarde)', 'variable', 'horario_preferido', 'type', 'text')) NOT NULL,
trigger_mode text DEFAULT 'new_contact'::text NOT NULL,
trigger_keywords text[] DEFAULT ARRAY[]::text[] NOT NULL,
idle_timeout_minutes integer DEFAULT 30 NOT NULL,
respect_optout 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 conversation_bots_idle_timeout_minutes_check CHECK (((idle_timeout_minutes >= 5) AND (idle_timeout_minutes <= 1440))),
CONSTRAINT conversation_bots_trigger_mode_check CHECK ((trigger_mode = ANY (ARRAY['new_contact'::text, 'all_unassigned'::text, 'keyword'::text])))
);
CREATE TABLE public.conversation_messages (
id bigint NOT NULL,
tenant_id uuid NOT NULL,
patient_id uuid,
channel text NOT NULL,
direction text NOT NULL,
from_number text,
to_number text,
body text,
media_url text,
media_mime text,
provider text NOT NULL,
provider_message_id text,
provider_raw jsonb,
kanban_status text DEFAULT 'awaiting_us'::text NOT NULL,
priority integer DEFAULT 0 NOT NULL,
read_at timestamp with time zone,
responded_at timestamp with time zone,
resolved_at timestamp with time zone,
received_at timestamp with time zone,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
delivered_at timestamp with time zone,
read_by_recipient_at timestamp with time zone,
delivery_status text,
CONSTRAINT conversation_messages_channel_check CHECK ((channel = ANY (ARRAY['whatsapp'::text, 'sms'::text, 'email'::text]))),
CONSTRAINT conversation_messages_delivery_status_check CHECK (((delivery_status IS NULL) OR (delivery_status = ANY (ARRAY['pending'::text, 'sent'::text, 'delivered'::text, 'read'::text, 'failed'::text])))),
CONSTRAINT conversation_messages_direction_check CHECK ((direction = ANY (ARRAY['inbound'::text, 'outbound'::text]))),
CONSTRAINT conversation_messages_kanban_status_check CHECK ((kanban_status = ANY (ARRAY['urgent'::text, 'awaiting_us'::text, 'awaiting_patient'::text, 'resolved'::text]))),
CONSTRAINT conversation_messages_provider_check CHECK ((provider = ANY (ARRAY['twilio'::text, 'evolution'::text, 'manual'::text])))
);
CREATE TABLE public.conversation_notes (
id uuid DEFAULT gen_random_uuid() NOT NULL,
tenant_id uuid NOT NULL,
thread_key text NOT NULL,
patient_id uuid,
contact_number text,
body text NOT NULL,
created_by uuid NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
deleted_at timestamp with time zone,
CONSTRAINT conversation_notes_body_check CHECK (((length(body) > 0) AND (length(body) <= 4000)))
);
CREATE TABLE public.conversation_optout_keywords (
id uuid DEFAULT gen_random_uuid() NOT NULL,
tenant_id uuid,
keyword text NOT NULL,
enabled boolean DEFAULT true NOT NULL,
is_system boolean DEFAULT false NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT conversation_optout_keywords_keyword_check CHECK (((length(keyword) > 0) AND (length(keyword) <= 100)))
);
CREATE TABLE public.conversation_optouts (
id uuid DEFAULT gen_random_uuid() NOT NULL,
tenant_id uuid NOT NULL,
phone text NOT NULL,
patient_id uuid,
source text DEFAULT 'keyword'::text NOT NULL,
keyword_matched text,
original_message text,
notes text,
blocked_by uuid,
opted_out_at timestamp with time zone DEFAULT now() NOT NULL,
opted_back_in_at timestamp with time zone,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT conversation_optouts_phone_check CHECK ((phone ~ '^\d{6,15}$'::text)),
CONSTRAINT conversation_optouts_source_check CHECK ((source = ANY (ARRAY['keyword'::text, 'manual'::text])))
);
CREATE TABLE public.conversation_sla_breaches (
id uuid DEFAULT gen_random_uuid() NOT NULL,
tenant_id uuid NOT NULL,
thread_key text NOT NULL,
assigned_to uuid,
last_inbound_at timestamp with time zone NOT NULL,
threshold_minutes_at_breach integer NOT NULL,
breached_at timestamp with time zone DEFAULT now() NOT NULL,
resolved_at timestamp with time zone,
resolved_by_message_id bigint,
notified_at timestamp with time zone,
notification_count integer DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL
);
CREATE TABLE public.conversation_sla_rules (
tenant_id uuid NOT NULL,
enabled boolean DEFAULT false NOT NULL,
threshold_minutes integer DEFAULT 60 NOT NULL,
respect_business_hours boolean DEFAULT true NOT NULL,
business_hours_start time without time zone DEFAULT '08:00:00'::time without time zone NOT NULL,
business_hours_end time without time zone DEFAULT '18:00:00'::time without time zone NOT NULL,
business_days smallint[] DEFAULT ARRAY[(1)::smallint, (2)::smallint, (3)::smallint, (4)::smallint, (5)::smallint] NOT NULL,
alert_scope text DEFAULT 'assigned_only'::text NOT NULL,
notify_admin_on_breach 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,
CONSTRAINT conversation_sla_rules_alert_scope_check CHECK ((alert_scope = ANY (ARRAY['assigned_only'::text, 'all'::text]))),
CONSTRAINT conversation_sla_rules_business_days_check CHECK (((array_length(business_days, 1) >= 1) AND (array_length(business_days, 1) <= 7))),
CONSTRAINT conversation_sla_rules_threshold_minutes_check CHECK (((threshold_minutes >= 1) AND (threshold_minutes <= 1440)))
);
CREATE TABLE public.conversation_tags (
id uuid DEFAULT gen_random_uuid() NOT NULL,
tenant_id uuid,
name text NOT NULL,
slug text NOT NULL,
color text DEFAULT '#6366f1'::text NOT NULL,
icon text,
"position" integer DEFAULT 100 NOT NULL,
is_system 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,
CONSTRAINT conversation_tags_color_check CHECK ((color ~ '^#[0-9a-fA-F]{6}$'::text)),
CONSTRAINT conversation_tags_name_check CHECK (((length(name) > 0) AND (length(name) <= 40))),
CONSTRAINT conversation_tags_slug_check CHECK ((slug ~ '^[a-z0-9_-]{1,40}$'::text))
);
CREATE TABLE public.conversation_thread_tags (
tenant_id uuid NOT NULL,
thread_key text NOT NULL,
tag_id uuid NOT NULL,
tagged_by uuid,
tagged_at timestamp with time zone DEFAULT now() NOT NULL
);
CREATE TABLE public.session_reminder_logs (
id bigint NOT NULL,
event_id uuid NOT NULL,
tenant_id uuid NOT NULL,
reminder_type text NOT NULL,
sent_at timestamp with time zone DEFAULT now() NOT NULL,
provider text,
skip_reason text,
to_phone text,
provider_message_id text,
conversation_message_id bigint,
CONSTRAINT session_reminder_logs_reminder_type_check CHECK ((reminder_type = ANY (ARRAY['24h'::text, '2h'::text, 'manual'::text])))
);
CREATE TABLE public.session_reminder_settings (
tenant_id uuid NOT NULL,
enabled boolean DEFAULT false NOT NULL,
send_24h boolean DEFAULT true NOT NULL,
send_2h boolean DEFAULT true NOT NULL,
template_24h text DEFAULT 'Oi {{nome_paciente}}! 👋 Lembrando da sua sessão amanhã, {{data_sessao}} às {{hora_sessao}}. Até lá!'::text NOT NULL,
template_2h text DEFAULT 'Oi {{nome_paciente}}! Sua sessão começa em 2 horas, às {{hora_sessao}}. Te espero! 😊'::text NOT NULL,
quiet_hours_enabled boolean DEFAULT true NOT NULL,
quiet_hours_start time without time zone DEFAULT '22:00:00'::time without time zone NOT NULL,
quiet_hours_end time without time zone DEFAULT '08:00:00'::time without time zone NOT NULL,
respect_opt_out 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 session_reminder_settings_template_24h_check CHECK (((length(template_24h) > 0) AND (length(template_24h) <= 2000))),
CONSTRAINT session_reminder_settings_template_2h_check CHECK (((length(template_2h) > 0) AND (length(template_2h) <= 2000)))
);
CREATE TABLE public.whatsapp_connection_incidents (
id uuid DEFAULT gen_random_uuid() NOT NULL,
tenant_id uuid NOT NULL,
channel_id uuid NOT NULL,
provider text NOT NULL,
kind text NOT NULL,
last_state text,
details jsonb,
started_at timestamp with time zone DEFAULT now() NOT NULL,
resolved_at timestamp with time zone,
duration_seconds integer,
notified_at timestamp with time zone,
notification_count integer DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT whatsapp_connection_incidents_kind_check CHECK ((kind = ANY (ARRAY['disconnected'::text, 'error'::text, 'qr_pending'::text, 'connecting'::text, 'unknown'::text]))),
CONSTRAINT whatsapp_connection_incidents_provider_check CHECK ((provider = ANY (ARRAY['evolution_api'::text, 'twilio'::text])))
);