--
-- PostgreSQL database dump
--

\restrict OYZjCTq2gLqELdyCChgzk1NjEGA2C8OKTRGUJKsllGft2WtbPKKeGe6fjZh3Yzj

-- Dumped from database version 17.6
-- Dumped by pg_dump version 17.6

SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET transaction_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;

--
-- Name: _realtime; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA _realtime;


--
-- Name: auth; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA auth;


--
-- Name: pg_cron; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS pg_cron WITH SCHEMA pg_catalog;


--
-- Name: EXTENSION pg_cron; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION pg_cron IS 'Job scheduler for PostgreSQL';


--
-- Name: extensions; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA extensions;


--
-- Name: graphql; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA graphql;


--
-- Name: graphql_public; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA graphql_public;


--
-- Name: pg_net; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS pg_net WITH SCHEMA extensions;


--
-- Name: EXTENSION pg_net; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION pg_net IS 'Async HTTP';


--
-- Name: pgbouncer; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA pgbouncer;


--
-- Name: realtime; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA realtime;


--
-- Name: storage; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA storage;


--
-- Name: supabase_functions; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA supabase_functions;


--
-- Name: vault; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA vault;


--
-- Name: btree_gist; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS btree_gist WITH SCHEMA public;


--
-- Name: EXTENSION btree_gist; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION btree_gist IS 'support for indexing common datatypes in GiST';


--
-- Name: citext; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA public;


--
-- Name: EXTENSION citext; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION citext IS 'data type for case-insensitive character strings';


--
-- Name: pg_graphql; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS pg_graphql WITH SCHEMA graphql;


--
-- Name: EXTENSION pg_graphql; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION pg_graphql IS 'pg_graphql: GraphQL support';


--
-- Name: pg_stat_statements; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA extensions;


--
-- Name: EXTENSION pg_stat_statements; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION pg_stat_statements IS 'track planning and execution statistics of all SQL statements executed';


--
-- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;


--
-- Name: EXTENSION pg_trgm; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION pg_trgm IS 'text similarity measurement and index searching based on trigrams';


--
-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA extensions;


--
-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';


--
-- Name: supabase_vault; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS supabase_vault WITH SCHEMA vault;


--
-- Name: EXTENSION supabase_vault; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION supabase_vault IS 'Supabase Vault Extension';


--
-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA extensions;


--
-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)';


--
-- Name: aal_level; Type: TYPE; Schema: auth; Owner: -
--

CREATE TYPE auth.aal_level AS ENUM (
    'aal1',
    'aal2',
    'aal3'
);


--
-- Name: code_challenge_method; Type: TYPE; Schema: auth; Owner: -
--

CREATE TYPE auth.code_challenge_method AS ENUM (
    's256',
    'plain'
);


--
-- Name: factor_status; Type: TYPE; Schema: auth; Owner: -
--

CREATE TYPE auth.factor_status AS ENUM (
    'unverified',
    'verified'
);


--
-- Name: factor_type; Type: TYPE; Schema: auth; Owner: -
--

CREATE TYPE auth.factor_type AS ENUM (
    'totp',
    'webauthn',
    'phone'
);


--
-- Name: oauth_authorization_status; Type: TYPE; Schema: auth; Owner: -
--

CREATE TYPE auth.oauth_authorization_status AS ENUM (
    'pending',
    'approved',
    'denied',
    'expired'
);


--
-- Name: oauth_client_type; Type: TYPE; Schema: auth; Owner: -
--

CREATE TYPE auth.oauth_client_type AS ENUM (
    'public',
    'confidential'
);


--
-- Name: oauth_registration_type; Type: TYPE; Schema: auth; Owner: -
--

CREATE TYPE auth.oauth_registration_type AS ENUM (
    'dynamic',
    'manual'
);


--
-- Name: oauth_response_type; Type: TYPE; Schema: auth; Owner: -
--

CREATE TYPE auth.oauth_response_type AS ENUM (
    'code'
);


--
-- Name: one_time_token_type; Type: TYPE; Schema: auth; Owner: -
--

CREATE TYPE auth.one_time_token_type AS ENUM (
    'confirmation_token',
    'reauthentication_token',
    'recovery_token',
    'email_change_token_new',
    'email_change_token_current',
    'phone_change_token'
);


--
-- Name: commitment_log_source; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.commitment_log_source AS ENUM (
    'manual',
    'auto'
);


--
-- Name: determined_field_type; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.determined_field_type AS ENUM (
    'text',
    'textarea',
    'number',
    'date',
    'select',
    'boolean'
);


--
-- Name: financial_record_type; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.financial_record_type AS ENUM (
    'receita',
    'despesa'
);


--
-- Name: recurrence_exception_type; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.recurrence_exception_type AS ENUM (
    'cancel_session',
    'reschedule_session',
    'patient_missed',
    'therapist_canceled',
    'holiday_block'
);


--
-- Name: recurrence_type; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.recurrence_type AS ENUM (
    'weekly',
    'biweekly',
    'monthly',
    'yearly',
    'custom_weekdays'
);


--
-- Name: status_agenda_serie; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.status_agenda_serie AS ENUM (
    'ativo',
    'pausado',
    'cancelado'
);


--
-- Name: status_evento_agenda; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.status_evento_agenda AS ENUM (
    'agendado',
    'realizado',
    'faltou',
    'cancelado',
    'remarcar',
    'remarcado',
    'confirmado'
);


--
-- Name: status_excecao_agenda; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.status_excecao_agenda AS ENUM (
    'pendente',
    'ativo',
    'arquivado'
);


--
-- Name: tipo_evento_agenda; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.tipo_evento_agenda AS ENUM (
    'sessao',
    'bloqueio'
);


--
-- Name: tipo_excecao_agenda; Type: TYPE; Schema: public; Owner: -
--

CREATE TYPE public.tipo_excecao_agenda AS ENUM (
    'bloqueio',
    'horario_extra'
);


--
-- Name: action; Type: TYPE; Schema: realtime; Owner: -
--

CREATE TYPE realtime.action AS ENUM (
    'INSERT',
    'UPDATE',
    'DELETE',
    'TRUNCATE',
    'ERROR'
);


--
-- Name: equality_op; Type: TYPE; Schema: realtime; Owner: -
--

CREATE TYPE realtime.equality_op AS ENUM (
    'eq',
    'neq',
    'lt',
    'lte',
    'gt',
    'gte',
    'in'
);


--
-- Name: user_defined_filter; Type: TYPE; Schema: realtime; Owner: -
--

CREATE TYPE realtime.user_defined_filter AS (
	column_name text,
	op realtime.equality_op,
	value text
);


--
-- Name: wal_column; Type: TYPE; Schema: realtime; Owner: -
--

CREATE TYPE realtime.wal_column AS (
	name text,
	type_name text,
	type_oid oid,
	value jsonb,
	is_pkey boolean,
	is_selectable boolean
);


--
-- Name: wal_rls; Type: TYPE; Schema: realtime; Owner: -
--

CREATE TYPE realtime.wal_rls AS (
	wal jsonb,
	is_rls_enabled boolean,
	subscription_ids uuid[],
	errors text[]
);


--
-- Name: buckettype; Type: TYPE; Schema: storage; Owner: -
--

CREATE TYPE storage.buckettype AS ENUM (
    'STANDARD',
    'ANALYTICS',
    'VECTOR'
);


--
-- Name: email(); Type: FUNCTION; Schema: auth; Owner: -
--

CREATE FUNCTION auth.email() RETURNS text
    LANGUAGE sql STABLE
    AS $$
  select 
  coalesce(
    nullif(current_setting('request.jwt.claim.email', true), ''),
    (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'email')
  )::text
$$;


--
-- Name: FUNCTION email(); Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON FUNCTION auth.email() IS 'Deprecated. Use auth.jwt() -> ''email'' instead.';


--
-- Name: jwt(); Type: FUNCTION; Schema: auth; Owner: -
--

CREATE FUNCTION auth.jwt() RETURNS jsonb
    LANGUAGE sql STABLE
    AS $$
  select 
    coalesce(
        nullif(current_setting('request.jwt.claim', true), ''),
        nullif(current_setting('request.jwt.claims', true), '')
    )::jsonb
$$;


--
-- Name: role(); Type: FUNCTION; Schema: auth; Owner: -
--

CREATE FUNCTION auth.role() RETURNS text
    LANGUAGE sql STABLE
    AS $$
  select 
  coalesce(
    nullif(current_setting('request.jwt.claim.role', true), ''),
    (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role')
  )::text
$$;


--
-- Name: FUNCTION role(); Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON FUNCTION auth.role() IS 'Deprecated. Use auth.jwt() -> ''role'' instead.';


--
-- Name: uid(); Type: FUNCTION; Schema: auth; Owner: -
--

CREATE FUNCTION auth.uid() RETURNS uuid
    LANGUAGE sql STABLE
    AS $$
  select 
  coalesce(
    nullif(current_setting('request.jwt.claim.sub', true), ''),
    (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')
  )::uuid
$$;


--
-- Name: FUNCTION uid(); Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON FUNCTION auth.uid() IS 'Deprecated. Use auth.jwt() -> ''sub'' instead.';


--
-- Name: grant_pg_cron_access(); Type: FUNCTION; Schema: extensions; Owner: -
--

CREATE FUNCTION extensions.grant_pg_cron_access() RETURNS event_trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF EXISTS (
    SELECT
    FROM pg_event_trigger_ddl_commands() AS ev
    JOIN pg_extension AS ext
    ON ev.objid = ext.oid
    WHERE ext.extname = 'pg_cron'
  )
  THEN
    grant usage on schema cron to postgres with grant option;

    alter default privileges in schema cron grant all on tables to postgres with grant option;
    alter default privileges in schema cron grant all on functions to postgres with grant option;
    alter default privileges in schema cron grant all on sequences to postgres with grant option;

    alter default privileges for user supabase_admin in schema cron grant all
        on sequences to postgres with grant option;
    alter default privileges for user supabase_admin in schema cron grant all
        on tables to postgres with grant option;
    alter default privileges for user supabase_admin in schema cron grant all
        on functions to postgres with grant option;

    grant all privileges on all tables in schema cron to postgres with grant option;
    revoke all on table cron.job from postgres;
    grant select on table cron.job to postgres with grant option;
  END IF;
END;
$$;


--
-- Name: FUNCTION grant_pg_cron_access(); Type: COMMENT; Schema: extensions; Owner: -
--

COMMENT ON FUNCTION extensions.grant_pg_cron_access() IS 'Grants access to pg_cron';


--
-- Name: grant_pg_graphql_access(); Type: FUNCTION; Schema: extensions; Owner: -
--

CREATE FUNCTION extensions.grant_pg_graphql_access() RETURNS event_trigger
    LANGUAGE plpgsql
    AS $_$
DECLARE
    func_is_graphql_resolve bool;
BEGIN
    func_is_graphql_resolve = (
        SELECT n.proname = 'resolve'
        FROM pg_event_trigger_ddl_commands() AS ev
        LEFT JOIN pg_catalog.pg_proc AS n
        ON ev.objid = n.oid
    );

    IF func_is_graphql_resolve
    THEN
        -- Update public wrapper to pass all arguments through to the pg_graphql resolve func
        DROP FUNCTION IF EXISTS graphql_public.graphql;
        create or replace function graphql_public.graphql(
            "operationName" text default null,
            query text default null,
            variables jsonb default null,
            extensions jsonb default null
        )
            returns jsonb
            language sql
        as $$
            select graphql.resolve(
                query := query,
                variables := coalesce(variables, '{}'),
                "operationName" := "operationName",
                extensions := extensions
            );
        $$;

        -- This hook executes when `graphql.resolve` is created. That is not necessarily the last
        -- function in the extension so we need to grant permissions on existing entities AND
        -- update default permissions to any others that are created after `graphql.resolve`
        grant usage on schema graphql to postgres, anon, authenticated, service_role;
        grant select on all tables in schema graphql to postgres, anon, authenticated, service_role;
        grant execute on all functions in schema graphql to postgres, anon, authenticated, service_role;
        grant all on all sequences in schema graphql to postgres, anon, authenticated, service_role;
        alter default privileges in schema graphql grant all on tables to postgres, anon, authenticated, service_role;
        alter default privileges in schema graphql grant all on functions to postgres, anon, authenticated, service_role;
        alter default privileges in schema graphql grant all on sequences to postgres, anon, authenticated, service_role;

        -- Allow postgres role to allow granting usage on graphql and graphql_public schemas to custom roles
        grant usage on schema graphql_public to postgres with grant option;
        grant usage on schema graphql to postgres with grant option;
    END IF;

END;
$_$;


--
-- Name: FUNCTION grant_pg_graphql_access(); Type: COMMENT; Schema: extensions; Owner: -
--

COMMENT ON FUNCTION extensions.grant_pg_graphql_access() IS 'Grants access to pg_graphql';


--
-- Name: grant_pg_net_access(); Type: FUNCTION; Schema: extensions; Owner: -
--

CREATE FUNCTION extensions.grant_pg_net_access() RETURNS event_trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF EXISTS (
    SELECT 1
    FROM pg_event_trigger_ddl_commands() AS ev
    JOIN pg_extension AS ext
    ON ev.objid = ext.oid
    WHERE ext.extname = 'pg_net'
  )
  THEN
    GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;

    ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
    ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;

    ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
    ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;

    REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
    REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;

    GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
    GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
  END IF;
END;
$$;


--
-- Name: FUNCTION grant_pg_net_access(); Type: COMMENT; Schema: extensions; Owner: -
--

COMMENT ON FUNCTION extensions.grant_pg_net_access() IS 'Grants access to pg_net';


--
-- Name: pgrst_ddl_watch(); Type: FUNCTION; Schema: extensions; Owner: -
--

CREATE FUNCTION extensions.pgrst_ddl_watch() RETURNS event_trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
  cmd record;
BEGIN
  FOR cmd IN SELECT * FROM pg_event_trigger_ddl_commands()
  LOOP
    IF cmd.command_tag IN (
      'CREATE SCHEMA', 'ALTER SCHEMA'
    , 'CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO', 'ALTER TABLE'
    , 'CREATE FOREIGN TABLE', 'ALTER FOREIGN TABLE'
    , 'CREATE VIEW', 'ALTER VIEW'
    , 'CREATE MATERIALIZED VIEW', 'ALTER MATERIALIZED VIEW'
    , 'CREATE FUNCTION', 'ALTER FUNCTION'
    , 'CREATE TRIGGER'
    , 'CREATE TYPE', 'ALTER TYPE'
    , 'CREATE RULE'
    , 'COMMENT'
    )
    -- don't notify in case of CREATE TEMP table or other objects created on pg_temp
    AND cmd.schema_name is distinct from 'pg_temp'
    THEN
      NOTIFY pgrst, 'reload schema';
    END IF;
  END LOOP;
END; $$;


--
-- Name: pgrst_drop_watch(); Type: FUNCTION; Schema: extensions; Owner: -
--

CREATE FUNCTION extensions.pgrst_drop_watch() RETURNS event_trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
  obj record;
BEGIN
  FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
  LOOP
    IF obj.object_type IN (
      'schema'
    , 'table'
    , 'foreign table'
    , 'view'
    , 'materialized view'
    , 'function'
    , 'trigger'
    , 'type'
    , 'rule'
    )
    AND obj.is_temporary IS false -- no pg_temp objects
    THEN
      NOTIFY pgrst, 'reload schema';
    END IF;
  END LOOP;
END; $$;


--
-- Name: set_graphql_placeholder(); Type: FUNCTION; Schema: extensions; Owner: -
--

CREATE FUNCTION extensions.set_graphql_placeholder() RETURNS event_trigger
    LANGUAGE plpgsql
    AS $_$
    DECLARE
    graphql_is_dropped bool;
    BEGIN
    graphql_is_dropped = (
        SELECT ev.schema_name = 'graphql_public'
        FROM pg_event_trigger_dropped_objects() AS ev
        WHERE ev.schema_name = 'graphql_public'
    );

    IF graphql_is_dropped
    THEN
        create or replace function graphql_public.graphql(
            "operationName" text default null,
            query text default null,
            variables jsonb default null,
            extensions jsonb default null
        )
            returns jsonb
            language plpgsql
        as $$
            DECLARE
                server_version float;
            BEGIN
                server_version = (SELECT (SPLIT_PART((select version()), ' ', 2))::float);

                IF server_version >= 14 THEN
                    RETURN jsonb_build_object(
                        'errors', jsonb_build_array(
                            jsonb_build_object(
                                'message', 'pg_graphql extension is not enabled.'
                            )
                        )
                    );
                ELSE
                    RETURN jsonb_build_object(
                        'errors', jsonb_build_array(
                            jsonb_build_object(
                                'message', 'pg_graphql is only available on projects running Postgres 14 onwards.'
                            )
                        )
                    );
                END IF;
            END;
        $$;
    END IF;

    END;
$_$;


--
-- Name: FUNCTION set_graphql_placeholder(); Type: COMMENT; Schema: extensions; Owner: -
--

COMMENT ON FUNCTION extensions.set_graphql_placeholder() IS 'Reintroduces placeholder function for graphql_public.graphql';


--
-- Name: get_auth(text); Type: FUNCTION; Schema: pgbouncer; Owner: -
--

CREATE FUNCTION pgbouncer.get_auth(p_usename text) RETURNS TABLE(username text, password text)
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO ''
    AS $_$
begin
    raise debug 'PgBouncer auth request: %', p_usename;

    return query
    select 
        rolname::text, 
        case when rolvaliduntil < now() 
            then null 
            else rolpassword::text 
        end 
    from pg_authid 
    where rolname=$1 and rolcanlogin;
end;
$_$;


--
-- Name: __rls_ping(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.__rls_ping() RETURNS text
    LANGUAGE sql STABLE
    AS $$
  select 'ok'::text;
$$;


--
-- Name: _first_response_runs(uuid, timestamp with time zone, timestamp with time zone); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public._first_response_runs(p_tenant_id uuid, p_from timestamp with time zone, p_to timestamp with time zone) RETURNS TABLE(thread_key text, inbound_started_at timestamp with time zone, responded_at timestamp with time zone, response_seconds integer, responder_id uuid)
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
    WITH msgs AS (
        SELECT
            m.id,
            m.tenant_id,
            m.direction,
            m.created_at,
            m.patient_id,
            m.from_number,
            m.to_number,
            -- mesma logica da view conversation_threads
            COALESCE(
                m.patient_id::text,
                'anon:' || COALESCE(
                    CASE WHEN m.direction = 'inbound' THEN m.from_number ELSE m.to_number END,
                    'unknown'
                )
            ) AS tk
        FROM public.conversation_messages m
        WHERE m.tenant_id = p_tenant_id
          AND m.direction IN ('inbound', 'outbound')
          AND m.created_at >= p_from
          AND m.created_at <= p_to
    ),
    with_prev AS (
        SELECT *,
               LAG(direction) OVER (PARTITION BY tenant_id, tk ORDER BY created_at, id) AS prev_direction
        FROM msgs
    ),
    run_starts AS (
        -- Primeira mensagem de cada "run inbound"
        SELECT tk, tenant_id, created_at AS inbound_started_at
        FROM with_prev
        WHERE direction = 'inbound'
          AND (prev_direction IS NULL OR prev_direction = 'outbound')
    )
    SELECT
        r.tk AS thread_key,
        r.inbound_started_at,
        o.created_at AS responded_at,
        EXTRACT(EPOCH FROM (o.created_at - r.inbound_started_at))::INT AS response_seconds,
        -- Quem respondeu: pega o assigned_to atual da thread (snapshot aproximado)
        a.assigned_to AS responder_id
    FROM run_starts r
    LEFT JOIN LATERAL (
        SELECT created_at
        FROM public.conversation_messages m2
        WHERE m2.tenant_id = r.tenant_id
          AND COALESCE(m2.patient_id::text, 'anon:' || COALESCE(m2.to_number, m2.from_number, 'unknown')) = r.tk
          AND m2.direction = 'outbound'
          AND m2.created_at > r.inbound_started_at
        ORDER BY m2.created_at
        LIMIT 1
    ) o ON true
    LEFT JOIN public.conversation_assignments a
        ON a.tenant_id = r.tenant_id AND a.thread_key = r.tk
    WHERE o.created_at IS NOT NULL;   -- so runs que foram respondidos
$$;


SET default_tablespace = '';

SET default_table_access_method = heap;

--
-- Name: subscriptions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.subscriptions (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    user_id uuid,
    plan_id uuid NOT NULL,
    status text DEFAULT 'active'::text NOT NULL,
    current_period_start timestamp with time zone,
    current_period_end timestamp with time zone,
    cancel_at_period_end boolean DEFAULT false NOT NULL,
    provider text DEFAULT 'manual'::text NOT NULL,
    provider_customer_id text,
    provider_subscription_id text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    tenant_id uuid,
    plan_key text,
    "interval" text,
    source text DEFAULT 'manual'::text NOT NULL,
    started_at timestamp with time zone DEFAULT now() NOT NULL,
    canceled_at timestamp with time zone,
    activated_at timestamp with time zone,
    past_due_since timestamp with time zone,
    suspended_at timestamp with time zone,
    suspended_reason text,
    cancelled_at timestamp with time zone,
    cancel_reason text,
    expired_at timestamp with time zone,
    CONSTRAINT subscriptions_interval_check CHECK (("interval" = ANY (ARRAY['month'::text, 'year'::text]))),
    CONSTRAINT subscriptions_owner_xor CHECK ((((tenant_id IS NOT NULL) AND (user_id IS NULL)) OR ((tenant_id IS NULL) AND (user_id IS NOT NULL)))),
    CONSTRAINT subscriptions_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'active'::text, 'past_due'::text, 'suspended'::text, 'cancelled'::text, 'expired'::text])))
);


--
-- Name: activate_subscription_from_intent(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.activate_subscription_from_intent(p_intent_id uuid) RETURNS public.subscriptions
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_intent record;
  v_sub public.subscriptions;
  v_days int;
  v_user_id uuid;
  v_plan_id uuid;
  v_target text;
begin
  -- l?? pela VIEW unificada
  select * into v_intent
  from public.subscription_intents
  where id = p_intent_id;

  if not found then
    raise exception 'Intent n??o encontrado: %', p_intent_id;
  end if;

  if v_intent.status <> 'paid' then
    raise exception 'Intent precisa estar paid para ativar assinatura';
  end if;

  -- resolve target e plan_id via plans.key
  select p.id, p.target
    into v_plan_id, v_target
  from public.plans p
  where p.key = v_intent.plan_key
  limit 1;

  if v_plan_id is null then
    raise exception 'Plano n??o encontrado em plans.key = %', v_intent.plan_key;
  end if;

  v_target := lower(coalesce(v_target, ''));

  -- ??? supervisor adicionado
  if v_target not in ('clinic', 'therapist', 'supervisor') then
    raise exception 'Target inv??lido em plans.target: %', v_target;
  end if;

  -- regra por target
  if v_target = 'clinic' then
    if v_intent.tenant_id is null then
      raise exception 'Intent sem tenant_id';
    end if;
  else
    -- therapist ou supervisor: vinculado ao user
    v_user_id := v_intent.user_id;
    if v_user_id is null then
      v_user_id := v_intent.created_by_user_id;
    end if;
  end if;

  if v_target in ('therapist', 'supervisor') and v_user_id is null then
    raise exception 'N??o foi poss??vel determinar user_id para assinatura %.', v_target;
  end if;

  -- cancela assinatura ativa anterior
  if v_target = 'clinic' then
    update public.subscriptions
    set status = 'cancelled',
        cancelled_at = now()
    where tenant_id = v_intent.tenant_id
      and plan_id = v_plan_id
      and status = 'active';
  else
    -- therapist ou supervisor
    update public.subscriptions
    set status = 'cancelled',
        cancelled_at = now()
    where user_id = v_user_id
      and plan_id = v_plan_id
      and status = 'active'
      and tenant_id is null;
  end if;

  -- dura????o do plano (30 dias para mensal)
  v_days := case
    when lower(coalesce(v_intent.interval, 'month')) = 'year' then 365
    else 30
  end;

  -- cria nova assinatura
  insert into public.subscriptions (
    user_id,
    plan_id,
    status,
    started_at,
    expires_at,
    cancelled_at,
    activated_at,
    tenant_id,
    plan_key,
    interval,
    source,
    created_at,
    updated_at
  )
  values (
    case when v_target = 'clinic' then null else v_user_id end,
    v_plan_id,
    'active',
    now(),
    now() + make_interval(days => v_days),
    null,
    now(),
    case when v_target = 'clinic' then v_intent.tenant_id else null end,
    v_intent.plan_key,
    v_intent.interval,
    'manual',
    now(),
    now()
  )
  returning * into v_sub;

  -- grava v??nculo intent ??? subscription
  if v_target = 'clinic' then
    update public.subscription_intents_tenant
    set subscription_id = v_sub.id
    where id = p_intent_id;
  else
    update public.subscription_intents_personal
    set subscription_id = v_sub.id
    where id = p_intent_id;
  end if;

  return v_sub;
end;
$$;


--
-- Name: add_whatsapp_credits(uuid, integer, text, uuid, uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.add_whatsapp_credits(p_tenant_id uuid, p_amount integer, p_kind text, p_purchase_id uuid DEFAULT NULL::uuid, p_admin_id uuid DEFAULT NULL::uuid, p_note text DEFAULT NULL::text) RETURNS integer
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_new_balance INT;
BEGIN
    IF p_amount <= 0 THEN
        RAISE EXCEPTION 'amount must be positive';
    END IF;
    IF p_kind NOT IN ('purchase', 'topup_manual', 'refund', 'adjustment') THEN
        RAISE EXCEPTION 'invalid kind for credit: %', p_kind;
    END IF;

    INSERT INTO public.whatsapp_credits_balance (tenant_id, balance, lifetime_purchased)
    VALUES (p_tenant_id, p_amount, CASE WHEN p_kind IN ('purchase', 'topup_manual') THEN p_amount ELSE 0 END)
    ON CONFLICT (tenant_id) DO UPDATE SET
        balance = whatsapp_credits_balance.balance + EXCLUDED.balance,
        lifetime_purchased = whatsapp_credits_balance.lifetime_purchased
            + CASE WHEN p_kind IN ('purchase', 'topup_manual') THEN p_amount ELSE 0 END,
        low_balance_alerted_at = NULL  -- reset alerta quando recebe creditos
    RETURNING balance INTO v_new_balance;

    INSERT INTO public.whatsapp_credits_transactions
        (tenant_id, kind, amount, balance_after, purchase_id, admin_id, note)
    VALUES
        (p_tenant_id, p_kind, p_amount, v_new_balance, p_purchase_id, p_admin_id, p_note);

    RETURN v_new_balance;
END;
$$;


--
-- Name: admin_adjust_whatsapp_credits(uuid, integer, uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.admin_adjust_whatsapp_credits(p_tenant_id uuid, p_amount integer, p_admin_id uuid, p_note text DEFAULT NULL::text) RETURNS integer
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_new_balance INT;
    v_current_balance INT;
    v_topup_net INT;
    v_usage_total INT;
    v_removable INT;
    v_clean_note TEXT;
BEGIN
    IF NOT public.is_saas_admin() THEN
        RAISE EXCEPTION 'permission_denied';
    END IF;

    IF p_tenant_id IS NULL THEN
        RAISE EXCEPTION 'tenant_required';
    END IF;

    IF p_amount IS NULL OR p_amount = 0 THEN
        RAISE EXCEPTION 'amount_required';
    END IF;

    IF ABS(p_amount) > 1000 THEN
        RAISE EXCEPTION 'amount_exceeds_limit_1000';
    END IF;

    IF p_admin_id IS NULL THEN
        RAISE EXCEPTION 'admin_id_required';
    END IF;

    v_clean_note := NULLIF(TRIM(COALESCE(p_note, '')), '');
    IF v_clean_note IS NOT NULL THEN
        v_clean_note := LEFT(v_clean_note, 500);
    END IF;

    IF p_amount > 0 THEN
        -- ADICIONAR
        INSERT INTO public.whatsapp_credits_balance (tenant_id, balance)
        VALUES (p_tenant_id, p_amount)
        ON CONFLICT (tenant_id) DO UPDATE SET
            balance = whatsapp_credits_balance.balance + EXCLUDED.balance,
            low_balance_alerted_at = NULL
        RETURNING balance INTO v_new_balance;

    ELSE
        -- REMOVER (amount < 0)
        SELECT balance INTO v_current_balance
            FROM public.whatsapp_credits_balance
            WHERE tenant_id = p_tenant_id
            FOR UPDATE;

        IF NOT FOUND THEN
            RAISE EXCEPTION 'tenant_has_no_balance';
        END IF;

        SELECT COALESCE(SUM(amount), 0) INTO v_topup_net
            FROM public.whatsapp_credits_transactions
            WHERE tenant_id = p_tenant_id
              AND kind IN ('topup_manual', 'adjustment', 'refund');

        SELECT COALESCE(ABS(SUM(amount)), 0) INTO v_usage_total
            FROM public.whatsapp_credits_transactions
            WHERE tenant_id = p_tenant_id
              AND kind = 'usage';

        v_removable := GREATEST(0, v_topup_net - v_usage_total);
        v_removable := LEAST(v_removable, v_current_balance);

        IF ABS(p_amount) > v_removable THEN
            RAISE EXCEPTION 'cannot_remove_beyond_removable: max=%', v_removable;
        END IF;

        UPDATE public.whatsapp_credits_balance
            SET balance = balance + p_amount    -- p_amount ja e negativo
            WHERE tenant_id = p_tenant_id
            RETURNING balance INTO v_new_balance;
    END IF;

    INSERT INTO public.whatsapp_credits_transactions
        (tenant_id, kind, amount, balance_after, admin_id, note)
    VALUES
        (p_tenant_id, 'adjustment', p_amount, v_new_balance, p_admin_id, v_clean_note);

    RETURN v_new_balance;
END;
$$;


--
-- Name: FUNCTION admin_adjust_whatsapp_credits(p_tenant_id uuid, p_amount integer, p_admin_id uuid, p_note text); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.admin_adjust_whatsapp_credits(p_tenant_id uuid, p_amount integer, p_admin_id uuid, p_note text) IS 'Ajuste manual SaaS admin (+/-). |amount|<=1000 por operacao. Remocao so afeta pool cortesia — purchases sao protegidas. Requer is_saas_admin().';


--
-- Name: admin_credit_addon(uuid, text, integer, uuid, text, text, integer); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.admin_credit_addon(p_tenant_id uuid, p_addon_type text, p_amount integer, p_product_id uuid DEFAULT NULL::uuid, p_description text DEFAULT 'Cr??dito manual'::text, p_payment_method text DEFAULT 'manual'::text, p_price_cents integer DEFAULT 0) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_credit   addon_credits%ROWTYPE;
  v_balance_before INTEGER;
  v_balance_after  INTEGER;
  v_tx_id    UUID;
BEGIN
  -- Upsert addon_credits
  INSERT INTO addon_credits (tenant_id, addon_type, balance, total_purchased)
  VALUES (p_tenant_id, p_addon_type, 0, 0)
  ON CONFLICT (tenant_id, addon_type) DO NOTHING;

  -- Lock e leitura
  SELECT * INTO v_credit
  FROM addon_credits
  WHERE tenant_id = p_tenant_id AND addon_type = p_addon_type
  FOR UPDATE;

  v_balance_before := v_credit.balance;
  v_balance_after  := v_credit.balance + p_amount;

  -- Atualiza saldo
  UPDATE addon_credits
  SET balance         = v_balance_after,
      total_purchased = total_purchased + p_amount,
      low_balance_notified = CASE WHEN v_balance_after > COALESCE(low_balance_threshold, 10) THEN false ELSE low_balance_notified END,
      updated_at      = now()
  WHERE id = v_credit.id;

  -- Registra transa????o
  INSERT INTO addon_transactions (
    tenant_id, addon_type, type, amount,
    balance_before, balance_after,
    product_id, description,
    admin_user_id, payment_method, price_cents
  ) VALUES (
    p_tenant_id, p_addon_type, 'purchase', p_amount,
    v_balance_before, v_balance_after,
    p_product_id, p_description,
    auth.uid(), p_payment_method, p_price_cents
  )
  RETURNING id INTO v_tx_id;

  RETURN jsonb_build_object(
    'success', true,
    'transaction_id', v_tx_id,
    'balance_before', v_balance_before,
    'balance_after', v_balance_after
  );
END;
$$;


--
-- Name: FUNCTION admin_credit_addon(p_tenant_id uuid, p_addon_type text, p_amount integer, p_product_id uuid, p_description text, p_payment_method text, p_price_cents integer); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.admin_credit_addon(p_tenant_id uuid, p_addon_type text, p_amount integer, p_product_id uuid, p_description text, p_payment_method text, p_price_cents integer) IS 'Admin adiciona cr??ditos de add-on a um tenant. Cria registro se n??o existir.';


--
-- Name: admin_delete_email_template_global(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.admin_delete_email_template_global(p_id uuid) RETURNS boolean
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
  DELETE FROM public.email_templates_global WHERE id = p_id;
  IF NOT FOUND THEN
    RAISE EXCEPTION 'Template com id % n??o encontrado', p_id;
  END IF;
  RETURN true;
END;
$$;


--
-- Name: admin_fix_plan_target(text, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.admin_fix_plan_target(p_plan_key text, p_new_target text) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_plan_id uuid;
begin
  -- (opcional) restringe targets v??lidos
  if p_new_target not in ('clinic','therapist') then
    raise exception 'Target inv??lido: %', p_new_target using errcode='P0001';
  end if;

  -- trava o plano
  select id into v_plan_id
  from public.plans
  where key = p_plan_key
  for update;

  if v_plan_id is null then
    raise exception 'Plano n??o encontrado: %', p_plan_key using errcode='P0001';
  end if;

  -- seguran??a: n??o mexer se existe subscription
  if exists (select 1 from public.subscriptions s where s.plan_id = v_plan_id) then
    raise exception 'Plano % possui subscriptions. Migra????o bloqueada.', p_plan_key using errcode='P0001';
  end if;

  -- liga bypass SOMENTE nesta transa????o
  perform set_config('app.plan_migration_bypass', '1', true);

  update public.plans
  set target = p_new_target
  where id = v_plan_id;

end
$$;


--
-- Name: admin_upsert_email_template_global(uuid, text, text, text, text, text, text, boolean, jsonb); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.admin_upsert_email_template_global(p_id uuid DEFAULT NULL::uuid, p_key text DEFAULT NULL::text, p_domain text DEFAULT NULL::text, p_channel text DEFAULT 'email'::text, p_subject text DEFAULT NULL::text, p_body_html text DEFAULT NULL::text, p_body_text text DEFAULT NULL::text, p_is_active boolean DEFAULT true, p_variables jsonb DEFAULT '{}'::jsonb) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_result jsonb;
  v_id uuid;
BEGIN
  -- UPDATE existente
  IF p_id IS NOT NULL THEN
    UPDATE public.email_templates_global
    SET
      subject   = COALESCE(p_subject, subject),
      body_html = COALESCE(p_body_html, body_html),
      body_text = p_body_text,
      is_active = p_is_active,
      variables = COALESCE(p_variables, variables),
      version   = version + 1
    WHERE id = p_id
    RETURNING to_jsonb(email_templates_global.*) INTO v_result;

    IF v_result IS NULL THEN
      RAISE EXCEPTION 'Template com id % n??o encontrado', p_id;
    END IF;

    RETURN v_result;
  END IF;

  -- INSERT novo
  IF p_key IS NULL OR p_domain IS NULL OR p_subject IS NULL OR p_body_html IS NULL THEN
    RAISE EXCEPTION 'key, domain, subject e body_html s??o obrigat??rios para novo template';
  END IF;

  INSERT INTO public.email_templates_global (key, domain, channel, subject, body_html, body_text, is_active, variables)
  VALUES (p_key, p_domain, p_channel, p_subject, p_body_html, p_body_text, p_is_active, p_variables)
  RETURNING to_jsonb(email_templates_global.*) INTO v_result;

  RETURN v_result;
END;
$$;


--
-- Name: agenda_cfg_sync(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.agenda_cfg_sync() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  if new.agenda_view_mode = 'custom' then
    new.usar_horario_admin_custom := true;
    new.admin_inicio_visualizacao := new.agenda_custom_start;
    new.admin_fim_visualizacao := new.agenda_custom_end;
  else
    new.usar_horario_admin_custom := false;
  end if;

  return new;
end;
$$;


--
-- Name: agendador_dias_disponiveis(text, integer, integer); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.agendador_dias_disponiveis(p_slug text, p_ano integer, p_mes integer) RETURNS TABLE(data date, tem_slots boolean)
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_owner_id     uuid;
  v_antecedencia int;
  v_agora        timestamptz;
  v_data         date;
  v_data_inicio  date;
  v_data_fim     date;
  v_db_dow       int;
  v_tem_slot     boolean;
  v_bloqueado    boolean;
BEGIN
  SELECT c.owner_id, c.antecedencia_minima_horas
  INTO v_owner_id, v_antecedencia
  FROM public.agendador_configuracoes c
  WHERE c.link_slug = p_slug AND c.ativo = true
  LIMIT 1;

  IF v_owner_id IS NULL THEN RETURN; END IF;

  v_agora       := now();
  v_data_inicio := make_date(p_ano, p_mes, 1);
  v_data_fim    := (v_data_inicio + interval '1 month' - interval '1 day')::date;

  v_data := v_data_inicio;
  WHILE v_data <= v_data_fim LOOP
    v_db_dow := extract(dow from v_data::timestamp)::int;

    -- ?????? Dia inteiro bloqueado? (agenda_bloqueios) ???????????????????????????????????????????????????????????????????????????
    SELECT EXISTS (
      SELECT 1 FROM public.agenda_bloqueios b
      WHERE b.owner_id     = v_owner_id
        AND b.data_inicio <= v_data
        AND COALESCE(b.data_fim, v_data) >= v_data
        AND b.hora_inicio IS NULL   -- bloqueio de dia inteiro
        AND (
          (NOT b.recorrente)
          OR (b.recorrente AND b.dia_semana = v_db_dow)
        )
    ) INTO v_bloqueado;

    IF v_bloqueado THEN
      v_data := v_data + 1;
      CONTINUE;
    END IF;

    -- ?????? Tem slots dispon??veis no dia? ???????????????????????????????????????????????????????????????????????????????????????????????????????????????
    SELECT EXISTS (
      SELECT 1 FROM public.agenda_online_slots s
      WHERE s.owner_id = v_owner_id
        AND s.weekday  = v_db_dow
        AND s.enabled  = true
        AND (v_data::text || ' ' || s.time::text)::timestamp
              AT TIME ZONE 'America/Sao_Paulo'
            >= v_agora + (v_antecedencia || ' hours')::interval
    ) INTO v_tem_slot;

    IF v_tem_slot THEN
      data      := v_data;
      tem_slots := true;
      RETURN NEXT;
    END IF;

    v_data := v_data + 1;
  END LOOP;
END;
$$;


--
-- Name: agendador_gerar_slug(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.agendador_gerar_slug() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
  v_slug text;
  v_exists boolean;
BEGIN
  -- s?? gera se ativou e n??o tem slug ainda
  IF NEW.ativo = true AND (NEW.link_slug IS NULL OR NEW.link_slug = '') THEN
    LOOP
      v_slug := lower(substring(replace(gen_random_uuid()::text, '-', ''), 1, 8));
      SELECT EXISTS (
        SELECT 1 FROM public.agendador_configuracoes
        WHERE link_slug = v_slug AND owner_id <> NEW.owner_id
      ) INTO v_exists;
      EXIT WHEN NOT v_exists;
    END LOOP;
    NEW.link_slug := v_slug;
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: agendador_slots_disponiveis(text, date); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.agendador_slots_disponiveis(p_slug text, p_data date) RETURNS TABLE(hora time without time zone, disponivel boolean)
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_owner_id       uuid;
  v_duracao        int;
  v_antecedencia   int;
  v_agora          timestamptz;
  v_db_dow         int;
  v_slot           time;
  v_slot_fim       time;
  v_slot_ts        timestamptz;
  v_ocupado        boolean;
  -- loop de recorr??ncias
  v_rule           RECORD;
  v_rule_start_dow int;
  v_first_occ      date;
  v_day_diff       int;
  v_ex_type        text;
BEGIN
  SELECT c.owner_id, c.duracao_sessao_min, c.antecedencia_minima_horas
  INTO v_owner_id, v_duracao, v_antecedencia
  FROM public.agendador_configuracoes c
  WHERE c.link_slug = p_slug AND c.ativo = true
  LIMIT 1;

  IF v_owner_id IS NULL THEN RETURN; END IF;

  v_agora  := now();
  v_db_dow := extract(dow from p_data::timestamp)::int;

  -- ?????? Dia inteiro bloqueado? (agenda_bloqueios sem hora) ?????????????????????????????????????????????????????????
  -- Se sim, n??o h?? nenhum slot dispon??vel ??? retorna vazio.
  IF EXISTS (
    SELECT 1 FROM public.agenda_bloqueios b
    WHERE b.owner_id     = v_owner_id
      AND b.data_inicio <= p_data
      AND COALESCE(b.data_fim, p_data) >= p_data
      AND b.hora_inicio IS NULL   -- bloqueio de dia inteiro
      AND (
        (NOT b.recorrente)
        OR (b.recorrente AND b.dia_semana = v_db_dow)
      )
  ) THEN
    RETURN;
  END IF;

  FOR v_slot IN
    SELECT s.time
    FROM public.agenda_online_slots s
    WHERE s.owner_id = v_owner_id
      AND s.weekday  = v_db_dow
      AND s.enabled  = true
    ORDER BY s.time
  LOOP
    v_slot_fim := v_slot + (v_duracao || ' minutes')::interval;
    v_ocupado  := false;

    -- ?????? Anteced??ncia m??nima ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
    v_slot_ts := (p_data::text || ' ' || v_slot::text)::timestamp
                  AT TIME ZONE 'America/Sao_Paulo';
    IF v_slot_ts < v_agora + (v_antecedencia || ' hours')::interval THEN
      v_ocupado := true;
    END IF;

    -- ?????? Bloqueio de hor??rio espec??fico (agenda_bloqueios com hora) ?????????????????????????????????
    IF NOT v_ocupado THEN
      SELECT EXISTS (
        SELECT 1 FROM public.agenda_bloqueios b
        WHERE b.owner_id     = v_owner_id
          AND b.data_inicio <= p_data
          AND COALESCE(b.data_fim, p_data) >= p_data
          AND b.hora_inicio IS NOT NULL
          AND b.hora_inicio  < v_slot_fim
          AND b.hora_fim     > v_slot
          AND (
            (NOT b.recorrente)
            OR (b.recorrente AND b.dia_semana = v_db_dow)
          )
      ) INTO v_ocupado;
    END IF;

    -- ?????? Eventos avulsos internos (agenda_eventos) ????????????????????????????????????????????????????????????????????????????????????
    IF NOT v_ocupado THEN
      SELECT EXISTS (
        SELECT 1 FROM public.agenda_eventos e
        WHERE e.owner_id = v_owner_id
          AND e.status::text NOT IN ('cancelado', 'faltou')
          AND (e.inicio_em AT TIME ZONE 'America/Sao_Paulo')::date = p_data
          AND (e.inicio_em AT TIME ZONE 'America/Sao_Paulo')::time < v_slot_fim
          AND (e.fim_em    AT TIME ZONE 'America/Sao_Paulo')::time > v_slot
      ) INTO v_ocupado;
    END IF;

    -- ?????? Recorr??ncias ativas (recurrence_rules) ?????????????????????????????????????????????????????????????????????????????????????????????
    IF NOT v_ocupado THEN
      FOR v_rule IN
        SELECT
          r.id,
          r.start_date::date                AS start_date,
          r.end_date::date                  AS end_date,
          r.start_time::time                AS start_time,
          r.end_time::time                  AS end_time,
          COALESCE(r.interval, 1)::int      AS interval
        FROM public.recurrence_rules r
        WHERE r.owner_id = v_owner_id
          AND r.status   = 'ativo'
          AND p_data    >= r.start_date::date
          AND (r.end_date IS NULL OR p_data <= r.end_date::date)
          AND v_db_dow = ANY(r.weekdays)
          AND r.start_time::time < v_slot_fim
          AND r.end_time::time   > v_slot
      LOOP
        v_rule_start_dow := extract(dow from v_rule.start_date)::int;
        v_first_occ      := v_rule.start_date
                              + (((v_db_dow - v_rule_start_dow + 7) % 7))::int;
        v_day_diff       := (p_data - v_first_occ)::int;

        IF v_day_diff >= 0 AND v_day_diff % (7 * v_rule.interval) = 0 THEN
          v_ex_type := NULL;
          SELECT ex.type INTO v_ex_type
          FROM public.recurrence_exceptions ex
          WHERE ex.recurrence_id = v_rule.id
            AND ex.original_date = p_data
          LIMIT 1;

          IF v_ex_type IS NULL OR v_ex_type NOT IN (
            'cancel_session', 'patient_missed',
            'therapist_canceled', 'holiday_block',
            'reschedule_session'
          ) THEN
            v_ocupado := true;
            EXIT;
          END IF;
        END IF;
      END LOOP;
    END IF;

    -- ?????? Recorr??ncias remarcadas para este dia ????????????????????????????????????????????????????????????????????????????????????????????????
    IF NOT v_ocupado THEN
      SELECT EXISTS (
        SELECT 1
        FROM public.recurrence_exceptions ex
        JOIN public.recurrence_rules r ON r.id = ex.recurrence_id
        WHERE r.owner_id  = v_owner_id
          AND r.status    = 'ativo'
          AND ex.type     = 'reschedule_session'
          AND ex.new_date = p_data
          AND COALESCE(ex.new_start_time, r.start_time)::time < v_slot_fim
          AND COALESCE(ex.new_end_time,   r.end_time)::time   > v_slot
      ) INTO v_ocupado;
    END IF;

    -- ?????? Solicita????es p??blicas pendentes ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????
    IF NOT v_ocupado THEN
      SELECT EXISTS (
        SELECT 1 FROM public.agendador_solicitacoes sol
        WHERE sol.owner_id        = v_owner_id
          AND sol.status          = 'pendente'
          AND sol.data_solicitada = p_data
          AND sol.hora_solicitada = v_slot
          AND (sol.reservado_ate IS NULL OR sol.reservado_ate > v_agora)
      ) INTO v_ocupado;
    END IF;

    hora       := v_slot;
    disponivel := NOT v_ocupado;
    RETURN NEXT;
  END LOOP;
END;
$$;


--
-- Name: auto_create_financial_record_from_session(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.auto_create_financial_record_from_session() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_price          NUMERIC(10,2);
  v_services_total NUMERIC(10,2);
  v_already_billed BOOLEAN;
BEGIN
  -- ?????? Guards de sa??da r??pida ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????

  -- S?? processa quando o status muda PARA 'realizado'
  IF NEW.status::TEXT <> 'realizado' THEN
    RETURN NEW;
  END IF;

  -- S?? processa quando houve mudan??a real de status
  IF OLD.status IS NOT DISTINCT FROM NEW.status THEN
    RETURN NEW;
  END IF;

  -- S?? sess??es (n??o bloqueios, feriados, etc.)
  IF NEW.tipo::TEXT <> 'sessao' THEN
    RETURN NEW;
  END IF;

  -- Paciente obrigat??rio para vincular a cobran??a
  IF NEW.patient_id IS NULL THEN
    RETURN NEW;
  END IF;

  -- Sess??es de pacote t??m cobran??a gerenciada por billing_contract
  IF NEW.billing_contract_id IS NOT NULL THEN
    RETURN NEW;
  END IF;

  -- Idempot??ncia: j?? existe financial_record para este evento?
  SELECT billed INTO v_already_billed
  FROM public.agenda_eventos
  WHERE id = NEW.id;

  IF v_already_billed = TRUE THEN
    -- Confirma no financial_records tamb??m (dupla verifica????o)
    IF EXISTS (
      SELECT 1 FROM public.financial_records
      WHERE agenda_evento_id = NEW.id AND deleted_at IS NULL
    ) THEN
      RETURN NEW;
    END IF;
  END IF;

  -- ?????? Busca do pre??o ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????

  v_price := NULL;

  -- Prioridade 1: soma dos servi??os da regra de recorr??ncia
  IF NEW.recurrence_id IS NOT NULL THEN
    SELECT COALESCE(SUM(rrs.final_price), 0)
    INTO v_services_total
    FROM public.recurrence_rule_services rrs
    WHERE rrs.rule_id = NEW.recurrence_id;

    IF v_services_total > 0 THEN
      v_price := v_services_total;
    END IF;

    -- Prioridade 2: price direto da regra (fallback se sem servi??os)
    IF v_price IS NULL OR v_price = 0 THEN
      SELECT price INTO v_price
      FROM public.recurrence_rules
      WHERE id = NEW.recurrence_id;
    END IF;
  END IF;

  -- Prioridade 3: price do pr??prio evento de agenda
  IF v_price IS NULL OR v_price = 0 THEN
    v_price := NEW.price;
  END IF;

  -- Sem pre??o ??? n??o criar registro (n??o ?? erro, apenas skip silencioso)
  IF v_price IS NULL OR v_price <= 0 THEN
    RETURN NEW;
  END IF;

  -- ?????? Cria????o do financial_record ???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????

  INSERT INTO public.financial_records (
    owner_id,
    tenant_id,
    patient_id,
    agenda_evento_id,
    type,
    amount,
    discount_amount,
    final_amount,
    clinic_fee_pct,
    clinic_fee_amount,
    status,
    due_date
    -- payment_method: NULL at?? o momento do pagamento (mark_as_paid preenche)
  ) VALUES (
    NEW.owner_id,
    NEW.tenant_id,
    NEW.patient_id,
    NEW.id,
    'receita',
    v_price,
    0,
    v_price,
    0,    -- clinic_fee_pct: sem campo de configura????o global no schema atual.
    0,    -- clinic_fee_amount: calculado manualmente ou via update posterior.
    'pending',
    (NEW.inicio_em::DATE + 7)  -- vencimento padr??o: 7 dias ap??s a sess??o
  );

  -- ?????? Marca sess??o como billed ????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
  -- UPDATE em billed (n??o em status) ??? n??o re-dispara este trigger
  UPDATE public.agenda_eventos
  SET billed = TRUE
  WHERE id = NEW.id;

  RETURN NEW;

EXCEPTION
  WHEN OTHERS THEN
    -- Log silencioso: nunca bloquear a agenda por falha financeira
    RAISE WARNING '[auto_create_financial_record_from_session] evento=% erro=%',
      NEW.id, SQLERRM;
    RETURN NEW;
END;
$$;


--
-- Name: FUNCTION auto_create_financial_record_from_session(); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.auto_create_financial_record_from_session() IS 'Trigger que cria automaticamente um financial_record (receita, pending) quando uma sess??o de agenda ?? marcada como realizada. Prioridade de pre??o: recurrence_rule_services > recurrence_rules.price > agenda_eventos.price. Skip silencioso se sem pre??o, pacote ou registro j?? existente.';


--
-- Name: can_delete_patient(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.can_delete_patient(p_patient_id uuid) RETURNS boolean
    LANGUAGE sql STABLE SECURITY DEFINER
    AS $$
  SELECT NOT EXISTS (
    SELECT 1 FROM public.agenda_eventos    WHERE patient_id = p_patient_id
    UNION ALL
    SELECT 1 FROM public.recurrence_rules  WHERE patient_id = p_patient_id
    UNION ALL
    SELECT 1 FROM public.billing_contracts WHERE patient_id = p_patient_id
  );
$$;


--
-- Name: cancel_notifications_on_opt_out(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.cancel_notifications_on_opt_out() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  -- WhatsApp opt-out
  IF OLD.whatsapp_opt_in = true AND NEW.whatsapp_opt_in = false THEN
    PERFORM public.cancel_patient_pending_notifications(
      NEW.patient_id, 'whatsapp'
    );
  END IF;
  -- Email opt-out
  IF OLD.email_opt_in = true AND NEW.email_opt_in = false THEN
    PERFORM public.cancel_patient_pending_notifications(
      NEW.patient_id, 'email'
    );
  END IF;
  -- SMS opt-out
  IF OLD.sms_opt_in = true AND NEW.sms_opt_in = false THEN
    PERFORM public.cancel_patient_pending_notifications(
      NEW.patient_id, 'sms'
    );
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: cancel_notifications_on_session_cancel(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.cancel_notifications_on_session_cancel() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  IF NEW.status = 'cancelado' AND OLD.status <> 'cancelado' THEN
    PERFORM public.cancel_patient_pending_notifications(
      NEW.patient_id, NULL, NEW.id
    );
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: cancel_patient_pending_notifications(uuid, text, uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.cancel_patient_pending_notifications(p_patient_id uuid, p_channel text DEFAULT NULL::text, p_evento_id uuid DEFAULT NULL::uuid) RETURNS integer
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
DECLARE
  v_canceled integer;
BEGIN
  UPDATE public.notification_queue
  SET status = 'cancelado',
      updated_at = now()
  WHERE patient_id = p_patient_id
    AND status IN ('pendente', 'processando')
    AND (p_channel IS NULL OR channel = p_channel)
    AND (p_evento_id IS NULL OR agenda_evento_id = p_evento_id);

  GET DIAGNOSTICS v_canceled = ROW_COUNT;
  RETURN v_canceled;
END;
$$;


--
-- Name: cancel_recurrence_from(uuid, date); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.cancel_recurrence_from(p_recurrence_id uuid, p_from_date date) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
  UPDATE public.recurrence_rules
  SET
    end_date   = p_from_date - INTERVAL '1 day',
    open_ended = false,
    status     = CASE
                   WHEN p_from_date <= start_date THEN 'cancelado'
                   ELSE status
                 END,
    updated_at = now()
  WHERE id = p_recurrence_id;
END;
$$;


--
-- Name: cancel_subscription(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.cancel_subscription(p_subscription_id uuid) RETURNS public.subscriptions
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_sub public.subscriptions;
  v_owner_type text;
  v_owner_ref uuid;
begin

  select *
  into v_sub
  from public.subscriptions
  where id = p_subscription_id
  for update;

  if not found then
    raise exception 'Subscription n??o encontrada';
  end if;

  if v_sub.status = 'canceled' then
    return v_sub;
  end if;

  if v_sub.tenant_id is not null then
    v_owner_type := 'clinic';
    v_owner_ref := v_sub.tenant_id;
  elsif v_sub.user_id is not null then
    v_owner_type := 'therapist';
    v_owner_ref := v_sub.user_id;
  else
    v_owner_type := null;
    v_owner_ref := null;
  end if;

  update public.subscriptions
  set status = 'canceled',
      cancel_at_period_end = false,
      updated_at = now()
  where id = p_subscription_id
  returning * into v_sub;

  insert into public.subscription_events(
    subscription_id,
    owner_id,
    owner_type,
    owner_ref,
    event_type,
    old_plan_id,
    new_plan_id,
    created_by,
    reason,
    source,
    metadata
  )
  values (
    v_sub.id,
    v_owner_ref,
    v_owner_type,
    v_owner_ref,
    'canceled',
    v_sub.plan_id,
    v_sub.plan_id,
    auth.uid(),
    'Cancelamento manual via admin',
    'admin_panel',
    jsonb_build_object('previous_status', 'active')
  );

  if v_owner_ref is not null then
    insert into public.entitlements_invalidation(owner_id, changed_at)
    values (v_owner_ref, now())
    on conflict (owner_id)
    do update set changed_at = excluded.changed_at;
  end if;

  return v_sub;

end;
$$;


--
-- Name: cancelar_eventos_serie(uuid, timestamp with time zone); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.cancelar_eventos_serie(p_serie_id uuid, p_a_partir_de timestamp with time zone DEFAULT now()) RETURNS integer
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
DECLARE
  v_count integer;
BEGIN
  UPDATE public.agenda_eventos
     SET status     = 'cancelado',
         updated_at = now()
   WHERE serie_id  = p_serie_id
     AND inicio_em >= p_a_partir_de
     AND status NOT IN ('realizado', 'cancelado');

  GET DIAGNOSTICS v_count = ROW_COUNT;
  RETURN v_count;
END;
$$;


--
-- Name: FUNCTION cancelar_eventos_serie(p_serie_id uuid, p_a_partir_de timestamp with time zone); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.cancelar_eventos_serie(p_serie_id uuid, p_a_partir_de timestamp with time zone) IS 'Cancela todos os eventos futuros de uma s??rie a partir de p_a_partir_de (inclusive).
   N??o cancela eventos j?? realizados.';


--
-- Name: change_subscription_plan(uuid, uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.change_subscription_plan(p_subscription_id uuid, p_new_plan_id uuid) RETURNS public.subscriptions
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_sub public.subscriptions;
  v_old_plan uuid;
  v_new_key text;

  v_owner_type text;
  v_owner_ref uuid;

  v_new_target text;
  v_sub_target text;
begin
  select *
    into v_sub
  from public.subscriptions
  where id = p_subscription_id
  for update;

  if not found then
    raise exception 'Subscription n??o encontrada';
  end if;

  v_old_plan := v_sub.plan_id;

  if v_old_plan = p_new_plan_id then
    return v_sub;
  end if;

  select key, target
    into v_new_key, v_new_target
  from public.plans
  where id = p_new_plan_id;

  if v_new_key is null then
    raise exception 'Plano n??o encontrado';
  end if;

  v_new_target := lower(coalesce(v_new_target, ''));

  v_sub_target := case
    when v_sub.tenant_id is not null then 'clinic'
    else 'therapist'
  end;

  if v_new_target <> v_sub_target then
    raise exception 'Plano inv??lido para este tipo de assinatura. Assinatura ?? % e o plano ?? %.',
      v_sub_target, v_new_target
      using errcode = 'P0001';
  end if;

  if v_sub.tenant_id is not null then
    v_owner_type := 'clinic';
    v_owner_ref := v_sub.tenant_id;
  elsif v_sub.user_id is not null then
    v_owner_type := 'therapist';
    v_owner_ref := v_sub.user_id;
  else
    v_owner_type := null;
    v_owner_ref := null;
  end if;

  update public.subscriptions
  set plan_id = p_new_plan_id,
      plan_key = v_new_key,
      updated_at = now()
  where id = p_subscription_id
  returning * into v_sub;

  insert into public.subscription_events(
    subscription_id,
    owner_id,
    owner_type,
    owner_ref,
    event_type,
    old_plan_id,
    new_plan_id,
    created_by,
    reason,
    source,
    metadata
  )
  values (
    v_sub.id,
    v_owner_ref,
    v_owner_type,
    v_owner_ref,
    'plan_changed',
    v_old_plan,
    p_new_plan_id,
    auth.uid(),
    'Plan change via DEV menu',
    'dev_menu',
    jsonb_build_object(
      'previous_plan', v_old_plan,
      'new_plan', p_new_plan_id,
      'new_plan_key', v_new_key,
      'new_plan_target', v_new_target
    )
  );

  if v_owner_ref is not null then
    insert into public.entitlements_invalidation (owner_id, changed_at)
    values (v_owner_ref, now())
    on conflict (owner_id)
    do update set changed_at = excluded.changed_at;
  end if;

  return v_sub;
end;
$$;


--
-- Name: check_rate_limit(text, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.check_rate_limit(p_ip_hash text, p_endpoint text) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    cfg                       saas_security_config%ROWTYPE;
    rl                        submission_rate_limits%ROWTYPE;
    v_now                     timestamptz := now();
    v_window_start            timestamptz;
    v_in_window               boolean;
    v_requires_captcha        boolean := false;
    v_blocked_until           timestamptz;
    v_retry_after_seconds     integer := 0;
BEGIN
    SELECT * INTO cfg FROM saas_security_config WHERE id = true;
    IF NOT FOUND THEN
        -- Sem config: fail-open (libera). Logado.
        RETURN jsonb_build_object('allowed', true, 'requires_captcha', false, 'reason', 'no_config');
    END IF;

    -- Modo paranoid global: sempre captcha
    IF cfg.captcha_required_globally THEN
        v_requires_captcha := true;
    END IF;

    -- Sem rate limit ativo: libera (mas pode exigir captcha pelo paranoid)
    IF NOT cfg.rate_limit_enabled THEN
        RETURN jsonb_build_object(
            'allowed',          true,
            'requires_captcha', v_requires_captcha,
            'reason',           CASE WHEN v_requires_captcha THEN 'paranoid_global' ELSE 'rate_limit_disabled' END
        );
    END IF;

    -- Sem ip_hash: libera (não dá pra rastrear)
    IF p_ip_hash IS NULL OR length(btrim(p_ip_hash)) = 0 THEN
        RETURN jsonb_build_object(
            'allowed',          true,
            'requires_captcha', v_requires_captcha,
            'reason',           'no_ip'
        );
    END IF;

    SELECT * INTO rl
      FROM submission_rate_limits
     WHERE ip_hash = p_ip_hash AND endpoint = p_endpoint;

    -- Bloqueio temporário ativo?
    IF FOUND AND rl.blocked_until IS NOT NULL AND rl.blocked_until > v_now THEN
        v_retry_after_seconds := EXTRACT(EPOCH FROM (rl.blocked_until - v_now))::int;
        RETURN jsonb_build_object(
            'allowed',             false,
            'requires_captcha',    false,
            'retry_after_seconds', v_retry_after_seconds,
            'reason',              'blocked'
        );
    END IF;

    -- Captcha condicional ativo?
    IF FOUND AND rl.requires_captcha_until IS NOT NULL AND rl.requires_captcha_until > v_now THEN
        v_requires_captcha := true;
    END IF;

    -- Janela atual ainda válida?
    v_window_start := v_now - (cfg.rate_limit_window_min || ' minutes')::interval;
    v_in_window := FOUND AND rl.window_start >= v_window_start;

    IF v_in_window AND rl.attempt_count >= cfg.rate_limit_max_attempts THEN
        -- Excedeu — bloqueia
        v_blocked_until := v_now + (cfg.block_duration_min || ' minutes')::interval;
        UPDATE submission_rate_limits
           SET blocked_until = v_blocked_until,
               last_attempt_at = v_now
         WHERE ip_hash = p_ip_hash AND endpoint = p_endpoint;

        v_retry_after_seconds := EXTRACT(EPOCH FROM (v_blocked_until - v_now))::int;
        RETURN jsonb_build_object(
            'allowed',             false,
            'requires_captcha',    false,
            'retry_after_seconds', v_retry_after_seconds,
            'reason',              'rate_limit_exceeded'
        );
    END IF;

    RETURN jsonb_build_object(
        'allowed',          true,
        'requires_captcha', v_requires_captcha,
        'reason',           CASE WHEN v_requires_captcha THEN 'captcha_required' ELSE 'ok' END
    );
END;
$$;


--
-- Name: cleanup_expired_math_challenges(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.cleanup_expired_math_challenges() RETURNS integer
    LANGUAGE sql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
    WITH d AS (
        DELETE FROM math_challenges WHERE expires_at < now() - interval '1 hour' RETURNING 1
    )
    SELECT COUNT(*)::int FROM d;
$$;


--
-- Name: cleanup_notification_queue(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.cleanup_notification_queue() RETURNS integer
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
DECLARE
  v_deleted integer;
BEGIN
  DELETE FROM public.notification_queue
  WHERE status IN ('enviado', 'cancelado', 'ignorado')
    AND created_at < now() - interval '90 days';

  GET DIAGNOSTICS v_deleted = ROW_COUNT;
  RETURN v_deleted;
END;
$$;


--
-- Name: convert_abandoned_intake_to_lead(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.convert_abandoned_intake_to_lead(p_intake_id uuid) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_intake RECORD;
    v_tenant_id UUID;
    v_thread_key TEXT;
    v_phone TEXT;
    v_note_body TEXT;
    v_admin_id UUID;
    v_msg_id BIGINT;
BEGIN
    SELECT * INTO v_intake FROM public.patient_intake_requests WHERE id = p_intake_id;
    IF NOT FOUND THEN RAISE EXCEPTION 'intake_not_found'; END IF;
    IF v_intake.status = 'abandoned_lead' THEN RETURN v_intake.lead_thread_key::UUID; END IF;

    -- Tenant_id vem via owner_id (tenant_members)
    SELECT tenant_id INTO v_tenant_id
        FROM public.tenant_members
        WHERE user_id = v_intake.owner_id
        ORDER BY CASE role WHEN 'tenant_admin' THEN 1 WHEN 'clinic_admin' THEN 2 ELSE 3 END
        LIMIT 1;

    IF v_tenant_id IS NULL THEN RAISE EXCEPTION 'tenant_not_resolved'; END IF;

    -- Normaliza telefone pra thread_key
    v_phone := regexp_replace(COALESCE(v_intake.telefone, ''), '\D', '', 'g');
    IF length(v_phone) BETWEEN 10 AND 11 THEN v_phone := '55' || v_phone; END IF;
    IF v_phone = '' THEN v_phone := 'unknown'; END IF;
    v_thread_key := 'anon:' || v_phone;

    -- Nota com dados coletados
    v_note_body := format(
        '📋 Lead abandonado (cadastro externo):%s%sNome: %s%sTelefone: %s%sE-mail: %s%sMotivo/Observacoes: %s%s%sIniciou em: %s · Ultima atualizacao: %s',
        E'\n', E'\n',
        COALESCE(v_intake.nome_completo, '—'),
        E'\n',
        COALESCE(v_intake.telefone, '—'),
        E'\n',
        COALESCE(v_intake.email_principal, '—'),
        E'\n',
        COALESCE(v_intake.onde_nos_conheceu, '—'),
        E'\n', E'\n',
        to_char(v_intake.created_at AT TIME ZONE 'America/Sao_Paulo', 'DD/MM HH24:MI'),
        to_char(COALESCE(v_intake.last_progress_at, v_intake.updated_at) AT TIME ZONE 'America/Sao_Paulo', 'DD/MM HH24:MI')
    );

    -- Pega 1 admin do tenant pra preencher created_by da nota
    SELECT user_id INTO v_admin_id
        FROM public.tenant_members
        WHERE tenant_id = v_tenant_id
          AND role IN ('tenant_admin', 'clinic_admin')
          AND status = 'active'
        LIMIT 1;

    IF v_admin_id IS NULL THEN
        v_admin_id := v_intake.owner_id;
    END IF;

    -- Cria mensagem placeholder (outbound sistema — entra no thread do CRM)
    INSERT INTO public.conversation_messages
        (tenant_id, channel, direction, from_number, to_number, body, provider,
         provider_raw, kanban_status)
    VALUES (
        v_tenant_id, 'whatsapp', 'inbound',
        CASE WHEN v_phone = 'unknown' THEN NULL ELSE v_phone END,
        NULL,
        format('🧾 Cadastro externo iniciado e não finalizado. %s entrou em contato via link público mas abandonou o formulário — ver nota interna.',
               COALESCE(v_intake.nome_completo, 'Visitante')),
        'system',
        jsonb_build_object('lead_from_abandoned_intake', true, 'intake_id', v_intake.id),
        'awaiting_us'
    ) RETURNING id INTO v_msg_id;

    -- Cria nota interna
    INSERT INTO public.conversation_notes
        (tenant_id, thread_key, contact_number, body, created_by)
    VALUES (
        v_tenant_id, v_thread_key,
        CASE WHEN v_phone = 'unknown' THEN NULL ELSE v_phone END,
        v_note_body, v_admin_id
    );

    -- Atualiza intake
    UPDATE public.patient_intake_requests
        SET status = 'abandoned_lead',
            lead_thread_key = v_thread_key,
            updated_at = now()
        WHERE id = p_intake_id;

    RETURN p_intake_id;
END;
$$;


--
-- Name: FUNCTION convert_abandoned_intake_to_lead(p_intake_id uuid); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.convert_abandoned_intake_to_lead(p_intake_id uuid) IS 'Transforma 1 intake abandonado em lead no CRM: cria mensagem placeholder + nota com dados + marca status.';


--
-- Name: create_clinic_tenant(text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.create_clinic_tenant(p_name text) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_uid uuid;
  v_tenant uuid;
  v_name text;
begin
  v_uid := auth.uid();
  if v_uid is null then
    raise exception 'Not authenticated';
  end if;

  v_name := nullif(trim(coalesce(p_name, '')), '');
  if v_name is null then
    v_name := 'Cl??nica';
  end if;

  insert into public.tenants (name, kind, created_at)
  values (v_name, 'clinic', now())
  returning id into v_tenant;

  insert into public.tenant_members (tenant_id, user_id, role, status, created_at)
  values (v_tenant, v_uid, 'tenant_admin', 'active', now());

  return v_tenant;
end;
$$;


--
-- Name: financial_records; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.financial_records (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    type public.financial_record_type DEFAULT 'receita'::public.financial_record_type NOT NULL,
    amount numeric(10,2) NOT NULL,
    description text,
    category text,
    payment_method text,
    paid_at timestamp with time zone,
    due_date date,
    installments smallint DEFAULT 1,
    installment_number smallint DEFAULT 1,
    installment_group uuid,
    agenda_evento_id uuid,
    patient_id uuid,
    clinic_fee_pct numeric(5,2) DEFAULT 0,
    clinic_fee_amount numeric(10,2) DEFAULT 0,
    net_amount numeric(10,2) GENERATED ALWAYS AS ((amount - clinic_fee_amount)) STORED,
    insurance_plan_id uuid,
    notes text,
    tags text[],
    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,
    discount_amount numeric(10,2) DEFAULT 0 NOT NULL,
    final_amount numeric(10,2) DEFAULT 0 NOT NULL,
    status text DEFAULT 'pending'::text NOT NULL,
    category_id uuid,
    CONSTRAINT financial_records_amount_check CHECK ((amount >= (0)::numeric)),
    CONSTRAINT financial_records_clinic_fee_amount_check CHECK ((clinic_fee_amount >= (0)::numeric)),
    CONSTRAINT financial_records_clinic_fee_pct_check CHECK (((clinic_fee_pct >= (0)::numeric) AND (clinic_fee_pct <= (100)::numeric))),
    CONSTRAINT financial_records_discount_amount_check CHECK ((discount_amount >= (0)::numeric)),
    CONSTRAINT financial_records_fee_lte_amount_chk CHECK (((clinic_fee_amount IS NULL) OR ((clinic_fee_amount >= (0)::numeric) AND (clinic_fee_amount <= amount)))),
    CONSTRAINT financial_records_final_amount_check CHECK ((final_amount >= (0)::numeric)),
    CONSTRAINT financial_records_installments_check CHECK ((installments >= 1)),
    CONSTRAINT financial_records_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'paid'::text, 'partial'::text, 'overdue'::text, 'cancelled'::text, 'refunded'::text])))
);


--
-- Name: create_financial_record_for_session(uuid, uuid, uuid, uuid, numeric, date); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.create_financial_record_for_session(p_tenant_id uuid, p_owner_id uuid, p_patient_id uuid, p_agenda_evento_id uuid, p_amount numeric, p_due_date date) RETURNS SETOF public.financial_records
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_existing public.financial_records%ROWTYPE;
  v_new      public.financial_records%ROWTYPE;
BEGIN
  -- Idempot??ncia: retorna o registro existente se j?? foi criado
  SELECT * INTO v_existing
  FROM public.financial_records
  WHERE agenda_evento_id = p_agenda_evento_id
    AND deleted_at IS NULL
  LIMIT 1;

  IF FOUND THEN
    RETURN NEXT v_existing;
    RETURN;
  END IF;

  -- Cria o novo registro
  INSERT INTO public.financial_records (
    tenant_id,
    owner_id,
    patient_id,
    agenda_evento_id,
    amount,
    discount_amount,
    final_amount,
    status,
    due_date
  ) VALUES (
    p_tenant_id,
    p_owner_id,
    p_patient_id,
    p_agenda_evento_id,
    p_amount,
    0,
    p_amount,
    'pending',
    p_due_date
  )
  RETURNING * INTO v_new;

  -- Marca o evento da agenda como billed = true
  UPDATE public.agenda_eventos
    SET billed = TRUE
  WHERE id = p_agenda_evento_id;

  RETURN NEXT v_new;
END;
$$;


--
-- Name: create_patient_intake_request(text, text, text, text, text, boolean); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.create_patient_intake_request(p_token text, p_name text, p_email text DEFAULT NULL::text, p_phone text DEFAULT NULL::text, p_notes text DEFAULT NULL::text, p_consent boolean DEFAULT false) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
declare
  v_owner uuid;
  v_active boolean;
  v_expires timestamptz;
  v_max_uses int;
  v_uses int;
  v_id uuid;
begin
  select owner_id, active, expires_at, max_uses, uses
    into v_owner, v_active, v_expires, v_max_uses, v_uses
  from public.patient_invites
  where token = p_token
  limit 1;

  if v_owner is null then
    raise exception 'Token inv??lido';
  end if;

  if v_active is not true then
    raise exception 'Link desativado';
  end if;

  if v_expires is not null and now() > v_expires then
    raise exception 'Link expirado';
  end if;

  if v_max_uses is not null and v_uses >= v_max_uses then
    raise exception 'Limite de uso atingido';
  end if;

  if p_name is null or length(trim(p_name)) = 0 then
    raise exception 'Nome ?? obrigat??rio';
  end if;

  insert into public.patient_intake_requests
    (owner_id, token, name, email, phone, notes, consent, status)
  values
    (v_owner, p_token, trim(p_name),
     nullif(lower(trim(p_email)), ''),
     nullif(trim(p_phone), ''),
     nullif(trim(p_notes), ''),
     coalesce(p_consent, false),
     'new')
  returning id into v_id;

  update public.patient_invites
    set uses = uses + 1
  where token = p_token;

  return v_id;
end;
$$;


--
-- Name: create_patient_intake_request_v2(text, jsonb); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.create_patient_intake_request_v2(p_token text, p_payload jsonb) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $_$
DECLARE
  v_owner_id   uuid;
  v_tenant_id  uuid;
  v_active     boolean;
  v_expires    timestamptz;
  v_max_uses   int;
  v_uses       int;
  v_intake_id  uuid;
  v_birth_raw  text;
  v_birth      date;
  v_email      text;
  v_email_alt  text;
  v_nome       text;
  v_consent    boolean;
  v_genero     text;
  v_estado_civil text;

  -- Whitelists para campos tipados
  c_generos        text[] := ARRAY['male','female','non_binary','other','na'];
  c_estados_civis  text[] := ARRAY['single','married','divorced','widowed','na'];
BEGIN
  -- ───────────────────────────────────────────────────────────────────────
  -- Carrega invite e valida TUDO (A#16)
  -- ───────────────────────────────────────────────────────────────────────
  SELECT owner_id, tenant_id, active, expires_at, max_uses, uses
    INTO v_owner_id, v_tenant_id, v_active, v_expires, v_max_uses, v_uses
  FROM public.patient_invites
  WHERE token = p_token
  LIMIT 1;

  IF v_owner_id IS NULL THEN
    RAISE EXCEPTION 'Token inválido' USING ERRCODE = '28000';
  END IF;

  IF v_active IS NOT TRUE THEN
    RAISE EXCEPTION 'Link desativado' USING ERRCODE = '28000';
  END IF;

  IF v_expires IS NOT NULL AND now() > v_expires THEN
    RAISE EXCEPTION 'Link expirado' USING ERRCODE = '28000';
  END IF;

  IF v_max_uses IS NOT NULL AND v_uses >= v_max_uses THEN
    RAISE EXCEPTION 'Limite de uso atingido' USING ERRCODE = '28000';
  END IF;

  -- ───────────────────────────────────────────────────────────────────────
  -- Resolver tenant_id (A#19)
  -- Se o invite não tem tenant_id, tenta achar a membership active do owner.
  -- ───────────────────────────────────────────────────────────────────────
  IF v_tenant_id IS NULL THEN
    SELECT tenant_id
      INTO v_tenant_id
    FROM public.tenant_members
    WHERE user_id = v_owner_id
      AND status = 'active'
    ORDER BY created_at ASC
    LIMIT 1;
  END IF;

  -- ───────────────────────────────────────────────────────────────────────
  -- Sanitização + validações de campos (A#27)
  -- ───────────────────────────────────────────────────────────────────────

  -- Nome obrigatório (max 200)
  v_nome := nullif(trim(p_payload->>'nome_completo'), '');
  IF v_nome IS NULL THEN
    RAISE EXCEPTION 'Nome é obrigatório';
  END IF;
  IF length(v_nome) > 200 THEN
    RAISE EXCEPTION 'Nome muito longo (máx 200 caracteres)';
  END IF;

  -- Email principal obrigatório + lower + max 120
  v_email := nullif(lower(trim(p_payload->>'email_principal')), '');
  IF v_email IS NULL THEN
    RAISE EXCEPTION 'E-mail é obrigatório';
  END IF;
  IF length(v_email) > 120 THEN
    RAISE EXCEPTION 'E-mail muito longo (máx 120 caracteres)';
  END IF;
  IF v_email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
    RAISE EXCEPTION 'E-mail inválido';
  END IF;

  -- Email alternativo opcional mas validado se presente
  v_email_alt := nullif(lower(trim(p_payload->>'email_alternativo')), '');
  IF v_email_alt IS NOT NULL THEN
    IF length(v_email_alt) > 120 THEN
      RAISE EXCEPTION 'E-mail alternativo muito longo';
    END IF;
    IF v_email_alt !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
      RAISE EXCEPTION 'E-mail alternativo inválido';
    END IF;
  END IF;

  -- Consent obrigatório
  v_consent := coalesce((p_payload->>'consent')::boolean, false);
  IF v_consent IS NOT TRUE THEN
    RAISE EXCEPTION 'Consentimento é obrigatório';
  END IF;

  -- Data de nascimento: aceita DD-MM-YYYY ou YYYY-MM-DD
  v_birth_raw := nullif(trim(coalesce(p_payload->>'data_nascimento', '')), '');
  v_birth := CASE
    WHEN v_birth_raw IS NULL THEN NULL
    WHEN v_birth_raw ~ '^\d{4}-\d{2}-\d{2}$' THEN v_birth_raw::date
    WHEN v_birth_raw ~ '^\d{2}-\d{2}-\d{4}$' THEN to_date(v_birth_raw, 'DD-MM-YYYY')
    ELSE NULL
  END;
  -- Sanidade: nascimento não pode ser no futuro nem antes de 1900
  IF v_birth IS NOT NULL AND (v_birth > current_date OR v_birth < '1900-01-01'::date) THEN
    v_birth := NULL;
  END IF;

  -- Gênero e estado civil: whitelist estrita (rejeita qualquer outra string)
  v_genero := nullif(trim(p_payload->>'genero'), '');
  IF v_genero IS NOT NULL AND NOT (v_genero = ANY(c_generos)) THEN
    v_genero := NULL;
  END IF;

  v_estado_civil := nullif(trim(p_payload->>'estado_civil'), '');
  IF v_estado_civil IS NOT NULL AND NOT (v_estado_civil = ANY(c_estados_civis)) THEN
    v_estado_civil := NULL;
  END IF;

  -- ───────────────────────────────────────────────────────────────────────
  -- INSERT com sanitização inline
  -- NOTA: notas_internas NÃO é lido do payload (A#17) — é campo interno
  -- do terapeuta, não deve vir do paciente.
  -- ───────────────────────────────────────────────────────────────────────
  INSERT INTO public.patient_intake_requests (
    owner_id,
    tenant_id,
    token,
    status,
    consent,

    nome_completo,
    email_principal,
    email_alternativo,
    telefone,
    telefone_alternativo,

    avatar_url,

    data_nascimento,
    cpf,
    rg,
    genero,
    estado_civil,
    profissao,
    escolaridade,
    nacionalidade,
    naturalidade,

    cep,
    pais,
    cidade,
    estado,
    endereco,
    numero,
    complemento,
    bairro,

    observacoes,

    encaminhado_por,
    onde_nos_conheceu
  )
  VALUES (
    v_owner_id,
    v_tenant_id,
    p_token,
    'new',
    v_consent,

    v_nome,
    v_email,
    v_email_alt,
    nullif(regexp_replace(coalesce(p_payload->>'telefone',''), '\D', '', 'g'), ''),
    nullif(regexp_replace(coalesce(p_payload->>'telefone_alternativo',''), '\D', '', 'g'), ''),

    left(nullif(trim(p_payload->>'avatar_url'), ''), 500),

    v_birth,
    nullif(regexp_replace(coalesce(p_payload->>'cpf',''), '\D', '', 'g'), ''),
    left(nullif(trim(p_payload->>'rg'), ''), 20),
    v_genero,
    v_estado_civil,
    left(nullif(trim(p_payload->>'profissao'), ''), 120),
    left(nullif(trim(p_payload->>'escolaridade'), ''), 120),
    left(nullif(trim(p_payload->>'nacionalidade'), ''), 80),
    left(nullif(trim(p_payload->>'naturalidade'), ''), 120),

    nullif(regexp_replace(coalesce(p_payload->>'cep',''), '\D', '', 'g'), ''),
    left(nullif(trim(p_payload->>'pais'), ''), 60),
    left(nullif(trim(p_payload->>'cidade'), ''), 120),
    left(nullif(trim(p_payload->>'estado'), ''), 2),
    left(nullif(trim(p_payload->>'endereco'), ''), 200),
    left(nullif(trim(p_payload->>'numero'), ''), 20),
    left(nullif(trim(p_payload->>'complemento'), ''), 120),
    left(nullif(trim(p_payload->>'bairro'), ''), 120),

    left(nullif(trim(p_payload->>'observacoes'), ''), 2000),

    left(nullif(trim(p_payload->>'encaminhado_por'), ''), 120),
    left(nullif(trim(p_payload->>'onde_nos_conheceu'), ''), 80)
  )
  RETURNING id INTO v_intake_id;

  -- Incrementa contador de uso (A#16)
  UPDATE public.patient_invites
    SET uses = uses + 1
  WHERE token = p_token;

  RETURN v_intake_id;
END;
$_$;


--
-- Name: FUNCTION create_patient_intake_request_v2(p_token text, p_payload jsonb); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.create_patient_intake_request_v2(p_token text, p_payload jsonb) IS 'Hardened 2026-04-18: valida active/expires/max_uses + incrementa uses; sanitiza todos os campos (trim, length, regex); resolve tenant_id; rejeita notas_internas (campo interno); exige consent=true.';


--
-- Name: create_patient_intake_request_v2(text, jsonb, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.create_patient_intake_request_v2(p_token text, p_payload jsonb, p_client_info text DEFAULT NULL::text) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $_$
DECLARE
  v_owner_id   uuid;
  v_tenant_id  uuid;
  v_active     boolean;
  v_expires    timestamptz;
  v_max_uses   int;
  v_uses       int;
  v_intake_id  uuid;
  v_birth_raw  text;
  v_birth      date;
  v_email      text;
  v_email_alt  text;
  v_nome       text;
  v_consent    boolean;
  v_genero     text;
  v_estado_civil text;
  v_err_msg    text;
  v_err_code   text;
  v_clean_info text;

  c_generos        text[] := ARRAY['male','female','non_binary','other','na'];
  c_estados_civis  text[] := ARRAY['single','married','divorced','widowed','na'];

  -- Helper para logar: escreve em patient_invite_attempts e não propaga erros.
  -- Implementado inline porque PL/pgSQL não permite sub-rotina local fácil.
BEGIN
  -- Sanitiza client_info recebido (cap + trim)
  v_clean_info := nullif(left(trim(coalesce(p_client_info, '')), 500), '');

  -- ───────────────────────────────────────────────────────────────────────
  -- Resolve invite + valida TUDO (A#16)
  -- ───────────────────────────────────────────────────────────────────────
  SELECT owner_id, tenant_id, active, expires_at, max_uses, uses
    INTO v_owner_id, v_tenant_id, v_active, v_expires, v_max_uses, v_uses
  FROM public.patient_invites
  WHERE token = p_token
  LIMIT 1;

  IF v_owner_id IS NULL THEN
    v_err_code := 'TOKEN_INVALID';
    v_err_msg := 'Token inválido';
    -- Log + raise (owner_id NULL porque token não bateu)
    BEGIN
      INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info)
      VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info);
    EXCEPTION WHEN OTHERS THEN NULL; END;
    RAISE EXCEPTION '%', v_err_msg USING ERRCODE = '28000';
  END IF;

  IF v_active IS NOT TRUE THEN
    v_err_code := 'TOKEN_DISABLED';
    v_err_msg := 'Link desativado';
    BEGIN
      INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id)
      VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id);
    EXCEPTION WHEN OTHERS THEN NULL; END;
    RAISE EXCEPTION '%', v_err_msg USING ERRCODE = '28000';
  END IF;

  IF v_expires IS NOT NULL AND now() > v_expires THEN
    v_err_code := 'TOKEN_EXPIRED';
    v_err_msg := 'Link expirado';
    BEGIN
      INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id)
      VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id);
    EXCEPTION WHEN OTHERS THEN NULL; END;
    RAISE EXCEPTION '%', v_err_msg USING ERRCODE = '28000';
  END IF;

  IF v_max_uses IS NOT NULL AND v_uses >= v_max_uses THEN
    v_err_code := 'TOKEN_MAX_USES';
    v_err_msg := 'Limite de uso atingido';
    BEGIN
      INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id)
      VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id);
    EXCEPTION WHEN OTHERS THEN NULL; END;
    RAISE EXCEPTION '%', v_err_msg USING ERRCODE = '28000';
  END IF;

  -- Resolve tenant_id se invite não tiver (A#19)
  IF v_tenant_id IS NULL THEN
    SELECT tenant_id
      INTO v_tenant_id
    FROM public.tenant_members
    WHERE user_id = v_owner_id
      AND status = 'active'
    ORDER BY created_at ASC
    LIMIT 1;
  END IF;

  -- ───────────────────────────────────────────────────────────────────────
  -- Sanitização + validações de campos (A#27)
  -- ───────────────────────────────────────────────────────────────────────
  v_nome := nullif(trim(p_payload->>'nome_completo'), '');
  IF v_nome IS NULL THEN
    v_err_code := 'VALIDATION'; v_err_msg := 'Nome é obrigatório';
    BEGIN INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id) VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id); EXCEPTION WHEN OTHERS THEN NULL; END;
    RAISE EXCEPTION '%', v_err_msg;
  END IF;
  IF length(v_nome) > 200 THEN
    v_err_code := 'VALIDATION'; v_err_msg := 'Nome muito longo';
    BEGIN INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id) VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id); EXCEPTION WHEN OTHERS THEN NULL; END;
    RAISE EXCEPTION '%', v_err_msg;
  END IF;

  v_email := nullif(lower(trim(p_payload->>'email_principal')), '');
  IF v_email IS NULL THEN
    v_err_code := 'VALIDATION'; v_err_msg := 'E-mail é obrigatório';
    BEGIN INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id) VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id); EXCEPTION WHEN OTHERS THEN NULL; END;
    RAISE EXCEPTION '%', v_err_msg;
  END IF;
  IF length(v_email) > 120 THEN
    v_err_code := 'VALIDATION'; v_err_msg := 'E-mail muito longo';
    BEGIN INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id) VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id); EXCEPTION WHEN OTHERS THEN NULL; END;
    RAISE EXCEPTION '%', v_err_msg;
  END IF;
  IF v_email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
    v_err_code := 'VALIDATION'; v_err_msg := 'E-mail inválido';
    BEGIN INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id) VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id); EXCEPTION WHEN OTHERS THEN NULL; END;
    RAISE EXCEPTION '%', v_err_msg;
  END IF;

  v_email_alt := nullif(lower(trim(p_payload->>'email_alternativo')), '');
  IF v_email_alt IS NOT NULL THEN
    IF length(v_email_alt) > 120 THEN
      v_err_code := 'VALIDATION'; v_err_msg := 'E-mail alternativo muito longo';
      BEGIN INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id) VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id); EXCEPTION WHEN OTHERS THEN NULL; END;
      RAISE EXCEPTION '%', v_err_msg;
    END IF;
    IF v_email_alt !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
      v_err_code := 'VALIDATION'; v_err_msg := 'E-mail alternativo inválido';
      BEGIN INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id) VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id); EXCEPTION WHEN OTHERS THEN NULL; END;
      RAISE EXCEPTION '%', v_err_msg;
    END IF;
  END IF;

  v_consent := coalesce((p_payload->>'consent')::boolean, false);
  IF v_consent IS NOT TRUE THEN
    v_err_code := 'CONSENT_REQUIRED'; v_err_msg := 'Consentimento é obrigatório';
    BEGIN INSERT INTO public.patient_invite_attempts (token, ok, error_code, error_msg, client_info, owner_id, tenant_id) VALUES (p_token, false, v_err_code, v_err_msg, v_clean_info, v_owner_id, v_tenant_id); EXCEPTION WHEN OTHERS THEN NULL; END;
    RAISE EXCEPTION '%', v_err_msg;
  END IF;

  v_birth_raw := nullif(trim(coalesce(p_payload->>'data_nascimento', '')), '');
  v_birth := CASE
    WHEN v_birth_raw IS NULL THEN NULL
    WHEN v_birth_raw ~ '^\d{4}-\d{2}-\d{2}$' THEN v_birth_raw::date
    WHEN v_birth_raw ~ '^\d{2}-\d{2}-\d{4}$' THEN to_date(v_birth_raw, 'DD-MM-YYYY')
    ELSE NULL
  END;
  IF v_birth IS NOT NULL AND (v_birth > current_date OR v_birth < '1900-01-01'::date) THEN
    v_birth := NULL;
  END IF;

  v_genero := nullif(trim(p_payload->>'genero'), '');
  IF v_genero IS NOT NULL AND NOT (v_genero = ANY(c_generos)) THEN
    v_genero := NULL;
  END IF;

  v_estado_civil := nullif(trim(p_payload->>'estado_civil'), '');
  IF v_estado_civil IS NOT NULL AND NOT (v_estado_civil = ANY(c_estados_civis)) THEN
    v_estado_civil := NULL;
  END IF;

  -- ───────────────────────────────────────────────────────────────────────
  -- INSERT
  -- ───────────────────────────────────────────────────────────────────────
  INSERT INTO public.patient_intake_requests (
    owner_id, tenant_id, token, status, consent,
    nome_completo, email_principal, email_alternativo, telefone, telefone_alternativo,
    avatar_url,
    data_nascimento, cpf, rg, genero, estado_civil,
    profissao, escolaridade, nacionalidade, naturalidade,
    cep, pais, cidade, estado, endereco, numero, complemento, bairro,
    observacoes, encaminhado_por, onde_nos_conheceu
  )
  VALUES (
    v_owner_id, v_tenant_id, p_token, 'new', v_consent,
    v_nome, v_email, v_email_alt,
    nullif(regexp_replace(coalesce(p_payload->>'telefone',''), '\D', '', 'g'), ''),
    nullif(regexp_replace(coalesce(p_payload->>'telefone_alternativo',''), '\D', '', 'g'), ''),
    left(nullif(trim(p_payload->>'avatar_url'), ''), 500),
    v_birth,
    nullif(regexp_replace(coalesce(p_payload->>'cpf',''), '\D', '', 'g'), ''),
    left(nullif(trim(p_payload->>'rg'), ''), 20),
    v_genero, v_estado_civil,
    left(nullif(trim(p_payload->>'profissao'), ''), 120),
    left(nullif(trim(p_payload->>'escolaridade'), ''), 120),
    left(nullif(trim(p_payload->>'nacionalidade'), ''), 80),
    left(nullif(trim(p_payload->>'naturalidade'), ''), 120),
    nullif(regexp_replace(coalesce(p_payload->>'cep',''), '\D', '', 'g'), ''),
    left(nullif(trim(p_payload->>'pais'), ''), 60),
    left(nullif(trim(p_payload->>'cidade'), ''), 120),
    left(nullif(trim(p_payload->>'estado'), ''), 2),
    left(nullif(trim(p_payload->>'endereco'), ''), 200),
    left(nullif(trim(p_payload->>'numero'), ''), 20),
    left(nullif(trim(p_payload->>'complemento'), ''), 120),
    left(nullif(trim(p_payload->>'bairro'), ''), 120),
    left(nullif(trim(p_payload->>'observacoes'), ''), 2000),
    left(nullif(trim(p_payload->>'encaminhado_por'), ''), 120),
    left(nullif(trim(p_payload->>'onde_nos_conheceu'), ''), 80)
  )
  RETURNING id INTO v_intake_id;

  UPDATE public.patient_invites
    SET uses = uses + 1
  WHERE token = p_token;

  -- Log de sucesso (best-effort, não propaga erro)
  BEGIN
    INSERT INTO public.patient_invite_attempts (token, ok, client_info, owner_id, tenant_id)
    VALUES (p_token, true, v_clean_info, v_owner_id, v_tenant_id);
  EXCEPTION WHEN OTHERS THEN NULL; END;

  RETURN v_intake_id;
END;
$_$;


--
-- Name: FUNCTION create_patient_intake_request_v2(p_token text, p_payload jsonb, p_client_info text); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.create_patient_intake_request_v2(p_token text, p_payload jsonb, p_client_info text) IS 'Hardened 2026-04-18: valida active/expires/max_uses + incrementa uses; sanitiza todos os campos (trim, length, regex); resolve tenant_id; rejeita notas_internas; exige consent=true; registra cada tentativa em patient_invite_attempts (A#24).';


--
-- Name: create_support_session(uuid, integer); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.create_support_session(p_tenant_id uuid, p_ttl_minutes integer DEFAULT 60) RETURNS json
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_admin_id  uuid;
  v_role      text;
  v_token     text;
  v_expires   timestamp with time zone;
  v_session   support_sessions;
BEGIN
  -- Verifica autentica????o
  v_admin_id := auth.uid();
  IF v_admin_id IS NULL THEN
    RAISE EXCEPTION 'N??o autenticado.' USING ERRCODE = 'P0001';
  END IF;

  -- Verifica role saas_admin
  SELECT role INTO v_role
    FROM public.profiles
   WHERE id = v_admin_id;

  IF v_role <> 'saas_admin' THEN
    RAISE EXCEPTION 'Acesso negado. Somente saas_admin pode criar sess??es de suporte.'
      USING ERRCODE = 'P0002';
  END IF;

  -- Valida TTL (1 a 120 minutos)
  IF p_ttl_minutes < 1 OR p_ttl_minutes > 120 THEN
    RAISE EXCEPTION 'TTL inv??lido. Use entre 1 e 120 minutos.'
      USING ERRCODE = 'P0003';
  END IF;

  -- Valida tenant
  IF NOT EXISTS (SELECT 1 FROM public.tenants WHERE id = p_tenant_id) THEN
    RAISE EXCEPTION 'Tenant n??o encontrado.'
      USING ERRCODE = 'P0004';
  END IF;

  -- Gera token ??nico (64 chars hex, sem pgcrypto)
  v_token   := replace(gen_random_uuid()::text, '-', '') || replace(gen_random_uuid()::text, '-', '');
  v_expires := now() + (p_ttl_minutes || ' minutes')::interval;

  -- Insere sess??o
  INSERT INTO public.support_sessions (tenant_id, admin_id, token, expires_at)
  VALUES (p_tenant_id, v_admin_id, v_token, v_expires)
  RETURNING * INTO v_session;

  RETURN json_build_object(
    'token',      v_session.token,
    'expires_at', v_session.expires_at,
    'session_id', v_session.id
  );
END;
$$;


--
-- Name: therapist_payouts; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.therapist_payouts (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    period_start date NOT NULL,
    period_end date NOT NULL,
    total_sessions integer DEFAULT 0 NOT NULL,
    gross_amount numeric(10,2) DEFAULT 0 NOT NULL,
    clinic_fee_total numeric(10,2) DEFAULT 0 NOT NULL,
    net_amount numeric(10,2) DEFAULT 0 NOT NULL,
    status text DEFAULT 'pending'::text NOT NULL,
    paid_at timestamp with time zone,
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT therapist_payouts_clinic_fee_total_check CHECK ((clinic_fee_total >= (0)::numeric)),
    CONSTRAINT therapist_payouts_gross_amount_check CHECK ((gross_amount >= (0)::numeric)),
    CONSTRAINT therapist_payouts_net_amount_check CHECK ((net_amount >= (0)::numeric)),
    CONSTRAINT therapist_payouts_period_chk CHECK ((period_end >= period_start)),
    CONSTRAINT therapist_payouts_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'paid'::text, 'cancelled'::text])))
);


--
-- Name: create_therapist_payout(uuid, uuid, date, date); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.create_therapist_payout(p_tenant_id uuid, p_therapist_id uuid, p_period_start date, p_period_end date) RETURNS public.therapist_payouts
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_payout        public.therapist_payouts%ROWTYPE;
  v_total_sessions INTEGER;
  v_gross         NUMERIC(10,2);
  v_clinic_fee    NUMERIC(10,2);
  v_net           NUMERIC(10,2);
BEGIN
  -- ?????? Verifica????o de permiss??o ????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
  -- Apenas o pr??prio terapeuta ou o tenant_admin pode criar o repasse
  IF auth.uid() <> p_therapist_id AND NOT public.is_tenant_admin(p_tenant_id) THEN
    RAISE EXCEPTION 'Sem permiss??o para criar repasse para este terapeuta.';
  END IF;

  -- ?????? Verifica se j?? existe repasse para o mesmo per??odo ???????????????????????????????????????????????????
  IF EXISTS (
    SELECT 1 FROM public.therapist_payouts
    WHERE owner_id    = p_therapist_id
      AND tenant_id   = p_tenant_id
      AND period_start = p_period_start
      AND period_end   = p_period_end
      AND status <> 'cancelled'
  ) THEN
    RAISE EXCEPTION
      'J?? existe um repasse ativo para o per??odo % a % deste terapeuta.',
      p_period_start, p_period_end;
  END IF;

  -- ?????? Agrega os financial_records eleg??veis ??????????????????????????????????????????????????????????????????????????????????????????
  -- Eleg??veis: paid, receita, owner=terapeuta, tenant correto, paid_at no per??odo,
  --            n??o soft-deleted, ainda n??o vinculados a nenhum payout.
  SELECT
    COUNT(*)                AS total_sessions,
    COALESCE(SUM(amount),        0) AS gross_amount,
    COALESCE(SUM(clinic_fee_amount), 0) AS clinic_fee_total,
    COALESCE(SUM(net_amount),    0) AS net_amount
  INTO
    v_total_sessions, v_gross, v_clinic_fee, v_net
  FROM public.financial_records fr
  WHERE fr.owner_id    = p_therapist_id
    AND fr.tenant_id   = p_tenant_id
    AND fr.type        = 'receita'
    AND fr.status      = 'paid'
    AND fr.deleted_at  IS NULL
    AND fr.paid_at::DATE BETWEEN p_period_start AND p_period_end
    AND NOT EXISTS (
      SELECT 1 FROM public.therapist_payout_records tpr
      WHERE tpr.financial_record_id = fr.id
    );

  -- Sem registros eleg??veis ??? n??o criar payout vazio
  IF v_total_sessions = 0 THEN
    RAISE EXCEPTION
      'Nenhum registro financeiro eleg??vel encontrado para o per??odo % a %.',
      p_period_start, p_period_end;
  END IF;

  -- ?????? Cria o repasse ???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
  INSERT INTO public.therapist_payouts (
    owner_id,
    tenant_id,
    period_start,
    period_end,
    total_sessions,
    gross_amount,
    clinic_fee_total,
    net_amount,
    status
  ) VALUES (
    p_therapist_id,
    p_tenant_id,
    p_period_start,
    p_period_end,
    v_total_sessions,
    v_gross,
    v_clinic_fee,
    v_net,
    'pending'
  )
  RETURNING * INTO v_payout;

  -- ?????? Vincula os financial_records ao repasse ????????????????????????????????????????????????????????????????????????????????????
  INSERT INTO public.therapist_payout_records (payout_id, financial_record_id)
  SELECT v_payout.id, fr.id
  FROM public.financial_records fr
  WHERE fr.owner_id    = p_therapist_id
    AND fr.tenant_id   = p_tenant_id
    AND fr.type        = 'receita'
    AND fr.status      = 'paid'
    AND fr.deleted_at  IS NULL
    AND fr.paid_at::DATE BETWEEN p_period_start AND p_period_end
    AND NOT EXISTS (
      SELECT 1 FROM public.therapist_payout_records tpr
      WHERE tpr.financial_record_id = fr.id
    );

  RETURN v_payout;
END;
$$;


--
-- Name: FUNCTION create_therapist_payout(p_tenant_id uuid, p_therapist_id uuid, p_period_start date, p_period_end date); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.create_therapist_payout(p_tenant_id uuid, p_therapist_id uuid, p_period_start date, p_period_end date) IS 'Cria um repasse para o terapeuta com todos os financial_records paid+receita do per??odo que ainda n??o estejam vinculados a outro repasse. Lan??a exce????o se n??o houver registros eleg??veis ou se j?? houver repasse ativo no per??odo.';


--
-- Name: current_member_id(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.current_member_id(p_tenant_id uuid) RETURNS uuid
    LANGUAGE sql STABLE
    AS $$
  select tm.id
  from public.tenant_members tm
  where tm.tenant_id = p_tenant_id
    and tm.user_id = auth.uid()
  limit 1
$$;


--
-- Name: current_member_role(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.current_member_role(p_tenant_id uuid) RETURNS text
    LANGUAGE sql STABLE
    AS $$
  select tm.role
  from public.tenant_members tm
  where tm.tenant_id = p_tenant_id
    and tm.user_id = auth.uid()
  limit 1
$$;


--
-- Name: debit_addon_credit(uuid, text, uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.debit_addon_credit(p_tenant_id uuid, p_addon_type text, p_queue_id uuid DEFAULT NULL::uuid, p_description text DEFAULT 'Consumo'::text) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_credit         addon_credits%ROWTYPE;
  v_balance_before INTEGER;
  v_balance_after  INTEGER;
BEGIN
  -- Lock e leitura
  SELECT * INTO v_credit
  FROM addon_credits
  WHERE tenant_id = p_tenant_id AND addon_type = p_addon_type AND is_active = true
  FOR UPDATE;

  IF NOT FOUND THEN
    RETURN jsonb_build_object('success', false, 'reason', 'no_credits', 'balance', 0);
  END IF;

  -- Verifica saldo
  IF v_credit.balance <= 0 THEN
    RETURN jsonb_build_object('success', false, 'reason', 'insufficient_balance', 'balance', 0);
  END IF;

  -- Verifica rate limit di??rio
  IF v_credit.daily_limit IS NOT NULL THEN
    -- Reset se passou do dia
    IF v_credit.daily_reset_at IS NULL OR v_credit.daily_reset_at < date_trunc('day', now()) THEN
      UPDATE addon_credits SET daily_used = 0, daily_reset_at = date_trunc('day', now()) + interval '1 day' WHERE id = v_credit.id;
      v_credit.daily_used := 0;
    END IF;

    IF v_credit.daily_used >= v_credit.daily_limit THEN
      RETURN jsonb_build_object('success', false, 'reason', 'daily_limit_reached', 'balance', v_credit.balance);
    END IF;
  END IF;

  -- Verifica rate limit hor??rio
  IF v_credit.hourly_limit IS NOT NULL THEN
    IF v_credit.hourly_reset_at IS NULL OR v_credit.hourly_reset_at < date_trunc('hour', now()) THEN
      UPDATE addon_credits SET hourly_used = 0, hourly_reset_at = date_trunc('hour', now()) + interval '1 hour' WHERE id = v_credit.id;
      v_credit.hourly_used := 0;
    END IF;

    IF v_credit.hourly_used >= v_credit.hourly_limit THEN
      RETURN jsonb_build_object('success', false, 'reason', 'hourly_limit_reached', 'balance', v_credit.balance);
    END IF;
  END IF;

  -- Verifica expira????o
  IF v_credit.expires_at IS NOT NULL AND v_credit.expires_at < now() THEN
    RETURN jsonb_build_object('success', false, 'reason', 'credits_expired', 'balance', v_credit.balance);
  END IF;

  v_balance_before := v_credit.balance;
  v_balance_after  := v_credit.balance - 1;

  -- Debita
  UPDATE addon_credits
  SET balance        = v_balance_after,
      total_consumed = total_consumed + 1,
      daily_used     = COALESCE(daily_used, 0) + 1,
      hourly_used    = COALESCE(hourly_used, 0) + 1,
      updated_at     = now()
  WHERE id = v_credit.id;

  -- Registra transa????o
  INSERT INTO addon_transactions (
    tenant_id, addon_type, type, amount,
    balance_before, balance_after,
    queue_id, description
  ) VALUES (
    p_tenant_id, p_addon_type, 'consume', -1,
    v_balance_before, v_balance_after,
    p_queue_id, p_description
  );

  RETURN jsonb_build_object(
    'success', true,
    'balance_before', v_balance_before,
    'balance_after', v_balance_after
  );
END;
$$;


--
-- Name: FUNCTION debit_addon_credit(p_tenant_id uuid, p_addon_type text, p_queue_id uuid, p_description text); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.debit_addon_credit(p_tenant_id uuid, p_addon_type text, p_queue_id uuid, p_description text) IS 'Debita 1 cr??dito de add-on. Verifica saldo, rate limits e expira????o.';


--
-- Name: deduct_whatsapp_credits(uuid, integer, bigint, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.deduct_whatsapp_credits(p_tenant_id uuid, p_amount integer, p_conversation_message_id bigint DEFAULT NULL::bigint, p_note text DEFAULT NULL::text) RETURNS integer
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_new_balance INT;
    v_row RECORD;
BEGIN
    IF p_amount <= 0 THEN
        RAISE EXCEPTION 'amount must be positive';
    END IF;

    -- Lock a linha e valida saldo
    SELECT balance, low_balance_threshold
        INTO v_row
        FROM public.whatsapp_credits_balance
        WHERE tenant_id = p_tenant_id
        FOR UPDATE;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'insufficient_credits';
    END IF;
    IF v_row.balance < p_amount THEN
        RAISE EXCEPTION 'insufficient_credits';
    END IF;

    UPDATE public.whatsapp_credits_balance
        SET balance = balance - p_amount,
            lifetime_used = lifetime_used + p_amount
        WHERE tenant_id = p_tenant_id
        RETURNING balance INTO v_new_balance;

    INSERT INTO public.whatsapp_credits_transactions
        (tenant_id, kind, amount, balance_after, conversation_message_id, note)
    VALUES
        (p_tenant_id, 'usage', -p_amount, v_new_balance, p_conversation_message_id, p_note);

    RETURN v_new_balance;
END;
$$;


--
-- Name: delete_commitment_full(uuid, uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.delete_commitment_full(p_tenant_id uuid, p_commitment_id uuid) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
declare
  v_is_native boolean;
  v_fields int := 0;
  v_logs int := 0;
  v_parent int := 0;
begin
  if auth.uid() is null then
    raise exception 'Not authenticated';
  end if;

  if not exists (
    select 1
    from public.tenant_members tm
    where tm.tenant_id = p_tenant_id
      and tm.user_id = auth.uid()
      and tm.status = 'active'
  ) then
    raise exception 'Not allowed';
  end if;

  select dc.is_native
    into v_is_native
  from public.determined_commitments dc
  where dc.tenant_id = p_tenant_id
    and dc.id = p_commitment_id;

  if v_is_native is null then
    raise exception 'Commitment not found';
  end if;

  if v_is_native = true then
    raise exception 'Cannot delete native commitment';
  end if;

  delete from public.determined_commitment_fields
   where tenant_id = p_tenant_id
     and commitment_id = p_commitment_id;
  get diagnostics v_fields = row_count;

  delete from public.commitment_time_logs
   where tenant_id = p_tenant_id
     and commitment_id = p_commitment_id;
  get diagnostics v_logs = row_count;

  delete from public.determined_commitments
   where tenant_id = p_tenant_id
     and id = p_commitment_id;
  get diagnostics v_parent = row_count;

  if v_parent <> 1 then
    raise exception 'Parent not deleted (RLS/owner issue).';
  end if;

  return jsonb_build_object(
    'ok', true,
    'deleted', jsonb_build_object(
      'fields', v_fields,
      'logs', v_logs,
      'commitment', v_parent
    )
  );
end;
$$;


--
-- Name: delete_determined_commitment(uuid, uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.delete_determined_commitment(p_tenant_id uuid, p_commitment_id uuid) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
declare
  v_is_native boolean;
  v_fields_deleted int := 0;
  v_logs_deleted int := 0;
  v_commitment_deleted int := 0;
begin
  if auth.uid() is null then
    raise exception 'Not authenticated';
  end if;

  if not exists (
    select 1
    from public.tenant_members tm
    where tm.tenant_id = p_tenant_id
      and tm.user_id = auth.uid()
      and tm.status = 'active'
  ) then
    raise exception 'Not allowed';
  end if;

  select dc.is_native
    into v_is_native
  from public.determined_commitments dc
  where dc.tenant_id = p_tenant_id
    and dc.id = p_commitment_id;

  if v_is_native is null then
    raise exception 'Commitment not found for tenant';
  end if;

  if v_is_native = true then
    raise exception 'Cannot delete native commitment';
  end if;

  delete from public.determined_commitment_fields f
   where f.tenant_id = p_tenant_id
     and f.commitment_id = p_commitment_id;
  get diagnostics v_fields_deleted = row_count;

  delete from public.commitment_time_logs l
   where l.tenant_id = p_tenant_id
     and l.commitment_id = p_commitment_id;
  get diagnostics v_logs_deleted = row_count;

  delete from public.determined_commitments dc
   where dc.tenant_id = p_tenant_id
     and dc.id = p_commitment_id;
  get diagnostics v_commitment_deleted = row_count;

  if v_commitment_deleted <> 1 then
    raise exception 'Delete did not remove the commitment (tenant mismatch?)';
  end if;

  return jsonb_build_object(
    'ok', true,
    'tenant_id', p_tenant_id,
    'commitment_id', p_commitment_id,
    'deleted', jsonb_build_object(
      'fields', v_fields_deleted,
      'logs', v_logs_deleted,
      'commitment', v_commitment_deleted
    )
  );
end;
$$;


--
-- Name: delete_plan_safe(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.delete_plan_safe(p_plan_id uuid) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_active_count int;
  v_plan_key     text;
BEGIN
  IF auth.uid() IS NULL THEN
    RAISE EXCEPTION 'Não autenticado' USING ERRCODE = '28000';
  END IF;

  IF NOT public.is_saas_admin() THEN
    RAISE EXCEPTION 'Apenas saas_admin pode deletar planos' USING ERRCODE = '42501';
  END IF;

  IF p_plan_id IS NULL THEN
    RAISE EXCEPTION 'plan_id obrigatório' USING ERRCODE = '22023';
  END IF;

  SELECT key INTO v_plan_key FROM public.plans WHERE id = p_plan_id;
  IF v_plan_key IS NULL THEN
    RAISE EXCEPTION 'plano não encontrado' USING ERRCODE = '22023';
  END IF;

  SELECT COUNT(*) INTO v_active_count
  FROM public.subscriptions
  WHERE plan_id = p_plan_id
    AND status = 'active';

  IF v_active_count > 0 THEN
    RAISE EXCEPTION 'Plano % tem % assinatura(s) ativa(s); migre os tenants antes de deletar.',
      v_plan_key, v_active_count
      USING ERRCODE = 'P0001';
  END IF;

  -- desativa preços ativos antes de deletar
  UPDATE public.plan_prices
     SET is_active = false,
         active_to = now()
   WHERE plan_id = p_plan_id
     AND is_active = true;

  DELETE FROM public.plans WHERE id = p_plan_id;

  RETURN jsonb_build_object(
    'deleted',  true,
    'plan_key', v_plan_key
  );
END;
$$;


--
-- Name: dev_list_auth_users(integer); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.dev_list_auth_users(p_limit integer DEFAULT 50) RETURNS TABLE(id uuid, email text, created_at timestamp with time zone)
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
begin
  -- s?? saas_admin pode ver
  if not exists (
    select 1
    from public.profiles p
    where p.id = auth.uid()
      and p.role = 'saas_admin'
  ) then
    return;
  end if;

  return query
  select
    u.id,
    u.email,
    u.created_at
  from auth.users u
  order by u.created_at desc
  limit greatest(1, least(coalesce(p_limit, 50), 500));
end;
$$;


--
-- Name: dev_list_custom_users(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.dev_list_custom_users() RETURNS TABLE(user_id uuid, email text, created_at timestamp with time zone, global_role text, tenant_role text, tenant_id uuid, password_dev text, kind text)
    LANGUAGE sql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
  with base as (
    select
      u.id as user_id,
      lower(u.email) as email,
      u.created_at
    from auth.users u
    where lower(u.email) not in (
      'clinic@agenciapsi.com.br',
      'therapist@agenciapsi.com.br',
      'patient@agenciapsi.com.br',
      'saas@agenciapsi.com.br'
    )
  ),
  prof as (
    select p.id, p.role as global_role
    from public.profiles p
  ),
  last_membership as (
    select distinct on (tm.user_id)
      tm.user_id,
      tm.tenant_id,
      tm.role as tenant_role,
      tm.created_at
    from public.tenant_members tm
    where tm.status = 'active'
    order by tm.user_id, tm.created_at desc
  )
  select
    b.user_id,
    b.email,
    b.created_at,
    pr.global_role,
    lm.tenant_role,
    lm.tenant_id,
    dc.password_dev,
    dc.kind
  from base b
  left join prof pr on pr.id = b.user_id
  left join last_membership lm on lm.user_id = b.user_id
  left join public.dev_user_credentials dc on lower(dc.email) = b.email
  order by b.created_at desc;
$$;


--
-- Name: dev_list_intent_leads(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.dev_list_intent_leads() RETURNS TABLE(email text, last_intent_at timestamp with time zone, plan_key text, billing_interval text, status text, tenant_id uuid)
    LANGUAGE sql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
  select
    lower(si.email) as email,
    max(si.created_at) as last_intent_at,
    (array_agg(si.plan_key order by si.created_at desc))[1] as plan_key,
    (array_agg(si.interval order by si.created_at desc))[1] as billing_interval,
    (array_agg(si.status order by si.created_at desc))[1] as status,
    (array_agg(si.tenant_id order by si.created_at desc))[1] as tenant_id
  from public.subscription_intents si
  where si.email is not null
    and not exists (
      select 1
      from auth.users au
      where lower(au.email) = lower(si.email)
    )
  group by lower(si.email)
  order by max(si.created_at) desc;
$$;


--
-- Name: dev_public_debug_snapshot(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.dev_public_debug_snapshot() RETURNS TABLE(users_total integer, tenants_total integer, intents_new_total integer, latest_intents jsonb)
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $_$
declare
  v_latest jsonb;
begin
  select jsonb_agg(
    jsonb_build_object(
      'created_at', si.created_at,
      'email_masked',
        regexp_replace(lower(si.email), '(^.).*(@.*$)', '\1***\2'),
      'plan_key', si.plan_key,
      'status', si.status
    )
    order by si.created_at desc
  )
  into v_latest
  from (
    select si.*
    from public.subscription_intents si
    where si.email is not null
    order by si.created_at desc
    limit 5
  ) si;

  return query
  select
    (select count(*)::int from auth.users) as users_total,
    (select count(*)::int from public.tenants) as tenants_total,
    (select count(*)::int from public.subscription_intents where status = 'new') as intents_new_total,
    coalesce(v_latest, '[]'::jsonb) as latest_intents;
end;
$_$;


--
-- Name: dev_set_updated_at(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.dev_set_updated_at() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  NEW.updated_at := now();
  RETURN NEW;
END;
$$;


--
-- Name: ensure_personal_tenant(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.ensure_personal_tenant() RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
DECLARE
  v_uid      uuid;
  v_existing uuid;
BEGIN
  v_uid := auth.uid();
  IF v_uid IS NULL THEN
    RAISE EXCEPTION 'Not authenticated';
  END IF;

  SELECT tm.tenant_id INTO v_existing
  FROM public.tenant_members tm
  JOIN public.tenants t ON t.id = tm.tenant_id
  WHERE tm.user_id = v_uid
    AND tm.status = 'active'
    AND t.kind IN ('therapist', 'saas')
  ORDER BY tm.created_at DESC
  LIMIT 1;

  IF v_existing IS NOT NULL THEN
    RETURN v_existing;
  END IF;

  RETURN public.provision_account_tenant(v_uid, 'therapist');
END;
$$;


--
-- Name: ensure_personal_tenant_for_user(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.ensure_personal_tenant_for_user(p_user_id uuid) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_uid uuid;
  v_existing uuid;
  v_tenant uuid;
  v_email text;
  v_name text;
begin
  v_uid := p_user_id;
  if v_uid is null then
    raise exception 'Missing user id';
  end if;

  -- s?? considera tenant pessoal (kind='saas')
  select tm.tenant_id
    into v_existing
  from public.tenant_members tm
  join public.tenants t on t.id = tm.tenant_id
  where tm.user_id = v_uid
    and tm.status = 'active'
    and t.kind = 'saas'
  order by tm.created_at desc
  limit 1;

  if v_existing is not null then
    return v_existing;
  end if;

  select email into v_email
  from auth.users
  where id = v_uid;

  v_name := coalesce(split_part(v_email, '@', 1), 'Conta');

  insert into public.tenants (name, kind, created_at)
  values (v_name || ' (Pessoal)', 'saas', now())
  returning id into v_tenant;

  insert into public.tenant_members (tenant_id, user_id, role, status, created_at)
  values (v_tenant, v_uid, 'tenant_admin', 'active', now());

  return v_tenant;
end;
$$;


--
-- Name: export_patient_data(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.export_patient_data(p_patient_id uuid) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'pg_temp'
    AS $$
DECLARE
    v_patient RECORD;
    v_tenant_id UUID;
    v_caller UUID;
    v_is_member BOOLEAN;
    v_result JSONB;
BEGIN
    v_caller := auth.uid();
    IF v_caller IS NULL THEN
        RAISE EXCEPTION 'Autenticacao obrigatoria' USING ERRCODE = '28000';
    END IF;

    -- carrega paciente
    SELECT * INTO v_patient FROM public.patients WHERE id = p_patient_id;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Paciente nao encontrado' USING ERRCODE = 'P0002';
    END IF;

    v_tenant_id := v_patient.tenant_id;

    -- verifica se caller e membro ativo do tenant do paciente
    SELECT EXISTS (
        SELECT 1 FROM public.tenant_members tm
        WHERE tm.tenant_id = v_tenant_id
          AND tm.user_id = v_caller
          AND tm.status = 'active'
    ) OR public.is_saas_admin() INTO v_is_member;

    IF NOT v_is_member THEN
        RAISE EXCEPTION 'Sem permissao para exportar dados deste paciente' USING ERRCODE = '42501';
    END IF;

    -- monta o payload
    v_result := jsonb_build_object(
        'export_metadata', jsonb_build_object(
            'generated_at', now(),
            'generated_by', v_caller,
            'tenant_id', v_tenant_id,
            'patient_id', p_patient_id,
            'lgpd_basis', 'Art. 18, II - portabilidade dos dados do titular',
            'controller', 'AgenciaPSI - Clinica responsavel',
            'format_version', '1.0'
        ),
        'paciente', to_jsonb(v_patient),
        'contatos', COALESCE((
            SELECT jsonb_agg(to_jsonb(pc) ORDER BY pc.created_at)
            FROM public.patient_contacts pc
            WHERE pc.patient_id = p_patient_id
        ), '[]'::jsonb),
        'contatos_apoio', COALESCE((
            SELECT jsonb_agg(to_jsonb(psc) ORDER BY psc.created_at)
            FROM public.patient_support_contacts psc
            WHERE psc.patient_id = p_patient_id
        ), '[]'::jsonb),
        'historico_status', COALESCE((
            SELECT jsonb_agg(to_jsonb(psh) ORDER BY psh.alterado_em)
            FROM public.patient_status_history psh
            WHERE psh.patient_id = p_patient_id
        ), '[]'::jsonb),
        'timeline', COALESCE((
            SELECT jsonb_agg(to_jsonb(pt) ORDER BY pt.ocorrido_em)
            FROM public.patient_timeline pt
            WHERE pt.patient_id = p_patient_id
        ), '[]'::jsonb),
        'descontos', COALESCE((
            SELECT jsonb_agg(to_jsonb(pd) ORDER BY pd.created_at)
            FROM public.patient_discounts pd
            WHERE pd.patient_id = p_patient_id
        ), '[]'::jsonb),
        'eventos_agenda', COALESCE((
            SELECT jsonb_agg(
                jsonb_build_object(
                    'id', ae.id,
                    'tipo', ae.tipo,
                    'inicio_em', ae.inicio_em,
                    'fim_em', ae.fim_em,
                    'status', ae.status,
                    'observacoes', ae.observacoes,
                    'created_at', ae.created_at
                ) ORDER BY ae.inicio_em
            )
            FROM public.agenda_eventos ae
            WHERE ae.patient_id = p_patient_id
        ), '[]'::jsonb),
        'registros_financeiros', COALESCE((
            SELECT jsonb_agg(
                jsonb_build_object(
                    'id', fr.id,
                    'amount', fr.amount,
                    'discount_amount', fr.discount_amount,
                    'final_amount', fr.final_amount,
                    'status', fr.status,
                    'due_date', fr.due_date,
                    'paid_at', fr.paid_at,
                    'payment_method', fr.payment_method,
                    'notes', fr.notes,
                    'created_at', fr.created_at
                ) ORDER BY fr.created_at
            )
            FROM public.financial_records fr
            WHERE fr.patient_id = p_patient_id
        ), '[]'::jsonb),
        'documentos', COALESCE((
            SELECT jsonb_agg(
                jsonb_build_object(
                    'id', d.id,
                    'nome_original', d.nome_original,
                    'tipo_documento', d.tipo_documento,
                    'categoria', d.categoria,
                    'descricao', d.descricao,
                    'mime_type', d.mime_type,
                    'tamanho_bytes', d.tamanho_bytes,
                    'status_revisao', d.status_revisao,
                    'visibilidade', d.visibilidade,
                    'uploaded_at', d.uploaded_at,
                    'created_at', d.created_at
                ) ORDER BY d.created_at
            )
            FROM public.documents d
            WHERE d.patient_id = p_patient_id AND d.deleted_at IS NULL
        ), '[]'::jsonb),
        'notificacoes_enviadas', COALESCE((
            SELECT jsonb_agg(
                jsonb_build_object(
                    'id', nl.id,
                    'channel', nl.channel,
                    'template_key', nl.template_key,
                    'recipient_address', nl.recipient_address,
                    'status', nl.status,
                    'sent_at', nl.sent_at,
                    'delivered_at', nl.delivered_at,
                    'read_at', nl.read_at,
                    'failure_reason', nl.failure_reason,
                    'created_at', nl.created_at
                ) ORDER BY nl.created_at
            )
            FROM public.notification_logs nl
            WHERE nl.patient_id = p_patient_id
        ), '[]'::jsonb),
        'audit_trail', COALESCE((
            SELECT jsonb_agg(
                jsonb_build_object(
                    'id', al.id,
                    'action', al.action,
                    'entity_type', al.entity_type,
                    'changed_fields', al.changed_fields,
                    'user_id', al.user_id,
                    'created_at', al.created_at
                ) ORDER BY al.created_at
            )
            FROM public.audit_logs al
            WHERE al.tenant_id = v_tenant_id
              AND al.entity_type = 'patients'
              AND al.entity_id = p_patient_id::text
        ), '[]'::jsonb),
        'acessos_a_documentos', COALESCE((
            SELECT jsonb_agg(
                jsonb_build_object(
                    'id', dal.id,
                    'documento_id', dal.documento_id,
                    'acao', dal.acao,
                    'user_id', dal.user_id,
                    'acessado_em', dal.acessado_em
                ) ORDER BY dal.acessado_em
            )
            FROM public.document_access_logs dal
            WHERE dal.documento_id IN (
                SELECT id FROM public.documents WHERE patient_id = p_patient_id
            )
        ), '[]'::jsonb),
        'grupos', COALESCE((
            SELECT jsonb_agg(jsonb_build_object('patient_group_id', pgp.patient_group_id))
            FROM public.patient_group_patient pgp
            WHERE pgp.patient_id = p_patient_id
        ), '[]'::jsonb),
        'tags', COALESCE((
            SELECT jsonb_agg(jsonb_build_object('tag_id', ppt.tag_id))
            FROM public.patient_patient_tag ppt
            WHERE ppt.patient_id = p_patient_id
        ), '[]'::jsonb)
    );

    -- registra o export como evento auditavel
    INSERT INTO public.audit_logs (
        tenant_id, user_id, entity_type, entity_id, action,
        old_values, new_values, changed_fields, metadata
    ) VALUES (
        v_tenant_id, v_caller, 'patients', p_patient_id::text, 'update',
        NULL, NULL, ARRAY['__lgpd_export__'],
        jsonb_build_object(
            'action_kind', 'lgpd_export',
            'lgpd_basis', 'Art. 18, II',
            'patient_name', v_patient.nome_completo
        )
    );

    RETURN v_result;
END;
$$;


--
-- Name: FUNCTION export_patient_data(p_patient_id uuid); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.export_patient_data(p_patient_id uuid) IS 'LGPD Art. 18, II - exporta todos os dados do paciente em jsonb portavel. Registra evento em audit_logs.';


--
-- Name: fanout_inbound_message_to_notifications(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.fanout_inbound_message_to_notifications() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'pg_temp'
    AS $$
DECLARE
    v_target_user UUID;
    v_title TEXT;
    v_detail TEXT;
    v_initials TEXT;
    v_deeplink TEXT;
    v_patient_name TEXT;
    v_payload JSONB;
BEGIN
    -- so processa inbound
    IF NEW.direction <> 'inbound' THEN
        RETURN NEW;
    END IF;

    -- busca nome do paciente (se vinculado)
    IF NEW.patient_id IS NOT NULL THEN
        SELECT nome_completo INTO v_patient_name FROM public.patients WHERE id = NEW.patient_id;
    END IF;

    -- titulo e detalhes
    v_title := COALESCE(v_patient_name, NEW.from_number, 'Desconhecido');
    v_detail := COALESCE(left(NEW.body, 100), '[mensagem sem texto]');

    -- iniciais
    IF v_patient_name IS NOT NULL THEN
        v_initials := upper(left(v_patient_name, 1)) ||
                      COALESCE(upper(left(split_part(v_patient_name, ' ', 2), 1)), '');
    ELSE
        v_initials := '?';
    END IF;

    -- deeplink para a pagina de conversas (clinic padrao; therapist tambem funciona via mesma rota na area dele)
    v_deeplink := '/admin/conversas';

    v_payload := jsonb_build_object(
        'title', v_title,
        'detail', v_detail,
        'avatar_initials', v_initials,
        'deeplink', v_deeplink,
        'channel', NEW.channel,
        'conversation_message_id', NEW.id,
        'patient_id', NEW.patient_id,
        'from_number', NEW.from_number
    );

    -- ─── decide destinatarios ─────────────────────────────────────────────

    -- Caso 1: paciente vinculado e tem responsible_member_id
    IF NEW.patient_id IS NOT NULL THEN
        SELECT tm.user_id INTO v_target_user
        FROM public.patients p
        JOIN public.tenant_members tm ON tm.id = p.responsible_member_id
        WHERE p.id = NEW.patient_id
          AND tm.status = 'active'
        LIMIT 1;

        IF v_target_user IS NOT NULL THEN
            INSERT INTO public.notifications (owner_id, tenant_id, type, ref_id, ref_table, payload)
            VALUES (v_target_user, NEW.tenant_id, 'inbound_message', NULL, 'conversation_messages', v_payload);
            RETURN NEW;
        END IF;
    END IF;

    -- Caso 2: fallback — fan-out pra todos tenant_admin/clinic_admin/therapist ativos
    INSERT INTO public.notifications (owner_id, tenant_id, type, ref_id, ref_table, payload)
    SELECT tm.user_id, NEW.tenant_id, 'inbound_message', NULL, 'conversation_messages', v_payload
    FROM public.tenant_members tm
    WHERE tm.tenant_id = NEW.tenant_id
      AND tm.status = 'active'
      AND tm.role IN ('clinic_admin', 'tenant_admin', 'therapist');

    RETURN NEW;
END;
$$;


--
-- Name: FUNCTION fanout_inbound_message_to_notifications(); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.fanout_inbound_message_to_notifications() IS 'Cria registros em notifications pra members apropriados quando chega mensagem inbound. Respeita responsible_member do paciente.';


--
-- Name: faq_votar(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.faq_votar(faq_id uuid) RETURNS void
    LANGUAGE sql SECURITY DEFINER
    AS $$
  update public.saas_faq
  set votos = votos + 1,
      updated_at = now()
  where id = faq_id
    and ativo = true;
$$;


--
-- Name: financial_records_inject_tenant(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.financial_records_inject_tenant() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF NEW.tenant_id IS NULL AND NEW.owner_id IS NOT NULL THEN
    SELECT tm.tenant_id INTO NEW.tenant_id
    FROM public.tenant_members tm
    WHERE tm.user_id = NEW.owner_id AND tm.status = 'active'
    ORDER BY tm.created_at DESC
    LIMIT 1;
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: first_response_by_therapist(uuid, timestamp with time zone, timestamp with time zone); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.first_response_by_therapist(p_tenant_id uuid, p_from timestamp with time zone DEFAULT (now() - '30 days'::interval), p_to timestamp with time zone DEFAULT now()) RETURNS TABLE(therapist_id uuid, runs_count integer, avg_seconds integer, median_seconds integer)
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
    SELECT
        r.responder_id AS therapist_id,
        COUNT(*)::INT AS runs_count,
        AVG(r.response_seconds)::INT AS avg_seconds,
        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY r.response_seconds)::INT AS median_seconds
    FROM public._first_response_runs(p_tenant_id, p_from, p_to) r
    WHERE r.responder_id IS NOT NULL
    GROUP BY r.responder_id
    ORDER BY avg_seconds ASC;
$$;


--
-- Name: FUNCTION first_response_by_therapist(p_tenant_id uuid, p_from timestamp with time zone, p_to timestamp with time zone); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.first_response_by_therapist(p_tenant_id uuid, p_from timestamp with time zone, p_to timestamp with time zone) IS 'Ranking de tempo medio de 1a resposta por terapeuta atribuido.';


--
-- Name: first_response_evolution(uuid, timestamp with time zone, timestamp with time zone, integer, uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.first_response_evolution(p_tenant_id uuid, p_from timestamp with time zone DEFAULT (now() - '30 days'::interval), p_to timestamp with time zone DEFAULT now(), p_bucket_days integer DEFAULT 7, p_therapist_id uuid DEFAULT NULL::uuid) RETURNS TABLE(bucket_start timestamp with time zone, runs_count integer, avg_seconds integer)
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
    WITH runs AS (
        SELECT r.inbound_started_at, r.response_seconds
        FROM public._first_response_runs(p_tenant_id, p_from, p_to) r
        WHERE (p_therapist_id IS NULL OR r.responder_id = p_therapist_id)
    ),
    bucketed AS (
        SELECT
            -- Janela alinhada a p_from: bucket_index * N dias + p_from
            p_from + (
                FLOOR(EXTRACT(EPOCH FROM (inbound_started_at - p_from)) / (p_bucket_days * 86400))::INT
                * p_bucket_days * interval '1 day'
            ) AS bucket_start,
            response_seconds
        FROM runs
    )
    SELECT
        bucket_start,
        COUNT(*)::INT AS runs_count,
        AVG(response_seconds)::INT AS avg_seconds
    FROM bucketed
    GROUP BY bucket_start
    ORDER BY bucket_start;
$$;


--
-- Name: FUNCTION first_response_evolution(p_tenant_id uuid, p_from timestamp with time zone, p_to timestamp with time zone, p_bucket_days integer, p_therapist_id uuid); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.first_response_evolution(p_tenant_id uuid, p_from timestamp with time zone, p_to timestamp with time zone, p_bucket_days integer, p_therapist_id uuid) IS 'Serie temporal em buckets de N dias pra sparkline.';


--
-- Name: first_response_stats(uuid, timestamp with time zone, timestamp with time zone, uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.first_response_stats(p_tenant_id uuid, p_from timestamp with time zone DEFAULT (now() - '30 days'::interval), p_to timestamp with time zone DEFAULT now(), p_therapist_id uuid DEFAULT NULL::uuid) RETURNS TABLE(runs_count integer, avg_seconds integer, median_seconds integer, min_seconds integer, max_seconds integer, sla_threshold_seconds integer, sla_compliant_count integer, sla_compliance_rate numeric)
    LANGUAGE plpgsql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_threshold_seconds INT;
BEGIN
    -- Pega threshold do SLA (se habilitado)
    SELECT CASE WHEN enabled THEN threshold_minutes * 60 ELSE NULL END
        INTO v_threshold_seconds
        FROM public.conversation_sla_rules
        WHERE tenant_id = p_tenant_id;

    RETURN QUERY
    WITH runs AS (
        SELECT r.response_seconds, r.responder_id
        FROM public._first_response_runs(p_tenant_id, p_from, p_to) r
        WHERE (p_therapist_id IS NULL OR r.responder_id = p_therapist_id)
    )
    SELECT
        COUNT(*)::INT AS runs_count,
        COALESCE(AVG(response_seconds)::INT, 0) AS avg_seconds,
        COALESCE(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY response_seconds)::INT, 0) AS median_seconds,
        COALESCE(MIN(response_seconds), 0) AS min_seconds,
        COALESCE(MAX(response_seconds), 0) AS max_seconds,
        v_threshold_seconds AS sla_threshold_seconds,
        COUNT(*) FILTER (WHERE v_threshold_seconds IS NOT NULL AND response_seconds <= v_threshold_seconds)::INT AS sla_compliant_count,
        CASE
            WHEN v_threshold_seconds IS NULL OR COUNT(*) = 0 THEN NULL
            ELSE ROUND(100.0 * COUNT(*) FILTER (WHERE response_seconds <= v_threshold_seconds) / COUNT(*), 1)
        END AS sla_compliance_rate
    FROM runs;
END;
$$;


--
-- Name: FUNCTION first_response_stats(p_tenant_id uuid, p_from timestamp with time zone, p_to timestamp with time zone, p_therapist_id uuid); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.first_response_stats(p_tenant_id uuid, p_from timestamp with time zone, p_to timestamp with time zone, p_therapist_id uuid) IS 'Metricas agregadas de tempo de 1a resposta no periodo. Opcionalmente filtra por responder_id.';


--
-- Name: fix_all_subscription_mismatches(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.fix_all_subscription_mismatches() RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  r record;
begin
  for r in
    select distinct s.user_id as owner_id
    from public.subscriptions s
    where s.status = 'active'
      and s.user_id is not null
  loop
    perform public.rebuild_owner_entitlements(r.owner_id);
  end loop;
end;
$$;


--
-- Name: fn_agenda_regras_semanais_no_overlap(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.fn_agenda_regras_semanais_no_overlap() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
declare
  v_count int;
begin
  if new.ativo is false then
    return new;
  end if;

  select count(*) into v_count
  from public.agenda_regras_semanais r
  where r.owner_id = new.owner_id
    and r.dia_semana = new.dia_semana
    and r.ativo is true
    and (tg_op = 'INSERT' or r.id <> new.id)
    and (new.hora_inicio < r.hora_fim and new.hora_fim > r.hora_inicio);

  if v_count > 0 then
    raise exception 'Janela sobreposta: j?? existe uma regra ativa nesse intervalo.';
  end if;

  return new;
end;
$$;


--
-- Name: fn_document_signature_timeline(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.fn_document_signature_timeline() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
DECLARE
    v_patient_id uuid;
    v_tenant_id  uuid;
    v_doc_nome   text;
BEGIN
    IF NEW.status = 'assinado' AND (OLD.status IS NULL OR OLD.status <> 'assinado') THEN
        SELECT d.patient_id, d.tenant_id, d.nome_original
        INTO v_patient_id, v_tenant_id, v_doc_nome
        FROM public.documents d
        WHERE d.id = NEW.documento_id;

        IF v_patient_id IS NOT NULL THEN
            INSERT INTO public.patient_timeline (
                patient_id, tenant_id, evento_tipo,
                titulo, descricao, icone_cor,
                link_ref_tipo, link_ref_id,
                gerado_por, ocorrido_em
            ) VALUES (
                v_patient_id,
                v_tenant_id,
                'documento_assinado',
                'Documento assinado: ' || COALESCE(v_doc_nome, 'documento'),
                'Assinado por ' || COALESCE(NEW.signatario_nome, NEW.signatario_tipo),
                'green',
                'documento',
                NEW.documento_id,
                NEW.signatario_id,
                NEW.assinado_em
            );
        END IF;
    END IF;
    RETURN NEW;
END;
$$;


--
-- Name: fn_documents_timeline_insert(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.fn_documents_timeline_insert() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
    INSERT INTO public.patient_timeline (
        patient_id, tenant_id, evento_tipo,
        titulo, descricao, icone_cor,
        link_ref_tipo, link_ref_id,
        gerado_por, ocorrido_em
    ) VALUES (
        NEW.patient_id,
        NEW.tenant_id,
        'documento_adicionado',
        'Documento adicionado: ' || COALESCE(NEW.nome_original, 'arquivo'),
        'Tipo: ' || COALESCE(NEW.tipo_documento, 'outro'),
        'blue',
        'documento',
        NEW.id,
        NEW.uploaded_by,
        NEW.uploaded_at
    );
    RETURN NEW;
END;
$$;


--
-- Name: fn_notify_agenda_status_change(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.fn_notify_agenda_status_change() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_url TEXT;
    v_key TEXT;
BEGIN
    -- So dispara se status realmente mudou
    IF NEW.status IS NOT DISTINCT FROM OLD.status THEN
        RETURN NEW;
    END IF;

    -- So dispara pra status "interessantes". Outros sao silenciosamente ignorados
    -- (a edge tambem tem essa logica, mas economizamos chamada HTTP aqui)
    IF NEW.status NOT IN ('cancelado', 'remarcado', 'confirmado') THEN
        RETURN NEW;
    END IF;

    -- Precisa de paciente vinculado (senao nao tem telefone)
    IF NEW.patient_id IS NULL THEN
        RETURN NEW;
    END IF;

    -- Busca settings
    BEGIN
        v_url := current_setting('app.settings.supabase_url', true);
        v_key := current_setting('app.settings.service_role_key', true);
    EXCEPTION WHEN OTHERS THEN
        -- Settings nao configuradas — silencioso
        RETURN NEW;
    END;

    IF v_url IS NULL OR v_key IS NULL THEN
        RETURN NEW;
    END IF;

    -- Fire and forget (pg_net)
    PERFORM net.http_post(
        url := v_url || '/functions/v1/send-session-status-notification',
        headers := jsonb_build_object(
            'Authorization', 'Bearer ' || v_key,
            'Content-Type', 'application/json'
        ),
        body := jsonb_build_object(
            'event_id', NEW.id,
            'old_status', OLD.status,
            'new_status', NEW.status
        )
    );

    RETURN NEW;
END;
$$;


--
-- Name: FUNCTION fn_notify_agenda_status_change(); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.fn_notify_agenda_status_change() IS 'Dispara edge send-session-status-notification quando status de agenda_eventos muda. So pra status mapeados (cancelado/remarcado/confirmado). Nao bloqueia UPDATE.';


--
-- Name: fn_sla_resolve_on_outbound(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.fn_sla_resolve_on_outbound() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_thread_key TEXT;
BEGIN
    -- So processa outbound
    IF NEW.direction <> 'outbound' THEN RETURN NEW; END IF;

    -- Calcula thread_key no mesmo padrao da view conversation_threads
    v_thread_key := COALESCE(
        NEW.patient_id::text,
        'anon:' || COALESCE(NEW.to_number, 'unknown')
    );

    UPDATE public.conversation_sla_breaches
        SET resolved_at = now(),
            resolved_by_message_id = NEW.id
        WHERE tenant_id = NEW.tenant_id
          AND thread_key = v_thread_key
          AND resolved_at IS NULL;

    RETURN NEW;
END;
$$;


--
-- Name: fn_whatsapp_low_balance_notify(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.fn_whatsapp_low_balance_notify() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_detail TEXT;
BEGIN
    -- So alerta na transicao (alerted_at NULL) E se esta abaixo do threshold
    IF NEW.balance < NEW.low_balance_threshold
       AND NEW.low_balance_alerted_at IS NULL THEN

        v_detail := format(
            'Saldo atual: %s credito(s). Alerta configurado em %s. '
            'Compre mais na loja para nao interromper envios via WhatsApp Oficial.',
            NEW.balance,
            NEW.low_balance_threshold
        );

        -- Stakeholders: owner do canal WhatsApp ativo + admins ativos do tenant
        INSERT INTO public.notifications
            (owner_id, tenant_id, type, ref_id, ref_table, payload)
        SELECT
            u.user_id,
            NEW.tenant_id,
            'system_alert',
            NEW.tenant_id,
            'whatsapp_credits_balance',
            jsonb_build_object(
                'title', 'Saldo de WhatsApp baixo',
                'detail', v_detail,
                'severity', 'warn',
                'deeplink', '/configuracoes/creditos-whatsapp'
            )
        FROM (
            SELECT owner_id AS user_id
                FROM public.notification_channels
                WHERE tenant_id = NEW.tenant_id
                  AND channel = 'whatsapp'
                  AND is_active = true
                  AND deleted_at IS NULL
            UNION
            SELECT user_id
                FROM public.tenant_members
                WHERE tenant_id = NEW.tenant_id
                  AND role IN ('clinic_admin', 'tenant_admin')
                  AND status = 'active'
        ) u
        WHERE u.user_id IS NOT NULL;

        -- Anti-spam: so alerta de novo depois que add_whatsapp_credits
        -- reseta alerted_at pra NULL (acontece em purchase/topup)
        NEW.low_balance_alerted_at := now();
    END IF;

    RETURN NEW;
END;
$$;


--
-- Name: FUNCTION fn_whatsapp_low_balance_notify(); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.fn_whatsapp_low_balance_notify() IS 'Trigger BEFORE UPDATE em whatsapp_credits_balance. Dispara notificacao system_alert quando saldo cruza low_balance_threshold pra baixo. Anti-spam via low_balance_alerted_at (resetado pelo add_whatsapp_credits).';


--
-- Name: generate_math_challenge(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.generate_math_challenge() RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_a    integer;
    v_b    integer;
    v_op   text;
    v_ans  integer;
    v_q    text;
    v_id   uuid;
BEGIN
    v_a := 1 + floor(random() * 9)::int;
    v_b := 1 + floor(random() * 9)::int;
    v_op := (ARRAY['+','-','*'])[1 + floor(random() * 3)::int];

    -- garantir resultado positivo na subtração
    IF v_op = '-' AND v_b > v_a THEN
        v_a := v_a + v_b;
    END IF;

    v_ans := CASE v_op
        WHEN '+' THEN v_a + v_b
        WHEN '-' THEN v_a - v_b
        WHEN '*' THEN v_a * v_b
    END;

    v_q := format('Quanto é %s %s %s?', v_a, v_op, v_b);

    INSERT INTO math_challenges (question, answer)
    VALUES (v_q, v_ans)
    RETURNING id INTO v_id;

    RETURN jsonb_build_object('id', v_id, 'question', v_q);
END;
$$;


--
-- Name: get_entity_primary_phone(text, uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.get_entity_primary_phone(p_entity_type text, p_entity_id uuid) RETURNS text
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
    SELECT number FROM public.contact_phones
    WHERE entity_type = p_entity_type
      AND entity_id = p_entity_id
    ORDER BY is_primary DESC, position ASC, created_at ASC
    LIMIT 1;
$$;


--
-- Name: get_financial_report(uuid, date, date, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.get_financial_report(p_owner_id uuid, p_start_date date, p_end_date date, p_group_by text DEFAULT 'month'::text) RETURNS TABLE(group_key text, group_label text, total_receitas numeric, total_despesas numeric, saldo numeric, total_pendente numeric, total_overdue numeric, count_records bigint)
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$

  -- ?????? Valida p_group_by antes de executar ??????????????????????????????????????????????????????????????????????????????????????????????????????
  -- (lan??a erro se valor inv??lido; plpgsql seria necess??rio para isso em SQL puro,
  --  ent??o usamos um CTE de valida????o com CASE WHEN para retornar vazio em vez de erro)

  WITH base AS (
    SELECT
      fr.type,
      fr.amount,
      fr.final_amount,
      fr.status,
      fr.deleted_at,
      -- Chave de agrupamento calculada conforme p_group_by
      CASE p_group_by
        WHEN 'month'    THEN TO_CHAR(
                               COALESCE(fr.paid_at::DATE, fr.due_date, fr.created_at::DATE),
                               'YYYY-MM'
                             )
        WHEN 'week'     THEN TO_CHAR(
                               COALESCE(fr.paid_at::DATE, fr.due_date, fr.created_at::DATE),
                               'IYYY-"W"IW'
                             )
        WHEN 'category' THEN COALESCE(fr.category_id::TEXT, fr.category, 'sem_categoria')
        WHEN 'patient'  THEN COALESCE(fr.patient_id::TEXT, 'sem_paciente')
        ELSE NULL  -- group_by inv??lido ??? group_key NULL ??? retorno vazio
      END AS gkey,
      -- Label leg??vel (enriquecido via JOIN abaixo quando poss??vel)
      CASE p_group_by
        WHEN 'month'    THEN TO_CHAR(
                               COALESCE(fr.paid_at::DATE, fr.due_date, fr.created_at::DATE),
                               'YYYY-MM'
                             )
        WHEN 'week'     THEN TO_CHAR(
                               COALESCE(fr.paid_at::DATE, fr.due_date, fr.created_at::DATE),
                               'IYYY-"W"IW'
                             )
        WHEN 'category' THEN COALESCE(fc.name, fr.category, 'Sem categoria')
        WHEN 'patient'  THEN COALESCE(p.nome_completo, fr.patient_id::TEXT, 'Sem paciente')
        ELSE NULL
      END AS glabel
    FROM public.financial_records fr
    LEFT JOIN public.financial_categories fc
           ON fc.id = fr.category_id
    LEFT JOIN public.patients p
           ON p.id = fr.patient_id
    WHERE fr.owner_id    = p_owner_id
      AND fr.deleted_at  IS NULL
      AND COALESCE(fr.paid_at::DATE, fr.due_date, fr.created_at::DATE)
          BETWEEN p_start_date AND p_end_date
  )

  SELECT
    gkey                                                                             AS group_key,
    glabel                                                                           AS group_label,

    COALESCE(SUM(final_amount) FILTER (WHERE type = 'receita' AND status = 'paid'), 0)
                                                                                     AS total_receitas,

    COALESCE(SUM(final_amount) FILTER (WHERE type = 'despesa' AND status = 'paid'), 0)
                                                                                     AS total_despesas,

    COALESCE(SUM(final_amount) FILTER (WHERE type = 'receita' AND status = 'paid'), 0)
    - COALESCE(SUM(final_amount) FILTER (WHERE type = 'despesa' AND status = 'paid'), 0)
                                                                                     AS saldo,

    COALESCE(SUM(final_amount) FILTER (WHERE status = 'pending'), 0)                AS total_pendente,

    COALESCE(SUM(final_amount) FILTER (WHERE status = 'overdue'), 0)                AS total_overdue,

    COUNT(*)                                                                         AS count_records

  FROM base
  WHERE gkey IS NOT NULL  -- descarta p_group_by inv??lido
  GROUP BY gkey, glabel
  ORDER BY gkey ASC;

$$;


--
-- Name: FUNCTION get_financial_report(p_owner_id uuid, p_start_date date, p_end_date date, p_group_by text); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.get_financial_report(p_owner_id uuid, p_start_date date, p_end_date date, p_group_by text) IS 'Relat??rio financeiro agrupado por m??s, semana ISO, categoria ou paciente. p_group_by aceita: ''month'' | ''week'' | ''category'' | ''patient''. Totais de receita/despesa consideram apenas registros com status=paid. total_pendente e total_overdue incluem todos os tipos (receita + despesa).';


--
-- Name: get_financial_summary(uuid, integer, integer); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.get_financial_summary(p_owner_id uuid, p_year integer, p_month integer) RETURNS TABLE(total_receitas numeric, total_despesas numeric, total_pendente numeric, saldo_liquido numeric, total_repasse numeric, count_receitas bigint, count_despesas bigint)
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
  SELECT
    -- Receitas pagas no per??odo
    COALESCE(SUM(amount) FILTER (
      WHERE type = 'receita' AND status = 'paid'
    ), 0) AS total_receitas,

    -- Despesas pagas no per??odo
    COALESCE(SUM(amount) FILTER (
      WHERE type = 'despesa' AND status = 'paid'
    ), 0) AS total_despesas,

    -- Tudo pendente ou vencido (receitas + despesas)
    COALESCE(SUM(amount) FILTER (
      WHERE status IN ('pending', 'overdue')
    ), 0) AS total_pendente,

    -- Saldo l??quido (receitas pagas ??? despesas pagas)
    COALESCE(SUM(amount) FILTER (
      WHERE type = 'receita' AND status = 'paid'
    ), 0)
    - COALESCE(SUM(amount) FILTER (
      WHERE type = 'despesa' AND status = 'paid'
    ), 0) AS saldo_liquido,

    -- Total repassado ?? cl??nica (apenas receitas pagas)
    COALESCE(SUM(clinic_fee_amount) FILTER (
      WHERE type = 'receita' AND status = 'paid'
    ), 0) AS total_repasse,

    -- Contadores (excluindo soft-deleted)
    COUNT(*) FILTER (WHERE type = 'receita' AND deleted_at IS NULL) AS count_receitas,
    COUNT(*) FILTER (WHERE type = 'despesa' AND deleted_at IS NULL) AS count_despesas

  FROM public.financial_records
  WHERE owner_id   = p_owner_id
    AND deleted_at IS NULL
    AND EXTRACT(YEAR  FROM COALESCE(paid_at::DATE, due_date, created_at::DATE)) = p_year
    AND EXTRACT(MONTH FROM COALESCE(paid_at::DATE, due_date, created_at::DATE)) = p_month;
$$;


--
-- Name: get_my_email(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.get_my_email() RETURNS text
    LANGUAGE sql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
  select lower(email)
  from auth.users
  where id = auth.uid();
$$;


--
-- Name: get_patient_intake_invite_info(text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.get_patient_intake_invite_info(p_token text) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'pg_temp'
    AS $$
DECLARE
    v_token_clean text;
    v_invite      RECORD;
    v_result      jsonb;
BEGIN
    v_token_clean := nullif(trim(coalesce(p_token, '')), '');
    IF v_token_clean IS NULL THEN
        RETURN jsonb_build_object('error', 'missing-token');
    END IF;

    SELECT pi.owner_id, pi.tenant_id, pi.active, pi.expires_at, pi.max_uses, pi.uses
      INTO v_invite
      FROM public.patient_invites pi
     WHERE pi.token = v_token_clean
     LIMIT 1;

    IF v_invite.owner_id IS NULL THEN
        RETURN jsonb_build_object('error', 'invalid-token');
    END IF;

    IF v_invite.active IS DISTINCT FROM true THEN
        RETURN jsonb_build_object('error', 'invalid-token');
    END IF;

    IF v_invite.expires_at IS NOT NULL AND v_invite.expires_at < now() THEN
        RETURN jsonb_build_object('error', 'invalid-token');
    END IF;

    IF v_invite.max_uses IS NOT NULL AND v_invite.uses >= v_invite.max_uses THEN
        RETURN jsonb_build_object('error', 'invalid-token');
    END IF;

    SELECT jsonb_build_object(
        'therapist', jsonb_build_object(
            'display_name',      coalesce(
                                     nullif(trim(p.full_name), ''),
                                     nullif(trim(p.nickname), ''),
                                     'Profissional'
                                 ),
            'avatar_url',        nullif(trim(coalesce(p.avatar_url, '')), ''),
            'work_description',  nullif(trim(coalesce(p.work_description, '')), ''),
            'bio',               nullif(trim(coalesce(p.bio, '')), ''),
            'phone',             nullif(trim(coalesce(p.phone, '')), ''),
            'site_url',          nullif(trim(coalesce(p.site_url, '')), ''),
            'instagram',         nullif(trim(coalesce(p.social_instagram, '')), '')
        ),
        'clinic', CASE
            WHEN cp.tenant_id IS NOT NULL THEN jsonb_build_object(
                'name',       nullif(trim(coalesce(cp.nome_fantasia, '')), ''),
                'logo_url',   nullif(trim(coalesce(cp.logo_url, '')), ''),
                'email',      nullif(trim(coalesce(cp.email, '')), ''),
                'phone',      nullif(trim(coalesce(cp.telefone, '')), ''),
                'site',       nullif(trim(coalesce(cp.site, '')), ''),
                'city',       nullif(trim(coalesce(cp.cidade, '')), ''),
                'state',      nullif(trim(coalesce(cp.estado, '')), ''),
                'neighborhood', nullif(trim(coalesce(cp.bairro, '')), ''),
                'street',     nullif(trim(coalesce(cp.logradouro, '')), ''),
                'number',     nullif(trim(coalesce(cp.numero, '')), ''),
                'social',     coalesce(cp.redes_sociais, '[]'::jsonb)
            )
            ELSE NULL
        END
    )
    INTO v_result
    FROM public.profiles p
    LEFT JOIN public.company_profiles cp ON cp.tenant_id = v_invite.tenant_id
    WHERE p.id = v_invite.owner_id
    LIMIT 1;

    IF v_result IS NULL THEN
        RETURN jsonb_build_object('error', 'invalid-token');
    END IF;

    RETURN jsonb_build_object('ok', true, 'info', v_result);
END;
$$;


--
-- Name: FUNCTION get_patient_intake_invite_info(p_token text); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.get_patient_intake_invite_info(p_token text) IS 'A#31 — Lookup público read-only (via edge function) dos dados de apresentação do terapeuta/clínica dono do link de cadastro externo. Só retorna campos não-sensíveis.';


--
-- Name: get_patient_session_counts(uuid[]); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.get_patient_session_counts(p_patient_ids uuid[]) RETURNS TABLE(patient_id uuid, session_count integer, last_session_at timestamp with time zone)
    LANGUAGE sql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
    SELECT
        ae.patient_id,
        COUNT(*)::int                AS session_count,
        MAX(ae.inicio_em)            AS last_session_at
    FROM public.agenda_eventos ae
    WHERE ae.patient_id = ANY(p_patient_ids)
      AND ae.tenant_id IN (
          SELECT tm.tenant_id
          FROM public.tenant_members tm
          WHERE tm.user_id = auth.uid()
            AND tm.status = 'active'
      )
    GROUP BY ae.patient_id;
$$;


--
-- Name: get_twilio_config(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.get_twilio_config() RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    cfg saas_twilio_config%ROWTYPE;
BEGIN
    -- Permite quem é saas_admin (UI) ou quando chamado via service_role (edge function)
    -- coalesce protege de NULL (auth.role() pode ser NULL fora de contexto JWT)
    IF NOT (public.is_saas_admin() OR coalesce(auth.role(), '') = 'service_role') THEN
        RAISE EXCEPTION 'Sem permissão' USING ERRCODE = '42501';
    END IF;

    SELECT * INTO cfg FROM saas_twilio_config WHERE id = true;
    IF NOT FOUND THEN
        RETURN jsonb_build_object(
            'account_sid',          NULL,
            'whatsapp_webhook_url', NULL,
            'usd_brl_rate',         5.5,
            'margin_multiplier',    1.4
        );
    END IF;

    RETURN jsonb_build_object(
        'account_sid',          cfg.account_sid,
        'whatsapp_webhook_url', cfg.whatsapp_webhook_url,
        'usd_brl_rate',         cfg.usd_brl_rate,
        'margin_multiplier',    cfg.margin_multiplier,
        'notes',                cfg.notes,
        'updated_at',           cfg.updated_at,
        'updated_by',           cfg.updated_by
    );
END;
$$;


--
-- Name: get_whatsapp_removable_balance(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.get_whatsapp_removable_balance(p_tenant_id uuid) RETURNS TABLE(balance integer, removable integer, protected_amount integer, topup_net integer, usage_total integer)
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_balance INT := 0;
    v_topup_net INT := 0;
    v_usage_total INT := 0;
    v_removable INT := 0;
BEGIN
    IF NOT public.is_saas_admin() THEN
        RAISE EXCEPTION 'permission_denied';
    END IF;

    SELECT COALESCE(b.balance, 0) INTO v_balance
        FROM public.whatsapp_credits_balance b
        WHERE b.tenant_id = p_tenant_id;

    v_balance := COALESCE(v_balance, 0);

    SELECT COALESCE(SUM(amount), 0) INTO v_topup_net
        FROM public.whatsapp_credits_transactions
        WHERE tenant_id = p_tenant_id
          AND kind IN ('topup_manual', 'adjustment', 'refund');

    SELECT COALESCE(ABS(SUM(amount)), 0) INTO v_usage_total
        FROM public.whatsapp_credits_transactions
        WHERE tenant_id = p_tenant_id
          AND kind = 'usage';

    v_removable := GREATEST(0, v_topup_net - v_usage_total);
    v_removable := LEAST(v_removable, v_balance);

    RETURN QUERY SELECT
        v_balance,
        v_removable,
        GREATEST(0, v_balance - v_removable),
        v_topup_net,
        v_usage_total;
END;
$$;


--
-- Name: FUNCTION get_whatsapp_removable_balance(p_tenant_id uuid); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.get_whatsapp_removable_balance(p_tenant_id uuid) IS 'Breakdown do saldo WhatsApp: removivel (pool cortesia restante) vs protegido (compras). Apenas saas_admin.';


--
-- Name: guard_account_type_immutable(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.guard_account_type_immutable() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF OLD.account_type <> 'free' AND NEW.account_type IS DISTINCT FROM OLD.account_type THEN
    RAISE EXCEPTION 'account_type ?? imut??vel ap??s escolha (atual: "%" para tentativa: "%"). Para mudar de perfil, crie uma nova conta.', OLD.account_type, NEW.account_type
      USING ERRCODE = 'P0001';
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: guard_locked_commitment(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.guard_locked_commitment() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  if (old.is_locked = true) then
    if (tg_op = 'DELETE') then
      raise exception 'Compromisso bloqueado n??o pode ser exclu??do.';
    end if;

    if (tg_op = 'UPDATE') then
      if (new.active = false) then
        raise exception 'Compromisso bloqueado n??o pode ser desativado.';
      end if;

      -- trava renomear (mant??m o "Sess??o" sempre igual)
      if (new.name is distinct from old.name) then
        raise exception 'Compromisso bloqueado n??o pode ser renomeado.';
      end if;

      -- se quiser travar descri????o tamb??m, descomente:
      -- if (new.description is distinct from old.description) then
      --   raise exception 'Compromisso bloqueado n??o pode alterar descri????o.';
      -- end if;
    end if;
  end if;

  return new;
end;
$$;


--
-- Name: guard_no_change_core_plan_key(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.guard_no_change_core_plan_key() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  if old.key in ('clinic_free','clinic_pro','therapist_free','therapist_pro')
     and new.key is distinct from old.key then
    raise exception 'N??o ?? permitido alterar a key do plano padr??o (%).', old.key
      using errcode = 'P0001';
  end if;

  return new;
end $$;


--
-- Name: guard_no_change_plan_target(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.guard_no_change_plan_target() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
declare
  v_bypass text;
begin
  -- bypass controlado por sess??o/transa????o:
  -- s?? passa se app.plan_migration_bypass = '1'
  v_bypass := current_setting('app.plan_migration_bypass', true);

  if v_bypass = '1' then
    return new;
  end if;

  -- comportamento original (bloqueia qualquer mudan??a)
  if new.target is distinct from old.target then
    raise exception 'N??o ?? permitido alterar target do plano (%) de % para %.',
      old.key, old.target, new.target
      using errcode = 'P0001';
  end if;

  return new;
end
$$;


--
-- Name: guard_no_delete_core_plans(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.guard_no_delete_core_plans() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  if old.key in ('clinic_free','clinic_pro','therapist_free','therapist_pro') then
    raise exception 'Plano padr??o (%) n??o pode ser removido.', old.key
      using errcode = 'P0001';
  end if;

  return old;
end $$;


--
-- Name: guard_patient_cannot_own_tenant(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.guard_patient_cannot_own_tenant() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
  v_account_type text;
BEGIN
  SELECT account_type INTO v_account_type
  FROM public.profiles
  WHERE id = NEW.user_id;

  IF v_account_type = 'patient' AND NEW.role IN ('tenant_admin', 'therapist') THEN
    RAISE EXCEPTION 'Usu??rio com perfil "patient" n??o pode ser propriet??rio ou terapeuta de um tenant. Se tornou profissional? Crie uma nova conta.'
      USING ERRCODE = 'P0001';
  END IF;

  RETURN NEW;
END;
$$;


--
-- Name: guard_tenant_kind_immutable(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.guard_tenant_kind_immutable() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF NEW.kind IS DISTINCT FROM OLD.kind THEN
    RAISE EXCEPTION 'tenants.kind ?? imut??vel ap??s cria????o. Tentativa de alterar "%" para "%".', OLD.kind, NEW.kind
      USING ERRCODE = 'P0001';
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: handle_new_user(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.handle_new_user() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
  INSERT INTO public.profiles (id, role, account_type)
  VALUES (NEW.id, 'portal_user', 'free')
  ON CONFLICT (id) DO NOTHING;
  RETURN NEW;
END;
$$;


--
-- Name: handle_new_user_create_personal_tenant(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.handle_new_user_create_personal_tenant() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  -- Desabilitado. Tenant criado no onboarding via provision_account_tenant().
  RETURN NEW;
END;
$$;


--
-- Name: has_feature(uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.has_feature(p_owner_id uuid, p_feature_key text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1
    from public.owner_feature_entitlements e
    where e.owner_id = p_owner_id
      and e.feature_key = p_feature_key
  );
$$;


--
-- Name: is_clinic_tenant(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.is_clinic_tenant(_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  SELECT EXISTS (
    SELECT 1 FROM public.tenants t
    WHERE t.id = _tenant_id
      AND t.kind IN ('clinic', 'clinic_coworking', 'clinic_reception', 'clinic_full')
  );
$$;


--
-- Name: is_saas_admin(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.is_saas_admin() RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1 from public.saas_admins sa
    where sa.user_id = auth.uid()
  );
$$;


--
-- Name: is_tenant_admin(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.is_tenant_admin(p_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
  select exists (
    select 1
    from public.tenant_members tm
    where tm.tenant_id = p_tenant_id
      and tm.user_id = auth.uid()
      and tm.role = 'tenant_admin'
      and tm.status = 'active'
  );
$$;


--
-- Name: is_tenant_member(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.is_tenant_member(_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1
    from public.tenant_members m
    where m.tenant_id = _tenant_id
      and m.user_id = auth.uid()
      and m.status = 'active'
  );
$$;


--
-- Name: is_therapist_tenant(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.is_therapist_tenant(_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  SELECT EXISTS (
    SELECT 1 FROM public.tenants t
    WHERE t.id = _tenant_id AND t.kind = 'therapist'
  );
$$;


--
-- Name: issue_patient_invite(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.issue_patient_invite() RETURNS text
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_uid        uuid;
  v_tenant_id  uuid;
  v_token      text;
  v_existing   text;
BEGIN
  v_uid := auth.uid();
  IF v_uid IS NULL THEN
    RAISE EXCEPTION 'Usuário não autenticado' USING ERRCODE = '28000';
  END IF;

  -- Se já existe ativo, retorna ele (mesma política da função anterior load_or_create)
  SELECT token
    INTO v_existing
  FROM public.patient_invites
  WHERE owner_id = v_uid
    AND active = true
  ORDER BY created_at DESC
  LIMIT 1;

  IF v_existing IS NOT NULL THEN
    RETURN v_existing;
  END IF;

  SELECT tenant_id
    INTO v_tenant_id
  FROM public.tenant_members
  WHERE user_id = v_uid
    AND status = 'active'
  ORDER BY created_at ASC
  LIMIT 1;

  v_token := replace(gen_random_uuid()::text, '-', '');

  INSERT INTO public.patient_invites (owner_id, tenant_id, token, active)
  VALUES (v_uid, v_tenant_id, v_token, true);

  RETURN v_token;
END;
$$;


--
-- Name: FUNCTION issue_patient_invite(); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.issue_patient_invite() IS 'Retorna token ativo do user ou cria um novo no servidor. Remove necessidade de gerar token no cliente.';


--
-- Name: jwt_email(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.jwt_email() RETURNS text
    LANGUAGE sql STABLE
    AS $$
  select nullif(lower(current_setting('request.jwt.claim.email', true)), '');
$$;


--
-- Name: list_financial_records(uuid, integer, integer, text, text, uuid, integer, integer); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.list_financial_records(p_owner_id uuid, p_year integer DEFAULT NULL::integer, p_month integer DEFAULT NULL::integer, p_type text DEFAULT NULL::text, p_status text DEFAULT NULL::text, p_patient_id uuid DEFAULT NULL::uuid, p_limit integer DEFAULT 50, p_offset integer DEFAULT 0) RETURNS SETOF public.financial_records
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
  SELECT *
  FROM public.financial_records
  WHERE owner_id    = p_owner_id
    AND deleted_at  IS NULL
    AND (p_type       IS NULL OR type::TEXT  = p_type)
    AND (p_status     IS NULL OR status      = p_status)
    AND (p_patient_id IS NULL OR patient_id  = p_patient_id)
    AND (p_year  IS NULL OR EXTRACT(YEAR  FROM COALESCE(paid_at::DATE, due_date, created_at::DATE)) = p_year)
    AND (p_month IS NULL OR EXTRACT(MONTH FROM COALESCE(paid_at::DATE, due_date, created_at::DATE)) = p_month)
  ORDER BY COALESCE(paid_at, due_date::TIMESTAMPTZ, created_at) DESC
  LIMIT  p_limit
  OFFSET p_offset;
$$;


--
-- Name: log_audit_change(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.log_audit_change() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'pg_temp'
    AS $$
DECLARE
    v_tenant_id UUID;
    v_entity_id TEXT;
    v_old JSONB;
    v_new JSONB;
    v_changed TEXT[];
    v_heavy_fields TEXT[] := ARRAY[
        'content', 'content_html', 'content_json', 'raw_data',
        'signature_data', 'pdf_blob', 'binary', 'body_html', 'body_text'
    ];
    v_noise_fields TEXT[] := ARRAY['updated_at', 'last_seen_at', 'last_activity_at'];
BEGIN
    IF TG_OP = 'DELETE' THEN
        v_tenant_id := OLD.tenant_id;
        v_entity_id := OLD.id::TEXT;
        v_old := to_jsonb(OLD) - v_heavy_fields;
        v_new := NULL;
    ELSIF TG_OP = 'INSERT' THEN
        v_tenant_id := NEW.tenant_id;
        v_entity_id := NEW.id::TEXT;
        v_old := NULL;
        v_new := to_jsonb(NEW) - v_heavy_fields;
    ELSE -- UPDATE
        v_tenant_id := NEW.tenant_id;
        v_entity_id := NEW.id::TEXT;
        v_old := to_jsonb(OLD) - v_heavy_fields;
        v_new := to_jsonb(NEW) - v_heavy_fields;

        -- calcular campos realmente alterados
        SELECT array_agg(key ORDER BY key) INTO v_changed
        FROM jsonb_each(to_jsonb(NEW)) AS kv(key, value)
        WHERE (to_jsonb(OLD))->kv.key IS DISTINCT FROM kv.value;

        -- se nada mudou, ignora
        IF v_changed IS NULL THEN
            RETURN NEW;
        END IF;

        -- se mudou apenas campos de ruido (ex: updated_at), ignora
        IF v_changed <@ v_noise_fields THEN
            RETURN NEW;
        END IF;
    END IF;

    INSERT INTO public.audit_logs (
        tenant_id, user_id, entity_type, entity_id, action,
        old_values, new_values, changed_fields
    ) VALUES (
        v_tenant_id,
        auth.uid(),
        TG_TABLE_NAME,
        v_entity_id,
        lower(TG_OP),
        v_old,
        v_new,
        v_changed
    );

    RETURN COALESCE(NEW, OLD);
END;
$$;


--
-- Name: FUNCTION log_audit_change(); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.log_audit_change() IS 'Trigger generica de audit. Filtra campos pesados (content, signature_data) e ruido (updated_at).';


--
-- Name: mark_as_paid(uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.mark_as_paid(p_financial_record_id uuid, p_payment_method text) RETURNS SETOF public.financial_records
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_record public.financial_records%ROWTYPE;
BEGIN
  -- Garante que o registro pertence ao usu??rio autenticado (RLS n??o aplica em SECURITY DEFINER)
  SELECT * INTO v_record
  FROM public.financial_records
  WHERE id = p_financial_record_id
    AND owner_id = auth.uid()
    AND deleted_at IS NULL;

  IF NOT FOUND THEN
    RAISE EXCEPTION 'Registro financeiro n??o encontrado ou sem permiss??o.';
  END IF;

  IF v_record.status NOT IN ('pending', 'overdue') THEN
    RAISE EXCEPTION 'Apenas cobran??as pendentes ou vencidas podem ser marcadas como pagas.';
  END IF;

  UPDATE public.financial_records
    SET status         = 'paid',
        paid_at        = NOW(),
        payment_method = p_payment_method,
        updated_at     = NOW()
  WHERE id = p_financial_record_id
  RETURNING * INTO v_record;

  RETURN NEXT v_record;
END;
$$;


--
-- Name: mark_payout_as_paid(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.mark_payout_as_paid(p_payout_id uuid) RETURNS public.therapist_payouts
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_payout public.therapist_payouts%ROWTYPE;
BEGIN
  -- Busca o payout
  SELECT * INTO v_payout
  FROM public.therapist_payouts
  WHERE id = p_payout_id;

  IF NOT FOUND THEN
    RAISE EXCEPTION 'Repasse n??o encontrado: %', p_payout_id;
  END IF;

  -- Verifica permiss??o: apenas tenant_admin do tenant do repasse
  IF NOT public.is_tenant_admin(v_payout.tenant_id) THEN
    RAISE EXCEPTION 'Apenas o administrador da cl??nica pode marcar repasses como pagos.';
  END IF;

  -- Verifica status
  IF v_payout.status <> 'pending' THEN
    RAISE EXCEPTION
      'Repasse j?? est?? com status ''%''. Apenas repasses pendentes podem ser pagos.',
      v_payout.status;
  END IF;

  -- Atualiza
  UPDATE public.therapist_payouts
  SET
    status     = 'paid',
    paid_at    = NOW(),
    updated_at = NOW()
  WHERE id = p_payout_id
  RETURNING * INTO v_payout;

  RETURN v_payout;
END;
$$;


--
-- Name: FUNCTION mark_payout_as_paid(p_payout_id uuid); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.mark_payout_as_paid(p_payout_id uuid) IS 'Marca um repasse de terapeuta como pago. Apenas o tenant_admin pode chamar. Apenas repasses com status=pending podem ser finalizados.';


--
-- Name: match_patient_by_phone(uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.match_patient_by_phone(p_tenant_id uuid, p_phone text) RETURNS uuid
    LANGUAGE plpgsql STABLE SECURITY DEFINER
    SET search_path TO 'public', 'pg_temp'
    AS $$
DECLARE
    v_normalized TEXT;
    v_patient_id UUID;
BEGIN
    v_normalized := public.normalize_phone_br(p_phone);
    IF v_normalized IS NULL OR length(v_normalized) < 10 THEN
        RETURN NULL;
    END IF;

    -- prioridade: telefone principal, depois alternativo, depois responsavel
    SELECT id INTO v_patient_id FROM public.patients
    WHERE tenant_id = p_tenant_id
      AND public.normalize_phone_br(telefone) = v_normalized
    LIMIT 1;
    IF v_patient_id IS NOT NULL THEN RETURN v_patient_id; END IF;

    SELECT id INTO v_patient_id FROM public.patients
    WHERE tenant_id = p_tenant_id
      AND public.normalize_phone_br(telefone_alternativo) = v_normalized
    LIMIT 1;
    IF v_patient_id IS NOT NULL THEN RETURN v_patient_id; END IF;

    SELECT id INTO v_patient_id FROM public.patients
    WHERE tenant_id = p_tenant_id
      AND public.normalize_phone_br(telefone_responsavel) = v_normalized
    LIMIT 1;

    RETURN v_patient_id;
END;
$$;


--
-- Name: FUNCTION match_patient_by_phone(p_tenant_id uuid, p_phone text); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.match_patient_by_phone(p_tenant_id uuid, p_phone text) IS 'Encontra patient_id do tenant cujo telefone (principal/alternativo/responsavel) bate com o numero.';


--
-- Name: my_tenants(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.my_tenants() RETURNS TABLE(tenant_id uuid, role text, status text, kind text)
    LANGUAGE sql STABLE
    AS $$
  select
    tm.tenant_id,
    tm.role,
    tm.status,
    t.kind
  from public.tenant_members tm
  join public.tenants t on t.id = tm.tenant_id
  where tm.user_id = auth.uid();
$$;


--
-- Name: normalize_phone_br(text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.normalize_phone_br(p_phone text) RETURNS text
    LANGUAGE plpgsql IMMUTABLE
    AS $$
DECLARE
    v_digits TEXT;
BEGIN
    IF p_phone IS NULL THEN RETURN NULL; END IF;

    -- remove tudo que nao seja digito
    v_digits := regexp_replace(p_phone, '\D', '', 'g');

    -- remove DDI 55 se tem 12+ digitos (+55 + DDD + numero)
    IF length(v_digits) >= 12 AND left(v_digits, 2) = '55' THEN
        v_digits := substr(v_digits, 3);
    END IF;

    -- pega os ultimos 11 digitos (DDD + 9digito + 8numero) ou 10 (DDD + 8numero)
    IF length(v_digits) > 11 THEN
        v_digits := right(v_digits, 11);
    END IF;

    RETURN v_digits;
END;
$$;


--
-- Name: FUNCTION normalize_phone_br(p_phone text); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.normalize_phone_br(p_phone text) IS 'Normaliza telefone BR para os ultimos 11 digitos (DDD+numero), removendo DDI +55 e formatacao.';


--
-- Name: notice_track_click(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.notice_track_click(p_notice_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
begin
  update public.global_notices
  set clicks_count = clicks_count + 1
  where id = p_notice_id;
end;
$$;


--
-- Name: notice_track_view(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.notice_track_view(p_notice_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
begin
  update public.global_notices
  set views_count = views_count + 1
  where id = p_notice_id;
end;
$$;


--
-- Name: notify_on_intake(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.notify_on_intake() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  IF NEW.status = 'new' THEN
    INSERT INTO public.notifications (
      owner_id,
      tenant_id,
      type,
      ref_id,
      ref_table,
      payload
    )
    VALUES (
      NEW.owner_id,
      NEW.tenant_id,
      'new_patient',
      NEW.id,
      'patient_intake_requests',
      jsonb_build_object(
        'title',  'Novo cadastro externo',
        'detail', COALESCE(NEW.nome_completo, 'Paciente'),
        'deeplink', '/therapist/patients/cadastro/recebidos',
        'avatar_initials', upper(left(COALESCE(NEW.nome_completo, '?'), 2))
      )
    );
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: notify_on_scheduling(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.notify_on_scheduling() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$                                                                                                                   BEGIN                                                                                                                     IF NEW.status = 'pendente' THEN
      INSERT INTO public.notifications (                                                                                        owner_id,                                                                                                               tenant_id,                                                                                                              type,                                                                                                                   ref_id,                                                                                                                 ref_table,                                                                                                              payload                                                                                                               )                                                                                                                       VALUES (
        NEW.owner_id,                                                                                                           NEW.tenant_id,
        'new_scheduling',                                                                                                       NEW.id,                                                                                                                 'agendador_solicitacoes',                                                                                               jsonb_build_object(                                                                                                       'title',  'Nova solicita????o de agendamento',                                                                            'detail', COALESCE(NEW.paciente_nome, 'Paciente') || ' ' || COALESCE(NEW.paciente_sobrenome, '') || ' ??? ' ||    COALESCE(NEW.tipo, ''),                                                                                                         'deeplink', '/therapist/agendamentos-recebidos',                                                                        'avatar_initials', upper(left(COALESCE(NEW.paciente_nome, '?'), 1) || left(COALESCE(NEW.paciente_sobrenome,     ''), 1))                                                                                                                      )                                                                                                                     );                                                                                                                    END IF;                                                                                                                 RETURN NEW;                                                                                                           END;                                                                                                                    $$;


--
-- Name: notify_on_session_status(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.notify_on_session_status() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
DECLARE
  v_nome text;
BEGIN
  IF NEW.status IN ('faltou', 'cancelado') AND OLD.status IS DISTINCT FROM NEW.status THEN

    SELECT nome_completo
      INTO v_nome
      FROM public.patients
     WHERE id = NEW.patient_id
     LIMIT 1;

    INSERT INTO public.notifications (
      owner_id,
      tenant_id,
      type,
      ref_id,
      ref_table,
      payload
    )
    VALUES (
      NEW.owner_id,
      NEW.tenant_id,
      'session_status',
      NEW.id,
      'agenda_eventos',
      jsonb_build_object(
        'title',           CASE WHEN NEW.status = 'faltou' THEN 'Paciente faltou' ELSE 'Sess??o cancelada' END,
        'detail',          COALESCE(v_nome, 'Paciente') || ' ??? ' || to_char(NEW.inicio_em, 'DD/MM HH24:MI'),
        'deeplink',        '/therapist/agenda',
        'avatar_initials', upper(left(COALESCE(v_nome, '?'), 2))
      )
    );

  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: on_new_user_seed_patient_groups(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.on_new_user_seed_patient_groups() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
  BEGIN
    PERFORM public.seed_default_patient_groups(NEW.id);
    RETURN NEW;
  END;
  $$;


--
-- Name: patients_validate_member_consistency(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.patients_validate_member_consistency() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
  v_tenant_responsible uuid;
  v_tenant_therapist uuid;
BEGIN
  -- responsible_member sempre deve existir e ser do tenant
  SELECT tenant_id INTO v_tenant_responsible
  FROM public.tenant_members
  WHERE id = NEW.responsible_member_id;

  IF v_tenant_responsible IS NULL THEN
    RAISE EXCEPTION 'Responsible member not found';
  END IF;

  IF NEW.tenant_id IS NULL THEN
    RAISE EXCEPTION 'tenant_id is required';
  END IF;

  IF v_tenant_responsible <> NEW.tenant_id THEN
    RAISE EXCEPTION 'Responsible member must belong to the same tenant';
  END IF;

  -- therapist scope: therapist_member_id deve existir e ser do mesmo tenant
  IF NEW.patient_scope = 'therapist' THEN
    IF NEW.therapist_member_id IS NULL THEN
      RAISE EXCEPTION 'therapist_member_id is required when patient_scope=therapist';
    END IF;

    SELECT tenant_id INTO v_tenant_therapist
    FROM public.tenant_members
    WHERE id = NEW.therapist_member_id;

    IF v_tenant_therapist IS NULL THEN
      RAISE EXCEPTION 'Therapist member not found';
    END IF;

    IF v_tenant_therapist <> NEW.tenant_id THEN
      RAISE EXCEPTION 'Therapist member must belong to the same tenant';
    END IF;
  END IF;

  RETURN NEW;
END;
$$;


--
-- Name: patients_validate_responsible_member_tenant(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.patients_validate_responsible_member_tenant() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
declare
  m_tenant uuid;
begin
  select tenant_id into m_tenant
  from public.tenant_members
  where id = new.responsible_member_id;

  if m_tenant is null then
    raise exception 'Responsible member not found';
  end if;

  if new.tenant_id is null then
    raise exception 'tenant_id is required';
  end if;

  if m_tenant <> new.tenant_id then
    raise exception 'Responsible member must belong to the same tenant';
  end if;

  return new;
end;
$$;


--
-- Name: populate_notification_queue(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.populate_notification_queue() RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  INSERT INTO public.notification_queue (
    tenant_id, owner_id, agenda_evento_id, patient_id,
    channel, template_key, schedule_key,
    resolved_vars, recipient_address,
    scheduled_at, idempotency_key
  )
  SELECT
    ae.tenant_id,
    ae.owner_id,
    ae.id AS agenda_evento_id,
    ae.patient_id,
    ch.channel,
    'session.' || REPLACE(ns.event_type, '_sessao', '') || '.' || ch.channel,
    ns.schedule_key,
    jsonb_build_object(
      'nome_paciente',   COALESCE(p.nome_completo, 'Paciente'),
      'data_sessao',     TO_CHAR(ae.inicio_em AT TIME ZONE 'America/Sao_Paulo', 'DD/MM/YYYY'),
      'hora_sessao',     TO_CHAR(ae.inicio_em AT TIME ZONE 'America/Sao_Paulo', 'HH24:MI'),
      'nome_terapeuta',  COALESCE(prof.full_name, 'Terapeuta'),
      'modalidade',      COALESCE(ae.modalidade, 'Presencial'),
      'titulo',          COALESCE(ae.titulo, 'Sess??o')
    ),
    CASE ch.channel
      WHEN 'whatsapp' THEN COALESCE(p.telefone, '')
      WHEN 'sms'      THEN COALESCE(p.telefone, '')
      WHEN 'email'    THEN COALESCE(p.email_principal, '')
    END,
    CASE
      WHEN (ae.inicio_em - (ns.offset_minutes || ' minutes')::interval)::time
           < ns.allowed_time_start
      THEN DATE_TRUNC('day', ae.inicio_em - (ns.offset_minutes || ' minutes')::interval)
           + ns.allowed_time_start
      WHEN (ae.inicio_em - (ns.offset_minutes || ' minutes')::interval)::time
           > ns.allowed_time_end
      THEN DATE_TRUNC('day', ae.inicio_em - (ns.offset_minutes || ' minutes')::interval)
           + ns.allowed_time_start
      ELSE ae.inicio_em - (ns.offset_minutes || ' minutes')::interval
    END,
    ae.id::text || ':' || ns.schedule_key || ':' || ch.channel || ':'
      || ae.inicio_em::date::text
  FROM public.agenda_eventos ae
  JOIN public.patients p ON p.id = ae.patient_id
  LEFT JOIN public.profiles prof ON prof.id = ae.owner_id
  JOIN public.notification_schedules ns
    ON ns.owner_id = ae.owner_id
    AND ns.is_active = true
    AND ns.deleted_at IS NULL
    AND ns.trigger_type = 'before_event'
    AND ns.event_type = 'lembrete_sessao'
  JOIN public.notification_channels nc
    ON nc.owner_id = ae.owner_id
    AND nc.is_active = true
    AND nc.deleted_at IS NULL
  CROSS JOIN LATERAL (
    SELECT 'whatsapp' AS channel WHERE ns.whatsapp_enabled AND nc.channel = 'whatsapp'
    UNION ALL
    SELECT 'email'    AS channel WHERE ns.email_enabled    AND nc.channel = 'email'
    UNION ALL
    SELECT 'sms'      AS channel WHERE ns.sms_enabled      AND nc.channel = 'sms'
  ) ch
  LEFT JOIN public.notification_preferences np
    ON np.patient_id = ae.patient_id
    AND np.owner_id = ae.owner_id
    AND np.deleted_at IS NULL
  WHERE
    ae.inicio_em > now()
    AND ae.inicio_em <= now() + interval '48 hours'
    AND ae.status NOT IN ('cancelado', 'faltou')
    AND CASE ch.channel
          WHEN 'whatsapp' THEN COALESCE(p.telefone, '') != ''
          WHEN 'sms'      THEN COALESCE(p.telefone, '') != ''
          WHEN 'email'    THEN COALESCE(p.email_principal, '') != ''
        END
    AND CASE ch.channel
          WHEN 'whatsapp' THEN COALESCE(np.whatsapp_opt_in, true)
          WHEN 'email'    THEN COALESCE(np.email_opt_in, true)
          WHEN 'sms'      THEN COALESCE(np.sms_opt_in, false)
        END
    AND EXISTS (
      SELECT 1 FROM public.profiles tp
      WHERE tp.id = ae.owner_id
      AND COALESCE(tp.notify_reminders, true) = true
    )
  ON CONFLICT (idempotency_key) DO NOTHING;
END;
$$;


--
-- Name: prevent_promoting_to_system(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.prevent_promoting_to_system() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  if new.is_system = true and old.is_system is distinct from true then
    raise exception 'N??o ?? permitido transformar um grupo comum em grupo do sistema.';
  end if;
  return new;
end;
$$;


--
-- Name: prevent_saas_membership(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.prevent_saas_membership() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF EXISTS (
    SELECT 1
    FROM public.profiles
    WHERE id = NEW.user_id
    AND role = 'saas_admin'
  ) THEN
    RAISE EXCEPTION 'SaaS admin cannot belong to tenant';
  END IF;

  RETURN NEW;
END;
$$;


--
-- Name: prevent_system_group_changes(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.prevent_system_group_changes() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  -- Se for grupo do sistema, regras r??gidas:
  if old.is_system = true then

    -- nunca pode deletar
    if tg_op = 'DELETE' then
      raise exception 'Grupos padr??o do sistema n??o podem ser alterados ou exclu??dos.';
    end if;

    if tg_op = 'UPDATE' then
      -- permite SOMENTE mudar tenant_id e/ou updated_at
      -- qualquer mudan??a de conte??do permanece proibida
      if
        new.nome         is distinct from old.nome or
        new.descricao    is distinct from old.descricao or
        new.cor          is distinct from old.cor or
        new.is_active    is distinct from old.is_active or
        new.is_system    is distinct from old.is_system or
        new.owner_id     is distinct from old.owner_id or
        new.therapist_id is distinct from old.therapist_id or
        new.created_at   is distinct from old.created_at
      then
        raise exception 'Grupos padr??o do sistema n??o podem ser alterados ou exclu??dos.';
      end if;

      -- chegou aqui: s?? tenant_id/updated_at mudaram -> ok
      return new;
    end if;

  end if;

  -- n??o-system: deixa passar
  if tg_op = 'DELETE' then
    return old;
  end if;

  return new;
end;
$$;


--
-- Name: provision_account_tenant(uuid, text, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.provision_account_tenant(p_user_id uuid, p_kind text, p_name text DEFAULT NULL::text) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
DECLARE
  v_tenant_id    uuid;
  v_account_type text;
  v_name         text;
BEGIN
  IF p_kind NOT IN ('therapist', 'clinic_coworking', 'clinic_reception', 'clinic_full') THEN
    RAISE EXCEPTION 'kind inv??lido: "%". Use: therapist, clinic_coworking, clinic_reception, clinic_full.', p_kind
      USING ERRCODE = 'P0001';
  END IF;

  v_account_type := CASE WHEN p_kind = 'therapist' THEN 'therapist' ELSE 'clinic' END;

  IF EXISTS (
    SELECT 1
    FROM public.tenant_members tm
    JOIN public.tenants t ON t.id = tm.tenant_id
    WHERE tm.user_id = p_user_id
      AND tm.role = 'tenant_admin'
      AND tm.status = 'active'
      AND t.kind = p_kind
  ) THEN
    RAISE EXCEPTION 'Usu??rio j?? possui um tenant do tipo "%".', p_kind
      USING ERRCODE = 'P0001';
  END IF;

  v_name := COALESCE(
    NULLIF(TRIM(p_name), ''),
    (
      SELECT COALESCE(NULLIF(TRIM(pr.full_name), ''), SPLIT_PART(au.email, '@', 1))
      FROM public.profiles pr
      JOIN auth.users au ON au.id = pr.id
      WHERE pr.id = p_user_id
    ),
    'Conta'
  );

  INSERT INTO public.tenants (name, kind, created_at)
  VALUES (v_name, p_kind, now())
  RETURNING id INTO v_tenant_id;

  INSERT INTO public.tenant_members (tenant_id, user_id, role, status, created_at)
  VALUES (v_tenant_id, p_user_id, 'tenant_admin', 'active', now());

  UPDATE public.profiles
  SET account_type = v_account_type
  WHERE id = p_user_id;

  PERFORM public.seed_determined_commitments(v_tenant_id);

  RETURN v_tenant_id;
END;
$$;


--
-- Name: FUNCTION provision_account_tenant(p_user_id uuid, p_kind text, p_name text); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.provision_account_tenant(p_user_id uuid, p_kind text, p_name text) IS 'Cria o tenant do tipo correto e atualiza account_type no profile. Chamar no onboarding ap??s escolha/pagamento de plano therapist ou clinic. p_kind: therapist | clinic_coworking | clinic_reception | clinic_full';


--
-- Name: reactivate_subscription(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.reactivate_subscription(p_subscription_id uuid) RETURNS public.subscriptions
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_sub public.subscriptions;
  v_owner_type text;
  v_owner_ref uuid;
begin

  select *
  into v_sub
  from public.subscriptions
  where id = p_subscription_id
  for update;

  if not found then
    raise exception 'Subscription n??o encontrada';
  end if;

  if v_sub.status = 'active' then
    return v_sub;
  end if;

  if v_sub.tenant_id is not null then
    v_owner_type := 'clinic';
    v_owner_ref := v_sub.tenant_id;
  elsif v_sub.user_id is not null then
    v_owner_type := 'therapist';
    v_owner_ref := v_sub.user_id;
  else
    v_owner_type := null;
    v_owner_ref := null;
  end if;

  update public.subscriptions
  set status = 'active',
      cancel_at_period_end = false,
      updated_at = now()
  where id = p_subscription_id
  returning * into v_sub;

  insert into public.subscription_events(
    subscription_id,
    owner_id,
    owner_type,
    owner_ref,
    event_type,
    old_plan_id,
    new_plan_id,
    created_by,
    reason,
    source,
    metadata
  )
  values (
    v_sub.id,
    v_owner_ref,
    v_owner_type,
    v_owner_ref,
    'reactivated',
    v_sub.plan_id,
    v_sub.plan_id,
    auth.uid(),
    'Reativa????o manual via admin',
    'admin_panel',
    jsonb_build_object('previous_status', 'canceled')
  );

  if v_owner_ref is not null then
    insert into public.entitlements_invalidation(owner_id, changed_at)
    values (v_owner_ref, now())
    on conflict (owner_id)
    do update set changed_at = excluded.changed_at;
  end if;

  return v_sub;

end;
$$;


--
-- Name: rebuild_owner_entitlements(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.rebuild_owner_entitlements(p_owner_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_plan_id uuid;
begin
  -- Plano ativo do owner (owner = subscriptions.user_id)
  select s.plan_id
    into v_plan_id
  from public.subscriptions s
  where s.user_id = p_owner_id
    and s.status = 'active'
  order by s.created_at desc
  limit 1;

  -- Sempre zera entitlements do owner (rebuild)
  delete from public.owner_feature_entitlements e
  where e.owner_id = p_owner_id;

  -- Se n??o tem assinatura ativa, acabou
  if v_plan_id is null then
    return;
  end if;

  -- Recria entitlements esperados pelo plano
  insert into public.owner_feature_entitlements (owner_id, feature_key, sources, limits_list)
  select
    p_owner_id as owner_id,
    f.key as feature_key,
    array['plan'::text] as sources,
    '{}'::jsonb as limits_list
  from public.plan_features pf
  join public.features f on f.id = pf.feature_id
  where pf.plan_id = v_plan_id;

end;
$$;


--
-- Name: record_submission_attempt(text, text, boolean, text, text, text, text, jsonb); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.record_submission_attempt(p_endpoint text, p_ip_hash text, p_success boolean, p_blocked_by text DEFAULT NULL::text, p_error_code text DEFAULT NULL::text, p_error_msg text DEFAULT NULL::text, p_user_agent text DEFAULT NULL::text, p_metadata jsonb DEFAULT NULL::jsonb) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    cfg            saas_security_config%ROWTYPE;
    v_now          timestamptz := now();
    v_window_start timestamptz;
    rl             submission_rate_limits%ROWTYPE;
BEGIN
    -- Log sempre (mesmo sem ip)
    INSERT INTO public_submission_attempts
        (endpoint, ip_hash, success, blocked_by, error_code, error_msg, user_agent, metadata)
    VALUES
        (p_endpoint, p_ip_hash, p_success, p_blocked_by,
         left(coalesce(p_error_code, ''), 80),
         left(coalesce(p_error_msg, ''), 500),
         left(coalesce(p_user_agent, ''), 500),
         p_metadata);

    -- Sem ip ou rate limit desligado: não atualiza contador
    IF p_ip_hash IS NULL OR length(btrim(p_ip_hash)) = 0 THEN RETURN; END IF;

    SELECT * INTO cfg FROM saas_security_config WHERE id = true;
    IF NOT FOUND OR NOT cfg.rate_limit_enabled THEN RETURN; END IF;

    v_window_start := v_now - (cfg.rate_limit_window_min || ' minutes')::interval;

    SELECT * INTO rl
      FROM submission_rate_limits
     WHERE ip_hash = p_ip_hash AND endpoint = p_endpoint;

    IF NOT FOUND THEN
        INSERT INTO submission_rate_limits
            (ip_hash, endpoint, attempt_count, fail_count, window_start, last_attempt_at)
        VALUES
            (p_ip_hash, p_endpoint, 1, CASE WHEN p_success THEN 0 ELSE 1 END, v_now, v_now);
    ELSE
        IF rl.window_start < v_window_start THEN
            -- Reset janela
            UPDATE submission_rate_limits
               SET attempt_count = 1,
                   fail_count = CASE WHEN p_success THEN 0 ELSE 1 END,
                   window_start = v_now,
                   last_attempt_at = v_now,
                   blocked_until = NULL
             WHERE ip_hash = p_ip_hash AND endpoint = p_endpoint;
        ELSE
            UPDATE submission_rate_limits
               SET attempt_count = attempt_count + 1,
                   fail_count = fail_count + CASE WHEN p_success THEN 0 ELSE 1 END,
                   last_attempt_at = v_now
             WHERE ip_hash = p_ip_hash AND endpoint = p_endpoint;
        END IF;

        -- Se atingiu threshold de captcha condicional, marca
        IF NOT p_success THEN
            SELECT * INTO rl FROM submission_rate_limits WHERE ip_hash = p_ip_hash AND endpoint = p_endpoint;
            IF rl.fail_count >= cfg.captcha_after_failures
               AND (rl.requires_captcha_until IS NULL OR rl.requires_captcha_until < v_now) THEN
                UPDATE submission_rate_limits
                   SET requires_captcha_until = v_now + (cfg.captcha_required_window_min || ' minutes')::interval
                 WHERE ip_hash = p_ip_hash AND endpoint = p_endpoint;
            END IF;
        END IF;
    END IF;
END;
$$;


--
-- Name: revoke_support_session(text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.revoke_support_session(p_token text) RETURNS boolean
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_admin_id uuid;
  v_role     text;
BEGIN
  v_admin_id := auth.uid();
  IF v_admin_id IS NULL THEN
    RAISE EXCEPTION 'N??o autenticado.' USING ERRCODE = 'P0001';
  END IF;

  SELECT role INTO v_role FROM public.profiles WHERE id = v_admin_id;
  IF v_role <> 'saas_admin' THEN
    RAISE EXCEPTION 'Acesso negado.' USING ERRCODE = 'P0002';
  END IF;

  DELETE FROM public.support_sessions
   WHERE token = p_token
     AND admin_id = v_admin_id;

  RETURN FOUND;
END;
$$;


--
-- Name: rotate_patient_invite_token(text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.rotate_patient_invite_token(p_new_token text) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
declare
  v_uid uuid;
  v_id uuid;
begin
  -- pega o usu??rio logado
  v_uid := auth.uid();
  if v_uid is null then
    raise exception 'Usu??rio n??o autenticado';
  end if;

  -- desativa tokens antigos ativos do usu??rio
  update public.patient_invites
    set active = false
  where owner_id = v_uid
    and active = true;

  -- cria novo token
  insert into public.patient_invites (owner_id, token, active)
  values (v_uid, p_new_token, true)
  returning id into v_id;

  return v_id;
end;
$$;


--
-- Name: rotate_patient_invite_token_v2(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.rotate_patient_invite_token_v2() RETURNS text
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_uid        uuid;
  v_tenant_id  uuid;
  v_new_token  text;
BEGIN
  v_uid := auth.uid();
  IF v_uid IS NULL THEN
    RAISE EXCEPTION 'Usuário não autenticado' USING ERRCODE = '28000';
  END IF;

  -- Token gerado no servidor (criptograficamente seguro via pgcrypto)
  v_new_token := replace(gen_random_uuid()::text, '-', '');

  -- Resolve tenant_id do usuário (active)
  SELECT tenant_id
    INTO v_tenant_id
  FROM public.tenant_members
  WHERE user_id = v_uid
    AND status = 'active'
  ORDER BY created_at ASC
  LIMIT 1;

  -- Desativa tokens ativos anteriores
  UPDATE public.patient_invites
    SET active = false
  WHERE owner_id = v_uid
    AND active = true;

  -- Insere novo
  INSERT INTO public.patient_invites (owner_id, tenant_id, token, active)
  VALUES (v_uid, v_tenant_id, v_new_token, true);

  RETURN v_new_token;
END;
$$;


--
-- Name: FUNCTION rotate_patient_invite_token_v2(); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.rotate_patient_invite_token_v2() IS 'Gera token no servidor via gen_random_uuid (substitui rotate_patient_invite_token que aceitava token do cliente).';


--
-- Name: saas_votar_doc(uuid, boolean); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.saas_votar_doc(p_doc_id uuid, p_util boolean) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_uid         uuid := auth.uid();
  v_voto_antigo boolean;
begin
  if v_uid is null then
    raise exception 'N??o autenticado';
  end if;

  -- Verifica se j?? votou
  select util into v_voto_antigo
  from public.saas_doc_votos
  where doc_id = p_doc_id and user_id = v_uid;

  if found then
    -- J?? votou igual ??? cancela o voto (toggle)
    if v_voto_antigo = p_util then
      delete from public.saas_doc_votos
      where doc_id = p_doc_id and user_id = v_uid;

      update public.saas_docs set
        votos_util     = greatest(0, votos_util     - (case when p_util  then 1 else 0 end)),
        votos_nao_util = greatest(0, votos_nao_util - (case when not p_util then 1 else 0 end)),
        updated_at = now()
      where id = p_doc_id;

      return jsonb_build_object('acao', 'removido', 'util', null);
    else
      -- Mudou de voto
      update public.saas_doc_votos set util = p_util, updated_at = now()
      where doc_id = p_doc_id and user_id = v_uid;

      update public.saas_docs set
        votos_util     = greatest(0, votos_util     + (case when p_util  then 1 else -1 end)),
        votos_nao_util = greatest(0, votos_nao_util + (case when not p_util then 1 else -1 end)),
        updated_at = now()
      where id = p_doc_id;

      return jsonb_build_object('acao', 'atualizado', 'util', p_util);
    end if;
  else
    -- Primeiro voto
    insert into public.saas_doc_votos (doc_id, user_id, util)
    values (p_doc_id, v_uid, p_util);

    update public.saas_docs set
      votos_util     = votos_util     + (case when p_util  then 1 else 0 end),
      votos_nao_util = votos_nao_util + (case when not p_util then 1 else 0 end),
      updated_at = now()
    where id = p_doc_id;

    return jsonb_build_object('acao', 'registrado', 'util', p_util);
  end if;
end;
$$;


--
-- Name: saas_wa_credits_revenue_evolution(timestamp with time zone, timestamp with time zone, integer); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.saas_wa_credits_revenue_evolution(p_from timestamp with time zone DEFAULT (now() - '30 days'::interval), p_to timestamp with time zone DEFAULT now(), p_bucket_days integer DEFAULT 7) RETURNS TABLE(bucket_start timestamp with time zone, purchases_count integer, revenue_brl numeric)
    LANGUAGE plpgsql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
    IF NOT public.is_saas_admin() THEN
        RAISE EXCEPTION 'permission_denied';
    END IF;

    RETURN QUERY
    WITH purchases AS (
        SELECT p.paid_at, p.amount_brl
        FROM public.whatsapp_credit_purchases p
        WHERE p.status = 'paid'
          AND p.paid_at >= p_from
          AND p.paid_at <= p_to
    ),
    bucketed AS (
        SELECT
            p_from + (
                FLOOR(EXTRACT(EPOCH FROM (paid_at - p_from)) / (p_bucket_days * 86400))::INT
                * p_bucket_days * interval '1 day'
            ) AS bucket_start,
            amount_brl
        FROM purchases
    )
    SELECT
        bucket_start,
        COUNT(*)::INT AS purchases_count,
        SUM(amount_brl)::NUMERIC AS revenue_brl
    FROM bucketed
    GROUP BY bucket_start
    ORDER BY bucket_start;
END;
$$;


--
-- Name: FUNCTION saas_wa_credits_revenue_evolution(p_from timestamp with time zone, p_to timestamp with time zone, p_bucket_days integer); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.saas_wa_credits_revenue_evolution(p_from timestamp with time zone, p_to timestamp with time zone, p_bucket_days integer) IS 'Serie temporal de receita em buckets de N dias (saas_admin).';


--
-- Name: saas_wa_credits_revenue_stats(timestamp with time zone, timestamp with time zone); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.saas_wa_credits_revenue_stats(p_from timestamp with time zone DEFAULT (now() - '30 days'::interval), p_to timestamp with time zone DEFAULT now()) RETURNS TABLE(revenue_brl numeric, purchases_count integer, tenants_count integer, credits_sold integer, avg_ticket_brl numeric)
    LANGUAGE plpgsql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
    IF NOT public.is_saas_admin() THEN
        RAISE EXCEPTION 'permission_denied';
    END IF;

    RETURN QUERY
    SELECT
        COALESCE(SUM(p.amount_brl), 0)::NUMERIC AS revenue_brl,
        COUNT(*)::INT AS purchases_count,
        COUNT(DISTINCT p.tenant_id)::INT AS tenants_count,
        COALESCE(SUM(p.credits), 0)::INT AS credits_sold,
        CASE WHEN COUNT(*) = 0 THEN 0
             ELSE ROUND(COALESCE(AVG(p.amount_brl), 0), 2)
        END AS avg_ticket_brl
    FROM public.whatsapp_credit_purchases p
    WHERE p.status = 'paid'
      AND p.paid_at >= p_from
      AND p.paid_at <= p_to;
END;
$$;


--
-- Name: FUNCTION saas_wa_credits_revenue_stats(p_from timestamp with time zone, p_to timestamp with time zone); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.saas_wa_credits_revenue_stats(p_from timestamp with time zone, p_to timestamp with time zone) IS 'Totais de receita WhatsApp creditos no periodo (saas_admin).';


--
-- Name: saas_wa_credits_top_packages(timestamp with time zone, timestamp with time zone); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.saas_wa_credits_top_packages(p_from timestamp with time zone DEFAULT (now() - '30 days'::interval), p_to timestamp with time zone DEFAULT now()) RETURNS TABLE(package_id uuid, package_name text, purchases_count integer, revenue_brl numeric, credits_sold integer)
    LANGUAGE plpgsql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
    IF NOT public.is_saas_admin() THEN
        RAISE EXCEPTION 'permission_denied';
    END IF;

    RETURN QUERY
    SELECT
        p.package_id,
        -- Nome snapshot do momento da compra; se tem package_id, usa o nome
        -- atual pra consolidar pacotes renomeados
        COALESCE(
            (SELECT pk.name FROM public.whatsapp_credit_packages pk WHERE pk.id = p.package_id),
            p.package_name
        ) AS package_name,
        COUNT(*)::INT AS purchases_count,
        SUM(p.amount_brl)::NUMERIC AS revenue_brl,
        SUM(p.credits)::INT AS credits_sold
    FROM public.whatsapp_credit_purchases p
    WHERE p.status = 'paid'
      AND p.paid_at >= p_from
      AND p.paid_at <= p_to
    GROUP BY p.package_id, p.package_name
    ORDER BY revenue_brl DESC
    LIMIT 10;
END;
$$;


--
-- Name: FUNCTION saas_wa_credits_top_packages(p_from timestamp with time zone, p_to timestamp with time zone); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.saas_wa_credits_top_packages(p_from timestamp with time zone, p_to timestamp with time zone) IS 'Ranking pacotes mais vendidos no periodo (saas_admin).';


--
-- Name: saas_wa_credits_usage_summary(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.saas_wa_credits_usage_summary() RETURNS TABLE(lifetime_purchased integer, lifetime_used integer, current_balance integer, usage_rate numeric, tenants_with_balance integer)
    LANGUAGE plpgsql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
    IF NOT public.is_saas_admin() THEN
        RAISE EXCEPTION 'permission_denied';
    END IF;

    RETURN QUERY
    SELECT
        COALESCE(SUM(lifetime_purchased), 0)::INT AS lifetime_purchased,
        COALESCE(SUM(lifetime_used), 0)::INT AS lifetime_used,
        COALESCE(SUM(balance), 0)::INT AS current_balance,
        CASE WHEN COALESCE(SUM(lifetime_purchased), 0) = 0 THEN 0
             ELSE ROUND(100.0 * COALESCE(SUM(lifetime_used), 0) / SUM(lifetime_purchased), 1)
        END AS usage_rate,
        COUNT(*)::INT AS tenants_with_balance
    FROM public.whatsapp_credits_balance;
END;
$$;


--
-- Name: FUNCTION saas_wa_credits_usage_summary(); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.saas_wa_credits_usage_summary() IS 'Snapshot atual: vendidos lifetime vs usados vs saldo (saas_admin).';


--
-- Name: safe_delete_patient(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.safe_delete_patient(p_patient_id uuid) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  -- Bloqueia se houver hist??rico
  IF NOT public.can_delete_patient(p_patient_id) THEN
    RETURN jsonb_build_object(
      'ok',      false,
      'error',   'has_history',
      'message', 'Este paciente possui hist??rico cl??nico ou financeiro e n??o pode ser removido. Voc?? pode desativar ou arquivar o paciente.'
    );
  END IF;

  -- Verifica ownership via RLS (owner_id ou responsible_member_id)
  IF NOT EXISTS (
    SELECT 1 FROM public.patients
     WHERE id = p_patient_id
       AND (
         owner_id = auth.uid()
         OR responsible_member_id IN (
           SELECT id FROM public.tenant_members WHERE user_id = auth.uid()
         )
       )
  ) THEN
    RETURN jsonb_build_object(
      'ok',      false,
      'error',   'forbidden',
      'message', 'Sem permiss??o para excluir este paciente.'
    );
  END IF;

  DELETE FROM public.patients WHERE id = p_patient_id;

  RETURN jsonb_build_object('ok', true);
END;
$$;


--
-- Name: sanitize_phone_br(text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.sanitize_phone_br(raw_phone text) RETURNS text
    LANGUAGE plpgsql IMMUTABLE
    AS $$                                                                           DECLARE digits text;
  BEGIN
    digits := regexp_replace(COALESCE(raw_phone, ''), '[^0-9]', '', 'g');
    IF digits = '' THEN RETURN ''; END IF;
    IF length(digits) = 10 OR length(digits) = 11 THEN
      digits := '55' || digits;
    END IF;
    RETURN digits;
  END; $$;


--
-- Name: search_global(text, text[], integer); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.search_global(p_q text, p_scope text[] DEFAULT NULL::text[], p_limit integer DEFAULT 8) RETURNS jsonb
    LANGUAGE plpgsql STABLE
    SET search_path TO 'public', 'pg_temp'
    AS $_$
DECLARE
    v_q            text;
    v_pattern      text;
    v_limit        int;
    v_patients     jsonb := '[]'::jsonb;
    v_appointments jsonb := '[]'::jsonb;
    v_documents    jsonb := '[]'::jsonb;
    v_services     jsonb := '[]'::jsonb;
    v_intakes      jsonb := '[]'::jsonb;
BEGIN
    -- Sanitize + length guards
    v_q := nullif(btrim(coalesce(p_q, '')), '');
    IF v_q IS NULL OR length(v_q) < 2 THEN
        RETURN jsonb_build_object(
            'patients',     '[]'::jsonb,
            'appointments', '[]'::jsonb,
            'documents',    '[]'::jsonb,
            'services',     '[]'::jsonb,
            'intakes',      '[]'::jsonb
        );
    END IF;
    v_q := left(v_q, 80);
    v_pattern := '%' || v_q || '%';
    v_limit := GREATEST(1, LEAST(coalesce(p_limit, 8), 20));

    -- ─────────────────────────────────────────────────────────────────────
    -- Pacientes
    -- ─────────────────────────────────────────────────────────────────────
    IF p_scope IS NULL OR 'patients' = ANY(p_scope) THEN
        WITH ranked AS (
            SELECT
                p.id,
                p.nome_completo,
                p.email_principal,
                p.telefone,
                p.avatar_url,
                GREATEST(
                    similarity(coalesce(p.nome_completo,   ''), v_q),
                    similarity(coalesce(p.email_principal, ''), v_q) * 0.7,
                    similarity(coalesce(p.telefone,        ''), v_q) * 0.5,
                    similarity(coalesce(p.cpf,             ''), v_q) * 0.6
                ) AS score
            FROM public.patients p
            WHERE p.nome_completo   ILIKE v_pattern
               OR p.email_principal ILIKE v_pattern
               OR p.telefone        ILIKE v_pattern
               OR p.cpf             ILIKE v_pattern
            ORDER BY score DESC, p.nome_completo ASC
            LIMIT v_limit
        )
        SELECT coalesce(jsonb_agg(jsonb_build_object(
            'id',         id,
            'label',      nome_completo,
            'sublabel',   coalesce(nullif(email_principal, ''), nullif(telefone, ''), ''),
            'avatar_url', avatar_url,
            'deeplink',   '/therapist/patients/cadastro/' || id::text,
            'score',      round(score::numeric, 3)
        )), '[]'::jsonb) INTO v_patients
        FROM ranked;
    END IF;

    -- ─────────────────────────────────────────────────────────────────────
    -- Agendamentos (com nome do paciente via join)
    -- ─────────────────────────────────────────────────────────────────────
    IF p_scope IS NULL OR 'appointments' = ANY(p_scope) THEN
        WITH ranked AS (
            SELECT
                e.id,
                coalesce(nullif(e.titulo_custom, ''), nullif(e.titulo, ''), 'Sessão') AS label,
                e.inicio_em,
                pat.nome_completo AS patient_name,
                GREATEST(
                    similarity(coalesce(e.titulo,        ''), v_q),
                    similarity(coalesce(e.titulo_custom, ''), v_q),
                    similarity(coalesce(pat.nome_completo, ''), v_q) * 0.9
                ) AS score
            FROM public.agenda_eventos e
            LEFT JOIN public.patients pat ON pat.id = e.patient_id
            WHERE e.titulo          ILIKE v_pattern
               OR e.titulo_custom   ILIKE v_pattern
               OR pat.nome_completo ILIKE v_pattern
            ORDER BY score DESC, e.inicio_em DESC
            LIMIT v_limit
        )
        SELECT coalesce(jsonb_agg(jsonb_build_object(
            'id',       id,
            'label',    label,
            'sublabel', trim(both ' · ' from
                            coalesce(patient_name, '') || ' · '
                            || to_char(inicio_em, 'DD/MM/YYYY HH24:MI')),
            'deeplink', '/therapist/agenda?event=' || id::text,
            'score',    round(score::numeric, 3)
        )), '[]'::jsonb) INTO v_appointments
        FROM ranked;
    END IF;

    -- ─────────────────────────────────────────────────────────────────────
    -- Documentos
    -- ─────────────────────────────────────────────────────────────────────
    IF p_scope IS NULL OR 'documents' = ANY(p_scope) THEN
        WITH ranked AS (
            SELECT
                d.id,
                d.patient_id,
                d.nome_original,
                d.tipo_documento,
                pat.nome_completo AS patient_name,
                GREATEST(
                    similarity(coalesce(d.nome_original, ''), v_q),
                    similarity(coalesce(d.descricao,     ''), v_q) * 0.7
                ) AS score
            FROM public.documents d
            LEFT JOIN public.patients pat ON pat.id = d.patient_id
            WHERE d.nome_original ILIKE v_pattern
               OR d.descricao     ILIKE v_pattern
            ORDER BY score DESC, d.nome_original ASC
            LIMIT v_limit
        )
        SELECT coalesce(jsonb_agg(jsonb_build_object(
            'id',       id,
            'label',    nome_original,
            'sublabel', trim(both ' · ' from
                            coalesce(patient_name, '') || ' · '
                            || coalesce(tipo_documento, '')),
            'deeplink', '/therapist/patients/' || patient_id::text || '/documents',
            'score',    round(score::numeric, 3)
        )), '[]'::jsonb) INTO v_documents
        FROM ranked;
    END IF;

    -- ─────────────────────────────────────────────────────────────────────
    -- Serviços (ativos)
    -- ─────────────────────────────────────────────────────────────────────
    IF p_scope IS NULL OR 'services' = ANY(p_scope) THEN
        WITH ranked AS (
            SELECT
                s.id,
                s.name,
                s.price,
                s.duration_min,
                GREATEST(
                    similarity(coalesce(s.name,        ''), v_q),
                    similarity(coalesce(s.description, ''), v_q) * 0.7
                ) AS score
            FROM public.services s
            WHERE s.active IS TRUE
              AND (s.name        ILIKE v_pattern
                OR s.description ILIKE v_pattern)
            ORDER BY score DESC, s.name ASC
            LIMIT v_limit
        )
        SELECT coalesce(jsonb_agg(jsonb_build_object(
            'id',       id,
            'label',    name,
            'sublabel', trim(both ' · ' from
                            'R$ ' || to_char(price, 'FM999G999G990D00') || ' · '
                            || coalesce(duration_min::text || ' min', '')),
            'deeplink', '/configuracoes/precificacao',
            'score',    round(score::numeric, 3)
        )), '[]'::jsonb) INTO v_services
        FROM ranked;
    END IF;

    -- ─────────────────────────────────────────────────────────────────────
    -- Intakes pendentes (patient_intake_requests com status='new')
    -- ─────────────────────────────────────────────────────────────────────
    IF p_scope IS NULL OR 'intakes' = ANY(p_scope) THEN
        WITH ranked AS (
            SELECT
                r.id,
                r.nome_completo,
                r.email_principal,
                r.telefone,
                r.created_at,
                GREATEST(
                    similarity(coalesce(r.nome_completo,   ''), v_q),
                    similarity(coalesce(r.email_principal, ''), v_q) * 0.7,
                    similarity(coalesce(r.telefone,        ''), v_q) * 0.5
                ) AS score
            FROM public.patient_intake_requests r
            WHERE r.status = 'new'
              AND (r.nome_completo   ILIKE v_pattern
                OR r.email_principal ILIKE v_pattern
                OR r.telefone        ILIKE v_pattern)
            ORDER BY score DESC, r.created_at DESC
            LIMIT v_limit
        )
        SELECT coalesce(jsonb_agg(jsonb_build_object(
            'id',       id,
            'label',    coalesce(nullif(trim(nome_completo), ''), '(sem nome)'),
            'sublabel', trim(both ' · ' from
                            coalesce(nullif(email_principal, ''), nullif(telefone, ''), '') || ' · '
                            || 'recebido ' || to_char(created_at, 'DD/MM/YYYY')),
            'deeplink', '/therapist/patients/cadastro/recebidos?id=' || id::text,
            'score',    round(score::numeric, 3)
        )), '[]'::jsonb) INTO v_intakes
        FROM ranked;
    END IF;

    RETURN jsonb_build_object(
        'patients',     v_patients,
        'appointments', v_appointments,
        'documents',    v_documents,
        'services',     v_services,
        'intakes',      v_intakes
    );
END;
$_$;


--
-- Name: FUNCTION search_global(p_q text, p_scope text[], p_limit integer); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.search_global(p_q text, p_scope text[], p_limit integer) IS 'Busca global do topbar — retorna jsonb agrupado por entidade. SECURITY INVOKER (RLS do chamador aplica).';


--
-- Name: seed_default_financial_categories(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.seed_default_financial_categories(p_user_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
  INSERT INTO public.financial_categories (user_id, name, type, color, icon, sort_order)
  VALUES
    (p_user_id, 'Sess??o',             'receita', '#22c55e', 'pi pi-heart',                    1),
    (p_user_id, 'Supervis??o',         'receita', '#6366f1', 'pi pi-users',                    2),
    (p_user_id, 'Conv??nio',           'receita', '#3b82f6', 'pi pi-building',                 3),
    (p_user_id, 'Grupo terap??utico',  'receita', '#f59e0b', 'pi pi-sitemap',                  4),
    (p_user_id, 'Outro (receita)',    'receita', '#8b5cf6', 'pi pi-plus-circle',              5),
    (p_user_id, 'Aluguel sala',       'despesa', '#ef4444', 'pi pi-home',                     1),
    (p_user_id, 'Plataforma/SaaS',    'despesa', '#f97316', 'pi pi-desktop',                  2),
    (p_user_id, 'Repasse cl??nica',    'despesa', '#64748b', 'pi pi-arrow-right-arrow-left',   3),
    (p_user_id, 'Supervis??o (custo)', 'despesa', '#6366f1', 'pi pi-users',                    4),
    (p_user_id, 'Outro (despesa)',    'despesa', '#94a3b8', 'pi pi-minus-circle',             5)
  ON CONFLICT DO NOTHING;
END;
$$;


--
-- Name: seed_default_patient_groups(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.seed_default_patient_groups(p_tenant_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_owner_id uuid;
BEGIN
  -- busca o owner (tenant_admin) do tenant
  SELECT user_id INTO v_owner_id
  FROM public.tenant_members
  WHERE tenant_id = p_tenant_id
    AND role = 'tenant_admin'
    AND status = 'active'
  LIMIT 1;

  IF v_owner_id IS NULL THEN
    RETURN;
  END IF;

  INSERT INTO public.patient_groups (owner_id, nome, cor, is_system, tenant_id)
  VALUES
    (v_owner_id, 'Crian??as',     '#60a5fa', true, p_tenant_id),
    (v_owner_id, 'Adolescentes', '#a78bfa', true, p_tenant_id),
    (v_owner_id, 'Idosos',       '#34d399', true, p_tenant_id)
  ON CONFLICT (owner_id, nome) DO NOTHING;
END;
$$;


--
-- Name: seed_determined_commitments(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.seed_determined_commitments(p_tenant_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_id uuid;
begin
  -- Sessão (locked + sempre ativa)
  if not exists (
    select 1 from public.determined_commitments
    where tenant_id = p_tenant_id and is_native = true and native_key = 'session'
  ) then
    insert into public.determined_commitments
      (tenant_id, is_native, native_key, is_locked, active, name, description)
    values
      (p_tenant_id, true, 'session', true, true, 'Sessão', 'Sessão com paciente');
  end if;

  -- Leitura
  if not exists (
    select 1 from public.determined_commitments
    where tenant_id = p_tenant_id and is_native = true and native_key = 'reading'
  ) then
    insert into public.determined_commitments
      (tenant_id, is_native, native_key, is_locked, active, name, description)
    values
      (p_tenant_id, true, 'reading', false, true, 'Leitura', 'Praticar leitura');
  end if;

  -- Supervisão
  if not exists (
    select 1 from public.determined_commitments
    where tenant_id = p_tenant_id and is_native = true and native_key = 'supervision'
  ) then
    insert into public.determined_commitments
      (tenant_id, is_native, native_key, is_locked, active, name, description)
    values
      (p_tenant_id, true, 'supervision', false, true, 'Supervisão', 'Supervisão');
  end if;

  -- Aula
  if not exists (
    select 1 from public.determined_commitments
    where tenant_id = p_tenant_id and is_native = true and native_key = 'class'
  ) then
    insert into public.determined_commitments
      (tenant_id, is_native, native_key, is_locked, active, name, description)
    values
      (p_tenant_id, true, 'class', false, false, 'Aula', 'Dar aula');
  end if;

  -- Análise pessoal
  if not exists (
    select 1 from public.determined_commitments
    where tenant_id = p_tenant_id and is_native = true and native_key = 'analysis'
  ) then
    insert into public.determined_commitments
      (tenant_id, is_native, native_key, is_locked, active, name, description)
    values
      (p_tenant_id, true, 'analysis', false, true, 'Análise Pessoal', 'Minha análise pessoal');
  end if;

  -- -------------------------------------------------------
  -- Campos padrão (idempotentes por (commitment_id, key))
  -- -------------------------------------------------------

  -- Sessão (NOVO em 2026-05-11: 'notes' como Observação default)
  select id into v_id
  from public.determined_commitments
  where tenant_id = p_tenant_id and is_native = true and native_key = 'session'
  limit 1;

  if v_id is not null then
    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'notes') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'notes', 'Observação', 'textarea', false, 30);
    end if;
  end if;

  -- Leitura
  select id into v_id
  from public.determined_commitments
  where tenant_id = p_tenant_id and is_native = true and native_key = 'reading'
  limit 1;

  if v_id is not null then
    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'book') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'book', 'Livro', 'text', false, 10);
    end if;

    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'author') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'author', 'Autor', 'text', false, 20);
    end if;

    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'notes') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'notes', 'Observação', 'textarea', false, 30);
    end if;
  end if;

  -- Supervisão
  select id into v_id
  from public.determined_commitments
  where tenant_id = p_tenant_id and is_native = true and native_key = 'supervision'
  limit 1;

  if v_id is not null then
    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'supervisor') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'supervisor', 'Supervisor', 'text', false, 10);
    end if;

    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'topic') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'topic', 'Assunto', 'text', false, 20);
    end if;

    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'notes') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'notes', 'Observação', 'textarea', false, 30);
    end if;
  end if;

  -- Aula
  select id into v_id
  from public.determined_commitments
  where tenant_id = p_tenant_id and is_native = true and native_key = 'class'
  limit 1;

  if v_id is not null then
    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'theme') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'theme', 'Tema', 'text', false, 10);
    end if;

    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'group') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'group', 'Turma', 'text', false, 20);
    end if;

    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'notes') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'notes', 'Observação', 'textarea', false, 30);
    end if;
  end if;

  -- Análise
  select id into v_id
  from public.determined_commitments
  where tenant_id = p_tenant_id and is_native = true and native_key = 'analysis'
  limit 1;

  if v_id is not null then
    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'analyst') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'analyst', 'Analista', 'text', false, 10);
    end if;

    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'focus') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'focus', 'Foco', 'text', false, 20);
    end if;

    if not exists (select 1 from public.determined_commitment_fields where commitment_id = v_id and key = 'notes') then
      insert into public.determined_commitment_fields (tenant_id, commitment_id, key, label, field_type, required, sort_order)
      values (p_tenant_id, v_id, 'notes', 'Observação', 'textarea', false, 30);
    end if;
  end if;
end;
$$;


--
-- Name: set_insurance_plans_updated_at(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.set_insurance_plans_updated_at() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN NEW.updated_at = now(); RETURN NEW; END; $$;


--
-- Name: set_medicos_updated_at(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.set_medicos_updated_at() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
    NEW.updated_at = now();
    RETURN NEW;
END;
$$;


--
-- Name: set_owner_id(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.set_owner_id() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  if new.owner_id is null then
    new.owner_id := auth.uid();
  end if;
  return new;
end;
$$;


--
-- Name: set_services_updated_at(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.set_services_updated_at() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$;


--
-- Name: set_tenant_feature_exception(uuid, text, boolean, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.set_tenant_feature_exception(p_tenant_id uuid, p_feature_key text, p_enabled boolean, p_reason text DEFAULT NULL::text) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $_$
DECLARE
  v_caller         uuid := auth.uid();
  v_is_saas        boolean := public.is_saas_admin();
  v_is_tenant_adm  boolean;
  v_plan_allows    boolean;
  v_feature_key    text;
  v_reason         text;
  v_is_exception   boolean;
BEGIN
  -- ───────────────────────────────────────────────────────────────────────
  -- Sanitização (padrão V#31)
  -- ───────────────────────────────────────────────────────────────────────
  IF v_caller IS NULL THEN
    RAISE EXCEPTION 'Não autenticado' USING ERRCODE = '28000';
  END IF;

  IF p_tenant_id IS NULL THEN
    RAISE EXCEPTION 'tenant_id obrigatório' USING ERRCODE = '22023';
  END IF;

  IF p_enabled IS NULL THEN
    RAISE EXCEPTION 'enabled obrigatório' USING ERRCODE = '22023';
  END IF;

  v_feature_key := nullif(btrim(coalesce(p_feature_key, '')), '');
  IF v_feature_key IS NULL THEN
    RAISE EXCEPTION 'feature_key obrigatório' USING ERRCODE = '22023';
  END IF;
  IF length(v_feature_key) > 80 THEN
    RAISE EXCEPTION 'feature_key inválido (>80)' USING ERRCODE = '22023';
  END IF;
  IF v_feature_key !~ '^[a-z][a-z0-9_.]*$' THEN
    RAISE EXCEPTION 'feature_key formato inválido' USING ERRCODE = '22023';
  END IF;

  v_reason := nullif(btrim(coalesce(p_reason, '')), '');
  IF v_reason IS NOT NULL AND length(v_reason) > 500 THEN
    v_reason := substring(v_reason FROM 1 FOR 500);
  END IF;

  IF NOT EXISTS (SELECT 1 FROM public.features WHERE key = v_feature_key) THEN
    RAISE EXCEPTION 'feature_key desconhecida: %', v_feature_key USING ERRCODE = '22023';
  END IF;

  IF NOT EXISTS (SELECT 1 FROM public.tenants WHERE id = p_tenant_id) THEN
    RAISE EXCEPTION 'tenant não encontrado' USING ERRCODE = '22023';
  END IF;

  -- ───────────────────────────────────────────────────────────────────────
  -- Plano permite essa feature?
  -- ───────────────────────────────────────────────────────────────────────
  SELECT EXISTS (
    SELECT 1
    FROM public.v_tenant_entitlements vte
    WHERE vte.tenant_id = p_tenant_id
      AND vte.feature_key = v_feature_key
  ) INTO v_plan_allows;

  v_is_exception := (p_enabled = true AND NOT v_plan_allows);

  -- ───────────────────────────────────────────────────────────────────────
  -- Caller é tenant_admin desse tenant?
  -- ───────────────────────────────────────────────────────────────────────
  v_is_tenant_adm := EXISTS (
    SELECT 1 FROM public.tenant_members tm
    WHERE tm.tenant_id = p_tenant_id
      AND tm.user_id   = v_caller
      AND tm.status    = 'active'
      AND tm.role IN ('tenant_admin','admin','owner')
  );

  -- ───────────────────────────────────────────────────────────────────────
  -- Autorização (assimétrica — V#34 Opção B2)
  -- ───────────────────────────────────────────────────────────────────────
  IF v_is_exception THEN
    -- Override positivo fora do plano = exceção comercial
    IF NOT v_is_saas THEN
      RAISE EXCEPTION 'Apenas saas_admin pode liberar feature fora do plano' USING ERRCODE = '42501';
    END IF;
    IF v_reason IS NULL THEN
      RAISE EXCEPTION 'reason obrigatório para exceção comercial' USING ERRCODE = '22023';
    END IF;
  ELSE
    -- Demais casos: tenant_admin OR saas_admin
    IF NOT (v_is_saas OR v_is_tenant_adm) THEN
      RAISE EXCEPTION 'Sem permissão para alterar features deste tenant' USING ERRCODE = '42501';
    END IF;
  END IF;

  -- ───────────────────────────────────────────────────────────────────────
  -- Persistência: bypass controlado do trigger guard quando é exceção
  -- (escopo de transação via SET LOCAL — só esta RPC vê)
  -- ───────────────────────────────────────────────────────────────────────
  IF v_is_exception THEN
    PERFORM set_config('app.allow_feature_exception', 'true', true);
  END IF;

  INSERT INTO public.tenant_features (tenant_id, feature_key, enabled, updated_at)
  VALUES (p_tenant_id, v_feature_key, p_enabled, now())
  ON CONFLICT (tenant_id, feature_key)
  DO UPDATE SET enabled = EXCLUDED.enabled, updated_at = now();

  -- Restaura flag (defensivo — SET LOCAL já é por transação, mas explicito)
  IF v_is_exception THEN
    PERFORM set_config('app.allow_feature_exception', 'false', true);
  END IF;

  INSERT INTO public.tenant_feature_exceptions_log
    (tenant_id, feature_key, enabled, reason, created_by)
  VALUES
    (p_tenant_id, v_feature_key, p_enabled, v_reason, v_caller);

  RETURN jsonb_build_object(
    'tenant_id',    p_tenant_id,
    'feature_key',  v_feature_key,
    'enabled',      p_enabled,
    'plan_allows',  v_plan_allows,
    'is_exception', v_is_exception,
    'reason',       v_reason
  );
END;
$_$;


--
-- Name: set_updated_at(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.set_updated_at() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$;


--
-- Name: set_updated_at_recurrence(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.set_updated_at_recurrence() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN NEW.updated_at = now(); RETURN NEW; END;
$$;


--
-- Name: sla_mark_notified(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.sla_mark_notified(p_breach_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
    UPDATE public.conversation_sla_breaches
        SET notified_at = now(),
            notification_count = notification_count + 1
        WHERE id = p_breach_id;
END;
$$;


--
-- Name: sla_open_breach(uuid, text, uuid, timestamp with time zone, integer); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.sla_open_breach(p_tenant_id uuid, p_thread_key text, p_assigned_to uuid, p_last_inbound_at timestamp with time zone, p_threshold_minutes integer) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_existing_id UUID;
    v_new_id UUID;
BEGIN
    IF p_tenant_id IS NULL OR p_thread_key IS NULL THEN
        RAISE EXCEPTION 'tenant_and_thread_required';
    END IF;

    -- Ja tem aberto? Retorna o mesmo id (idempotente)
    SELECT id INTO v_existing_id
        FROM public.conversation_sla_breaches
        WHERE tenant_id = p_tenant_id
          AND thread_key = p_thread_key
          AND resolved_at IS NULL;

    IF FOUND THEN
        UPDATE public.conversation_sla_breaches
            SET assigned_to = COALESCE(p_assigned_to, assigned_to),
                last_inbound_at = COALESCE(p_last_inbound_at, last_inbound_at)
            WHERE id = v_existing_id;
        RETURN v_existing_id;
    END IF;

    INSERT INTO public.conversation_sla_breaches
        (tenant_id, thread_key, assigned_to, last_inbound_at, threshold_minutes_at_breach)
    VALUES
        (p_tenant_id, p_thread_key, p_assigned_to, p_last_inbound_at, p_threshold_minutes)
    RETURNING id INTO v_new_id;

    RETURN v_new_id;
END;
$$;


--
-- Name: split_recurrence_at(uuid, date); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.split_recurrence_at(p_recurrence_id uuid, p_from_date date) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_old   public.recurrence_rules;
  v_new_id uuid;
BEGIN
  -- busca a regra original
  SELECT * INTO v_old
  FROM public.recurrence_rules
  WHERE id = p_recurrence_id;

  IF NOT FOUND THEN
    RAISE EXCEPTION 'recurrence_rule % n??o encontrada', p_recurrence_id;
  END IF;

  -- encerra a regra antiga na data anterior
  UPDATE public.recurrence_rules
  SET
    end_date   = p_from_date - INTERVAL '1 day',
    open_ended = false,
    updated_at = now()
  WHERE id = p_recurrence_id;

  -- cria nova regra a partir de p_from_date
  INSERT INTO public.recurrence_rules (
    tenant_id, owner_id, therapist_id, patient_id,
    determined_commitment_id, type, interval, weekdays,
    start_time, end_time, timezone, duration_min,
    start_date, end_date, max_occurrences, open_ended,
    modalidade, titulo_custom, observacoes, extra_fields, status
  )
  SELECT
    tenant_id, owner_id, therapist_id, patient_id,
    determined_commitment_id, type, interval, weekdays,
    start_time, end_time, timezone, duration_min,
    p_from_date, v_old.end_date, v_old.max_occurrences, v_old.open_ended,
    modalidade, titulo_custom, observacoes, extra_fields, status
  FROM public.recurrence_rules
  WHERE id = p_recurrence_id
  RETURNING id INTO v_new_id;

  RETURN v_new_id;
END;
$$;


--
-- Name: subscription_intents_view_insert(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.subscription_intents_view_insert() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_target text;
  v_plan_id uuid;
begin
  select p.id, p.target into v_plan_id, v_target
  from public.plans p
  where p.key = new.plan_key;

  if v_plan_id is null then
    raise exception 'Plano inv??lido: plan_key=%', new.plan_key;
  end if;

  if lower(v_target) = 'clinic' then
    if new.tenant_id is null then
      raise exception 'Inten????o clinic exige tenant_id.';
    end if;

    insert into public.subscription_intents_tenant (
      id, tenant_id, created_by_user_id, email,
      plan_id, plan_key, interval, amount_cents, currency,
      status, source, notes, created_at, paid_at
    ) values (
      coalesce(new.id, gen_random_uuid()),
      new.tenant_id, new.created_by_user_id, new.email,
      v_plan_id, new.plan_key, coalesce(new.interval,'month'),
      new.amount_cents, coalesce(new.currency,'BRL'),
      coalesce(new.status,'pending'), coalesce(new.source,'manual'),
      new.notes, coalesce(new.created_at, now()), new.paid_at
    );

    new.plan_target := 'clinic';
    return new;
  end if;

  -- therapist ou supervisor ??? tabela personal
  if lower(v_target) in ('therapist', 'supervisor') then
    insert into public.subscription_intents_personal (
      id, user_id, created_by_user_id, email,
      plan_id, plan_key, interval, amount_cents, currency,
      status, source, notes, created_at, paid_at
    ) values (
      coalesce(new.id, gen_random_uuid()),
      new.user_id, new.created_by_user_id, new.email,
      v_plan_id, new.plan_key, coalesce(new.interval,'month'),
      new.amount_cents, coalesce(new.currency,'BRL'),
      coalesce(new.status,'pending'), coalesce(new.source,'manual'),
      new.notes, coalesce(new.created_at, now()), new.paid_at
    );

    new.plan_target := lower(v_target); -- 'therapist' ou 'supervisor'
    return new;
  end if;

  raise exception 'Target de plano n??o suportado: %', v_target;
end;
$$;


--
-- Name: subscriptions_validate_scope(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.subscriptions_validate_scope() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
  v_target text;
BEGIN
  SELECT lower(p.target) INTO v_target
  FROM public.plans p
  WHERE p.id = NEW.plan_id;

  IF v_target IS NULL THEN
    RAISE EXCEPTION 'Plano inv??lido (target nulo).';
  END IF;

  IF v_target = 'clinic' THEN
    IF NEW.tenant_id IS NULL THEN
      RAISE EXCEPTION 'Assinatura clinic exige tenant_id.';
    END IF;
    IF NEW.user_id IS NOT NULL THEN
      RAISE EXCEPTION 'Assinatura clinic n??o pode ter user_id (XOR).';
    END IF;

  ELSIF v_target IN ('therapist', 'supervisor') THEN
    -- supervisor ?? pessoal como therapist
    IF NEW.tenant_id IS NOT NULL THEN
      RAISE EXCEPTION 'Assinatura % n??o deve ter tenant_id.', v_target;
    END IF;
    IF NEW.user_id IS NULL THEN
      RAISE EXCEPTION 'Assinatura % exige user_id.', v_target;
    END IF;

  ELSIF v_target = 'patient' THEN
    IF NEW.tenant_id IS NOT NULL THEN
      RAISE EXCEPTION 'Assinatura patient n??o deve ter tenant_id.';
    END IF;
    IF NEW.user_id IS NULL THEN
      RAISE EXCEPTION 'Assinatura patient exige user_id.';
    END IF;

  ELSE
    RAISE EXCEPTION 'Target de plano inv??lido: %', v_target;
  END IF;

  RETURN NEW;
END;
$$;


--
-- Name: sync_busy_mirror_agenda_eventos(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.sync_busy_mirror_agenda_eventos() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
declare
  clinic_tenant uuid;
  is_personal boolean;
  should_mirror boolean;
begin
  -- Anti-recurs??o: espelho n??o espelha
  if (tg_op <> 'DELETE') then
    if new.mirror_of_event_id is not null then
      return new;
    end if;
  else
    if old.mirror_of_event_id is not null then
      return old;
    end if;
  end if;

  -- Define se ?? pessoal e se deve espelhar
  if (tg_op = 'DELETE') then
    is_personal := (old.tenant_id = old.owner_id);
    should_mirror := (old.visibility_scope in ('busy_only','private'));
  else
    is_personal := (new.tenant_id = new.owner_id);
    should_mirror := (new.visibility_scope in ('busy_only','private'));
  end if;

  -- Se n??o ?? pessoal, n??o faz nada
  if not is_personal then
    if (tg_op = 'DELETE') then
      return old;
    end if;
    return new;
  end if;

  -- DELETE: remove espelhos existentes
  if (tg_op = 'DELETE') then
    delete from public.agenda_eventos e
    where e.mirror_of_event_id = old.id
      and e.mirror_source = 'personal_busy_mirror';

    return old;
  end if;

  -- INSERT/UPDATE:
  -- Se n??o deve espelhar, remove espelhos e sai
  if not should_mirror then
    delete from public.agenda_eventos e
    where e.mirror_of_event_id = new.id
      and e.mirror_source = 'personal_busy_mirror';

    return new;
  end if;

  -- Para cada cl??nica onde o usu??rio ?? therapist active, cria/atualiza o "Ocupado"
  for clinic_tenant in
    select tm.tenant_id
    from public.tenant_members tm
    where tm.user_id = new.owner_id
      and tm.role = 'therapist'
      and tm.status = 'active'
      and tm.tenant_id <> new.owner_id
  loop
    insert into public.agenda_eventos (
      tenant_id,
      owner_id,
      terapeuta_id,
      paciente_id,
      tipo,
      status,
      titulo,
      observacoes,
      inicio_em,
      fim_em,
      mirror_of_event_id,
      mirror_source,
      visibility_scope,
      created_at,
      updated_at
    ) values (
      clinic_tenant,
      new.owner_id,
      new.owner_id,
      null,
      'bloqueio'::public.tipo_evento_agenda,
      'agendado'::public.status_evento_agenda,
      'Ocupado',
      null,
      new.inicio_em,
      new.fim_em,
      new.id,
      'personal_busy_mirror',
      'public',
      now(),
      now()
    )
    on conflict (tenant_id, mirror_of_event_id) where mirror_of_event_id is not null
    do update set
      owner_id = excluded.owner_id,
      terapeuta_id = excluded.terapeuta_id,
      tipo = excluded.tipo,
      status = excluded.status,
      titulo = excluded.titulo,
      observacoes = excluded.observacoes,
      inicio_em = excluded.inicio_em,
      fim_em = excluded.fim_em,
      updated_at = now();
  end loop;

  -- Limpa espelhos de cl??nicas onde o v??nculo therapist active n??o existe mais
  delete from public.agenda_eventos e
  where e.mirror_of_event_id = new.id
    and e.mirror_source = 'personal_busy_mirror'
    and not exists (
      select 1
      from public.tenant_members tm
      where tm.user_id = new.owner_id
        and tm.role = 'therapist'
        and tm.status = 'active'
        and tm.tenant_id = e.tenant_id
    );

  return new;
end;
$$;


--
-- Name: sync_legacy_email_fields(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.sync_legacy_email_fields() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_entity_type TEXT;
    v_entity_id UUID;
    v_primary TEXT;
    v_secondary TEXT;
BEGIN
    IF TG_OP = 'DELETE' THEN
        v_entity_type := OLD.entity_type;
        v_entity_id := OLD.entity_id;
    ELSE
        v_entity_type := NEW.entity_type;
        v_entity_id := NEW.entity_id;
    END IF;

    SELECT email INTO v_primary
        FROM public.contact_emails
        WHERE entity_type = v_entity_type AND entity_id = v_entity_id
        ORDER BY is_primary DESC, position ASC, created_at ASC
        LIMIT 1;

    SELECT email INTO v_secondary
        FROM public.contact_emails
        WHERE entity_type = v_entity_type AND entity_id = v_entity_id
          AND is_primary = false
        ORDER BY position ASC, created_at ASC
        OFFSET 0
        LIMIT 1;

    IF v_entity_type = 'patient' THEN
        UPDATE public.patients
            SET email_principal = v_primary,
                email_alternativo = v_secondary
            WHERE id = v_entity_id;
    ELSIF v_entity_type = 'medico' THEN
        UPDATE public.medicos
            SET email = v_primary
            WHERE id = v_entity_id;
    END IF;

    IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
END;
$$;


--
-- Name: sync_legacy_phone_fields(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.sync_legacy_phone_fields() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_entity_type TEXT;
    v_entity_id UUID;
    v_primary TEXT;
    v_secondary TEXT;
    v_whatsapp_slug TEXT;
    v_whatsapp TEXT;
BEGIN
    -- Identifica entidade afetada (pode ser OLD em delete)
    IF TG_OP = 'DELETE' THEN
        v_entity_type := OLD.entity_type;
        v_entity_id := OLD.entity_id;
    ELSE
        v_entity_type := NEW.entity_type;
        v_entity_id := NEW.entity_id;
    END IF;

    -- Pega primary (ou primeiro)
    SELECT number INTO v_primary
        FROM public.contact_phones
        WHERE entity_type = v_entity_type AND entity_id = v_entity_id
        ORDER BY is_primary DESC, position ASC, created_at ASC
        LIMIT 1;

    -- Pega segundo (depois do primary)
    SELECT number INTO v_secondary
        FROM public.contact_phones
        WHERE entity_type = v_entity_type AND entity_id = v_entity_id
          AND is_primary = false
        ORDER BY position ASC, created_at ASC
        OFFSET 0
        LIMIT 1;

    -- Sincroniza campos legados
    IF v_entity_type = 'patient' THEN
        UPDATE public.patients
            SET telefone = v_primary,
                telefone_alternativo = v_secondary
            WHERE id = v_entity_id;
    ELSIF v_entity_type = 'medico' THEN
        -- Medicos: telefone_profissional = primary; telefone_pessoal = secundario
        UPDATE public.medicos
            SET telefone_profissional = v_primary,
                telefone_pessoal = v_secondary
            WHERE id = v_entity_id;
    END IF;

    IF TG_OP = 'DELETE' THEN
        RETURN OLD;
    ELSE
        RETURN NEW;
    END IF;
END;
$$;


--
-- Name: sync_overdue_financial_records(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.sync_overdue_financial_records() RETURNS integer
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_count integer;
BEGIN
  UPDATE public.financial_records
  SET
    status     = 'overdue',
    updated_at = NOW()
  WHERE status     = 'pending'
    AND due_date   IS NOT NULL
    AND due_date   < CURRENT_DATE
    AND deleted_at IS NULL;

  GET DIAGNOSTICS v_count = ROW_COUNT;
  RETURN v_count;
END;
$$;


--
-- Name: FUNCTION sync_overdue_financial_records(); Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON FUNCTION public.sync_overdue_financial_records() IS 'Marca como overdue todos os financial_records pendentes com due_date vencido. Pode ser chamada manualmente, via pg_cron ou via Supabase Edge Function agendada.';


--
-- Name: tenant_accept_invite(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_accept_invite(p_token uuid) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
declare
  v_uid uuid;
  v_email text;
  v_invite public.tenant_invites%rowtype;
begin
  -- 1) precisa estar autenticado
  v_uid := auth.uid();
  if v_uid is null then
    raise exception 'not_authenticated' using errcode = 'P0001';
  end if;

  -- 2) pega email real do usu??rio logado sem depender do JWT claim
  select u.email
    into v_email
  from auth.users u
  where u.id = v_uid;

  if v_email is null or length(trim(v_email)) = 0 then
    raise exception 'missing_user_email' using errcode = 'P0001';
  end if;

  -- 3) carrega o invite e trava linha (evita 2 aceites concorrentes)
  select *
    into v_invite
  from public.tenant_invites i
  where i.token = p_token
  for update;

  if not found then
    raise exception 'invite_not_found' using errcode = 'P0001';
  end if;

  -- 4) valida????es de estado
  if v_invite.revoked_at is not null then
    raise exception 'invite_revoked' using errcode = 'P0001';
  end if;

  if v_invite.accepted_at is not null then
    raise exception 'invite_already_accepted' using errcode = 'P0001';
  end if;

  if v_invite.expires_at is not null and v_invite.expires_at <= now() then
    raise exception 'invite_expired' using errcode = 'P0001';
  end if;

  -- 5) valida email (case-insensitive)
  if lower(trim(v_invite.email)) <> lower(trim(v_email)) then
    raise exception 'email_mismatch' using errcode = 'P0001';
  end if;

  -- 6) consome o invite
  update public.tenant_invites
  set accepted_at = now(),
      accepted_by = v_uid
  where id = v_invite.id;

  -- 7) cria ou reativa o membership
  insert into public.tenant_members (tenant_id, user_id, role, status, created_at)
  values (v_invite.tenant_id, v_uid, v_invite.role, 'active', now())
  on conflict (tenant_id, user_id)
  do update set
    role = excluded.role,
    status = 'active';

  -- 8) retorno ??til pro front (voc?? j?? tenta ler tenant_id no AcceptInvitePage)
  return jsonb_build_object(
    'ok', true,
    'tenant_id', v_invite.tenant_id,
    'role', v_invite.role
  );
end;
$$;


--
-- Name: tenant_members; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.tenant_members (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    user_id uuid NOT NULL,
    role text NOT NULL,
    status text DEFAULT 'active'::text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: tenant_add_member_by_email(uuid, text, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_add_member_by_email(p_tenant_id uuid, p_email text, p_role text DEFAULT 'therapist'::text) RETURNS public.tenant_members
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
declare
  v_target_uid uuid;
  v_member public.tenant_members%rowtype;
  v_is_admin boolean;
  v_email text;
begin
  if p_tenant_id is null then
    raise exception 'tenant_id ?? obrigat??rio';
  end if;

  v_email := lower(trim(coalesce(p_email, '')));
  if v_email = '' then
    raise exception 'email ?? obrigat??rio';
  end if;

  -- valida role permitida
  if p_role not in ('tenant_admin','therapist','secretary','patient') then
    raise exception 'role inv??lida: %', p_role;
  end if;

  -- apenas admin do tenant (role real no banco)
  select exists (
    select 1
    from public.tenant_members tm
    where tm.tenant_id = p_tenant_id
      and tm.user_id = auth.uid()
      and tm.role = 'tenant_admin'
      and coalesce(tm.status,'active') = 'active'
  ) into v_is_admin;

  if not v_is_admin then
    raise exception 'sem permiss??o: apenas admin da cl??nica pode adicionar membros';
  end if;

  -- acha usu??rio pelo e-mail no Supabase Auth
  select u.id
  into v_target_uid
  from auth.users u
  where lower(u.email) = v_email
  limit 1;

  if v_target_uid is null then
    raise exception 'nenhum usu??rio encontrado com este e-mail';
  end if;

  -- cria ou reativa membro
  insert into public.tenant_members (tenant_id, user_id, role, status)
  values (p_tenant_id, v_target_uid, p_role, 'active')
  on conflict (tenant_id, user_id)
  do update set
    role = excluded.role,
    status = 'active'
  returning * into v_member;

  return v_member;
end;
$$;


--
-- Name: tenant_feature_allowed(uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_feature_allowed(p_tenant_id uuid, p_feature_key text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1
    from public.v_tenant_entitlements v
    where v.tenant_id = p_tenant_id
      and v.feature_key = p_feature_key
      and coalesce(v.allowed, false) = true
  );
$$;


--
-- Name: tenant_feature_enabled(uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_feature_enabled(p_tenant_id uuid, p_feature_key text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select coalesce(
    (select tf.enabled
     from public.tenant_features tf
     where tf.tenant_id = p_tenant_id and tf.feature_key = p_feature_key),
    false
  );
$$;


--
-- Name: tenant_features_guard_with_plan(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_features_guard_with_plan() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
  v_allowed   boolean;
  v_bypass    text;
BEGIN
  -- Só valida quando está habilitando
  IF new.enabled IS DISTINCT FROM true THEN
    RETURN new;
  END IF;

  -- Bypass autorizado: setado pela RPC set_tenant_feature_exception
  -- após validar que o caller é saas_admin com reason.
  v_bypass := current_setting('app.allow_feature_exception', true);
  IF v_bypass = 'true' THEN
    RETURN new;
  END IF;

  -- Permitido pelo plano do tenant?
  SELECT EXISTS (
    SELECT 1
    FROM public.v_tenant_entitlements_full v
    WHERE v.tenant_id = new.tenant_id
      AND v.feature_key = new.feature_key
      AND v.allowed = true
  ) INTO v_allowed;

  IF NOT v_allowed THEN
    RAISE EXCEPTION 'Feature % não permitida pelo plano atual do tenant %.',
      new.feature_key, new.tenant_id
      USING ERRCODE = 'P0001';
  END IF;

  RETURN new;
END;
$$;


--
-- Name: tenant_has_feature(uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_has_feature(_tenant_id uuid, _feature text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select
    exists (
      select 1
      from public.v_tenant_entitlements e
      where e.tenant_id = _tenant_id
        and e.feature_key = _feature
        and e.allowed = true
    )
    or exists (
      select 1
      from public.tenant_features tf
      where tf.tenant_id = _tenant_id
        and tf.feature_key = _feature
        and tf.enabled = true
    );
$$;


--
-- Name: tenant_invite_member_by_email(uuid, text, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_invite_member_by_email(p_tenant_id uuid, p_email text, p_role text) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
declare
  v_email text;
  v_my_email text;
  v_token uuid;
  v_updated int;
begin
  -- valida????es b??sicas
  if p_tenant_id is null then
    raise exception 'tenant_id inv??lido' using errcode = 'P0001';
  end if;

  v_email := lower(trim(coalesce(p_email, '')));
  if v_email = '' then
    raise exception 'Informe um email' using errcode = 'P0001';
  end if;

  -- role permitido (ajuste se quiser)
  if p_role is null or p_role not in ('therapist', 'secretary') then
    raise exception 'Role inv??lido (use therapist/secretary)' using errcode = 'P0001';
  end if;

  -- ??? bloqueio: auto-convite
  v_my_email := public.get_my_email();
  if v_my_email is not null and v_email = v_my_email then
    raise exception 'Voc?? n??o pode convidar o seu pr??prio email.' using errcode = 'P0001';
  end if;

  -- ??? bloqueio: j?? ?? membro ativo do tenant
  if exists (
    select 1
    from tenant_members tm
    join auth.users au on au.id = tm.user_id
    where tm.tenant_id = p_tenant_id
      and tm.status = 'active'
      and lower(au.email) = v_email
  ) then
    raise exception 'Este email j?? est?? vinculado a esta cl??nica.' using errcode = 'P0001';
  end if;

  -- ??? permiss??o: s?? admin do tenant pode convidar
  if not exists (
    select 1
    from tenant_members me
    where me.tenant_id = p_tenant_id
      and me.user_id = auth.uid()
      and me.status = 'active'
      and me.role in ('tenant_admin','clinic_admin')
  ) then
    raise exception 'Sem permiss??o para convidar membros.' using errcode = 'P0001';
  end if;

  -- Gera token (reenvio simples / regenera????o)
  v_token := gen_random_uuid();

  -- 1) tenta "regerar" um convite pendente existente (mesmo email)
  update tenant_invites
     set token = v_token,
         role = p_role,
         created_at = now(),
         expires_at = now() + interval '7 days',
         accepted_at = null,
         revoked_at = null
   where tenant_id = p_tenant_id
     and lower(email) = v_email
     and accepted_at is null
     and revoked_at is null;

  get diagnostics v_updated = row_count;

  -- 2) se n??o atualizou nada, cria convite novo
  if v_updated = 0 then
    insert into tenant_invites (tenant_id, email, role, token, created_at, expires_at)
    values (p_tenant_id, v_email, p_role, v_token, now(), now() + interval '7 days');
  end if;

  return v_token;
end;
$$;


--
-- Name: tenant_reactivate_member(uuid, uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_reactivate_member(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  update public.tenant_members
     set status = 'active'
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


--
-- Name: tenant_remove_member(uuid, uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_remove_member(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
declare
  v_role text;
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  if p_member_user_id = auth.uid() then
    raise exception 'cannot_remove_self';
  end if;

  -- pega role atual do membro (se n??o existir, erro)
  select role into v_role
  from public.tenant_members
  where tenant_id = p_tenant_id
    and user_id = p_member_user_id;

  if v_role is null then
    raise exception 'membership_not_found';
  end if;

  -- trava: se for therapist, n??o pode remover com eventos futuros
  if v_role = 'therapist' then
    if exists (
      select 1
      from public.agenda_eventos e
      where e.owner_id = p_tenant_id
        and e.terapeuta_id = p_member_user_id
        and e.inicio_em >= now()
        and e.status::text not in ('cancelado','cancelled','canceled')
      limit 1
    ) then
      raise exception 'cannot_remove_therapist_with_future_events';
    end if;
  end if;

  update public.tenant_members
     set status = 'inactive'
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


--
-- Name: tenant_remove_member_soft(uuid, uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_remove_member_soft(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  if p_member_user_id = auth.uid() then
    raise exception 'cannot_remove_self';
  end if;

  update public.tenant_members
     set status = 'inactive'
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


--
-- Name: tenant_revoke_invite(uuid, text, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_revoke_invite(p_tenant_id uuid, p_email text, p_role text) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
declare
  v_email text;
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  v_email := lower(trim(p_email));

  update public.tenant_invites
     set revoked_at = now(),
         revoked_by = auth.uid()
   where tenant_id = p_tenant_id
     and lower(email) = v_email
     and role = p_role
     and accepted_at is null
     and revoked_at is null;

  if not found then
    raise exception 'invite_not_found';
  end if;
end;
$$;


--
-- Name: tenant_set_member_status(uuid, uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_set_member_status(p_tenant_id uuid, p_member_user_id uuid, p_new_status text) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  -- valida status (adapte aos seus valores reais)
  if p_new_status not in ('active','inactive','suspended','invited') then
    raise exception 'invalid_status: %', p_new_status;
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  -- evita desativar a si mesmo (opcional)
  if p_member_user_id = auth.uid() and p_new_status <> 'active' then
    raise exception 'cannot_disable_self';
  end if;

  update public.tenant_members
     set status = p_new_status
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


--
-- Name: tenant_update_member_role(uuid, uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.tenant_update_member_role(p_tenant_id uuid, p_member_user_id uuid, p_new_role text) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  -- exige auth
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  -- valida role
  if p_new_role not in ('tenant_admin','therapist','secretary','patient') then
    raise exception 'invalid_role: %', p_new_role;
  end if;

  -- somente tenant_admin ativo pode alterar role
  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  -- evita o admin remover o pr??prio admin sem querer (opcional mas recomendado)
  if p_member_user_id = auth.uid() and p_new_role <> 'tenant_admin' then
    raise exception 'cannot_demote_self';
  end if;

  update public.tenant_members
     set role = p_new_role
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


--
-- Name: toggle_plan(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.toggle_plan(owner uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  current_key text;
  new_key text;
begin
  select p.key into current_key
  from subscriptions s
  join plans p on p.id = s.plan_id
  where s.owner_id = owner
    and s.status = 'active';

  new_key := case
    when current_key = 'pro' then 'free'
    else 'pro'
  end;

  update subscriptions s
  set plan_id = p.id
  from plans p
  where p.key = new_key
    and s.owner_id = owner
    and s.status = 'active';
end;
$$;


--
-- Name: transition_subscription(uuid, text, text, jsonb); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.transition_subscription(p_subscription_id uuid, p_to_status text, p_reason text DEFAULT NULL::text, p_metadata jsonb DEFAULT NULL::jsonb) RETURNS public.subscriptions
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_sub public.subscriptions;
  v_uid uuid;
  v_is_allowed boolean := false;
begin
  v_uid := auth.uid();

  select *
    into v_sub
  from public.subscriptions
  where id = p_subscription_id;

  if not found then
    raise exception 'Assinatura n??o encontrada';
  end if;

  -- =====================================================
  -- ???? BLOCO DE AUTORIZA????O
  -- =====================================================

  -- 1) SaaS admin pode tudo
  if is_saas_admin() then
    v_is_allowed := true;
  end if;

  -- 2) Assinatura pessoal (therapist)
  if not v_is_allowed
     and v_sub.tenant_id is null
     and v_sub.user_id = v_uid then
    v_is_allowed := true;
  end if;

  -- 3) Assinatura de clinic (tenant)
  if not v_is_allowed
     and v_sub.tenant_id is not null then

    if exists (
      select 1
      from public.tenant_members tm
      where tm.tenant_id = v_sub.tenant_id
        and tm.user_id = v_uid
        and tm.status = 'active'
        and tm.role = 'tenant_admin'
    ) then
      v_is_allowed := true;
    end if;

  end if;

  if not v_is_allowed then
    raise exception 'Sem permiss??o para transicionar esta assinatura';
  end if;

  -- =====================================================
  -- ???? TRANSI????O
  -- =====================================================

  update public.subscriptions
  set status = p_to_status,
      updated_at = now(),
      cancelled_at = case when p_to_status = 'cancelled' then now() else cancelled_at end,
      suspended_at = case when p_to_status = 'suspended' then now() else suspended_at end,
      past_due_since = case when p_to_status = 'past_due' then now() else past_due_since end,
      expired_at = case when p_to_status = 'expired' then now() else expired_at end,
      activated_at = case when p_to_status = 'active' then now() else activated_at end
  where id = p_subscription_id
  returning * into v_sub;

  -- =====================================================
  -- ???? EVENT LOG
  -- =====================================================

  insert into public.subscription_events (
    subscription_id,
    owner_id,
    event_type,
    created_at,
    created_by,
    source,
    reason,
    metadata,
    owner_type,
    owner_ref
  )
  values (
    v_sub.id,
    coalesce(v_sub.tenant_id, v_sub.user_id),
    'status_changed',
    now(),
    v_uid,
    'manual_transition',
    p_reason,
    p_metadata,
    case when v_sub.tenant_id is not null then 'tenant' else 'personal' end,
    coalesce(v_sub.tenant_id, v_sub.user_id)
  );

  return v_sub;
end;
$$;


--
-- Name: trg_fn_financial_records_auto_overdue(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.trg_fn_financial_records_auto_overdue() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
  IF NEW.status = 'pending'
    AND NEW.due_date IS NOT NULL
    AND NEW.due_date < CURRENT_DATE
  THEN
    NEW.status := 'overdue';
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: trg_fn_patient_risco_timeline(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.trg_fn_patient_risco_timeline() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  IF OLD.risco_elevado IS DISTINCT FROM NEW.risco_elevado THEN
    INSERT INTO public.patient_timeline (
      patient_id, tenant_id,
      evento_tipo, titulo, descricao, icone_cor,
      gerado_por, ocorrido_em
    ) VALUES (
      NEW.id, NEW.tenant_id,
      CASE WHEN NEW.risco_elevado THEN 'risco_sinalizado' ELSE 'risco_removido' END,
      CASE WHEN NEW.risco_elevado THEN 'Risco elevado sinalizado' ELSE 'Sinalização de risco removida' END,
      NEW.risco_nota,
      CASE WHEN NEW.risco_elevado THEN 'red' ELSE 'green' END,
      auth.uid(),
      now()
    );
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: trg_fn_patient_status_history(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.trg_fn_patient_status_history() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  IF (TG_OP = 'INSERT') OR (OLD.status IS DISTINCT FROM NEW.status) THEN
    INSERT INTO public.patient_status_history (
      patient_id, tenant_id,
      status_anterior, status_novo,
      motivo, encaminhado_para, data_saida,
      alterado_por, alterado_em
    ) VALUES (
      NEW.id, NEW.tenant_id,
      CASE WHEN TG_OP = 'INSERT' THEN NULL ELSE OLD.status END,
      NEW.status,
      NEW.motivo_saida,
      NEW.encaminhado_para,
      NEW.data_saida,
      auth.uid(),
      now()
    );
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: trg_fn_patient_status_timeline(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.trg_fn_patient_status_timeline() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  IF (TG_OP = 'INSERT') OR (OLD.status IS DISTINCT FROM NEW.status) THEN
    INSERT INTO public.patient_timeline (
      patient_id, tenant_id,
      evento_tipo, titulo, descricao, icone_cor,
      gerado_por, ocorrido_em
    ) VALUES (
      NEW.id, NEW.tenant_id,
      'status_alterado',
      'Status alterado para ' || NEW.status,
      CASE
        WHEN TG_OP = 'INSERT' THEN 'Paciente cadastrado'
        ELSE 'De ' || OLD.status || ' → ' || NEW.status ||
             CASE WHEN NEW.motivo_saida IS NOT NULL THEN ' · ' || NEW.motivo_saida ELSE '' END
      END,
      CASE NEW.status
        WHEN 'Ativo'        THEN 'green'
        WHEN 'Alta'         THEN 'blue'
        WHEN 'Inativo'      THEN 'gray'
        WHEN 'Encaminhado'  THEN 'amber'
        WHEN 'Arquivado'    THEN 'gray'
        ELSE 'gray'
      END,
      auth.uid(),
      now()
    );
  END IF;
  RETURN NEW;
END;
$$;


--
-- Name: trg_set_updated_at(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.trg_set_updated_at() RETURNS trigger
    LANGUAGE plpgsql
    AS $$                                                                                                   BEGIN
    NEW.updated_at = now();
    RETURN NEW;
  END;
  $$;


--
-- Name: unstick_notification_queue(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.unstick_notification_queue() RETURNS integer
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
DECLARE
  v_unstuck integer;
BEGIN
  UPDATE public.notification_queue
  SET status = 'pendente',
      attempts = attempts + 1,
      last_error = 'Timeout: preso em processando por >10min',
      next_retry_at = now() + interval '2 minutes'
  WHERE status = 'processando'
    AND updated_at < now() - interval '10 minutes';

  GET DIAGNOSTICS v_unstuck = ROW_COUNT;
  RETURN v_unstuck;
END;
$$;


--
-- Name: update_payment_settings_updated_at(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.update_payment_settings_updated_at() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$;


--
-- Name: update_professional_pricing_updated_at(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.update_professional_pricing_updated_at() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$;


--
-- Name: update_twilio_config(text, text, numeric, numeric, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.update_twilio_config(p_account_sid text DEFAULT NULL::text, p_whatsapp_webhook_url text DEFAULT NULL::text, p_usd_brl_rate numeric DEFAULT NULL::numeric, p_margin_multiplier numeric DEFAULT NULL::numeric, p_notes text DEFAULT NULL::text) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $_$
DECLARE
    v_caller         uuid := auth.uid();
    v_account_sid    text;
    v_webhook_url    text;
    v_notes          text;
BEGIN
    IF v_caller IS NULL THEN
        RAISE EXCEPTION 'Não autenticado' USING ERRCODE = '28000';
    END IF;
    IF NOT public.is_saas_admin() THEN
        RAISE EXCEPTION 'Apenas saas_admin pode atualizar config Twilio' USING ERRCODE = '42501';
    END IF;

    -- Sanitização
    v_account_sid := nullif(btrim(coalesce(p_account_sid, '')), '');
    v_webhook_url := nullif(btrim(coalesce(p_whatsapp_webhook_url, '')), '');
    v_notes       := nullif(btrim(coalesce(p_notes, '')), '');

    IF v_account_sid IS NOT NULL AND v_account_sid !~ '^AC[a-zA-Z0-9]{32}$' THEN
        RAISE EXCEPTION 'account_sid inválido (esperado AC + 32 chars)' USING ERRCODE = '22023';
    END IF;
    IF v_webhook_url IS NOT NULL AND v_webhook_url !~ '^https?://' THEN
        RAISE EXCEPTION 'webhook_url deve começar com http(s)://' USING ERRCODE = '22023';
    END IF;
    IF p_usd_brl_rate IS NOT NULL AND (p_usd_brl_rate <= 0 OR p_usd_brl_rate >= 100) THEN
        RAISE EXCEPTION 'usd_brl_rate fora da faixa (0..100)' USING ERRCODE = '22023';
    END IF;
    IF p_margin_multiplier IS NOT NULL AND (p_margin_multiplier < 1 OR p_margin_multiplier > 10) THEN
        RAISE EXCEPTION 'margin_multiplier fora da faixa (1..10)' USING ERRCODE = '22023';
    END IF;
    IF v_notes IS NOT NULL AND length(v_notes) > 1000 THEN
        v_notes := substring(v_notes FROM 1 FOR 1000);
    END IF;

    UPDATE saas_twilio_config
       SET account_sid          = COALESCE(v_account_sid, account_sid),
           whatsapp_webhook_url = COALESCE(v_webhook_url, whatsapp_webhook_url),
           usd_brl_rate         = COALESCE(p_usd_brl_rate, usd_brl_rate),
           margin_multiplier    = COALESCE(p_margin_multiplier, margin_multiplier),
           notes                = COALESCE(v_notes, notes),
           updated_at           = now(),
           updated_by           = v_caller
     WHERE id = true;

    RETURN public.get_twilio_config();
END;
$_$;


--
-- Name: user_has_feature(uuid, text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.user_has_feature(_user_id uuid, _feature text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1
    from public.v_user_entitlements e
    where e.user_id = _user_id
      and e.feature_key = _feature
      and e.allowed = true
  );
$$;


--
-- Name: validate_share_token(text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.validate_share_token(p_token text) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    sl document_share_links%ROWTYPE;
    v_doc documents%ROWTYPE;
    v_token text;
BEGIN
    v_token := nullif(btrim(coalesce(p_token, '')), '');
    IF v_token IS NULL THEN
        RAISE EXCEPTION 'token obrigatório' USING ERRCODE = '22023';
    END IF;

    SELECT * INTO sl FROM document_share_links WHERE token = v_token LIMIT 1;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Token inválido' USING ERRCODE = '28000';
    END IF;
    IF sl.ativo IS NOT TRUE THEN
        RAISE EXCEPTION 'Link desativado' USING ERRCODE = '28000';
    END IF;
    IF sl.expira_em IS NOT NULL AND sl.expira_em < now() THEN
        RAISE EXCEPTION 'Link expirado' USING ERRCODE = '28000';
    END IF;
    IF sl.usos_max IS NOT NULL AND sl.usos >= sl.usos_max THEN
        RAISE EXCEPTION 'Limite de uso atingido' USING ERRCODE = '28000';
    END IF;

    UPDATE document_share_links SET usos = usos + 1 WHERE id = sl.id;

    BEGIN
        INSERT INTO document_access_logs (document_id, tenant_id, action, share_link_id)
        SELECT sl.document_id, d.tenant_id, 'shared_link_access', sl.id
        FROM documents d WHERE d.id = sl.document_id;
    EXCEPTION WHEN OTHERS THEN
        NULL;
    END;

    SELECT * INTO v_doc FROM documents WHERE id = sl.document_id;

    RETURN jsonb_build_object(
        'document_id',    sl.document_id,
        'bucket',         v_doc.storage_bucket,
        'bucket_path',    v_doc.bucket_path,
        'nome_original',  v_doc.nome_original,
        'mime_type',      v_doc.mime_type,
        'tamanho_bytes',  v_doc.tamanho_bytes
    );
END;
$$;


--
-- Name: validate_support_session(text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.validate_support_session(p_token text) RETURNS json
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_session support_sessions;
BEGIN
  IF p_token IS NULL OR length(trim(p_token)) < 32 THEN
    RETURN json_build_object('valid', false, 'tenant_id', null);
  END IF;

  SELECT * INTO v_session
    FROM public.support_sessions
   WHERE token = p_token
     AND expires_at > now()
   LIMIT 1;

  IF NOT FOUND THEN
    RETURN json_build_object('valid', false, 'tenant_id', null);
  END IF;

  RETURN json_build_object(
    'valid',     true,
    'tenant_id', v_session.tenant_id
  );
END;
$$;


--
-- Name: verify_math_challenge(uuid, integer); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.verify_math_challenge(p_id uuid, p_answer integer) RETURNS boolean
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    mc math_challenges%ROWTYPE;
BEGIN
    IF p_id IS NULL OR p_answer IS NULL THEN RETURN false; END IF;

    SELECT * INTO mc FROM math_challenges WHERE id = p_id;
    IF NOT FOUND OR mc.used OR mc.expires_at < now() THEN
        RETURN false;
    END IF;

    UPDATE math_challenges SET used = true WHERE id = p_id;

    RETURN mc.answer = p_answer;
END;
$$;


--
-- Name: whatsapp_heartbeat_mark_notified(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.whatsapp_heartbeat_mark_notified(p_incident_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
    UPDATE public.whatsapp_connection_incidents
        SET notified_at = now(),
            notification_count = notification_count + 1
        WHERE id = p_incident_id;
END;
$$;


--
-- Name: whatsapp_heartbeat_open_incident(uuid, text, text, jsonb); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.whatsapp_heartbeat_open_incident(p_channel_id uuid, p_kind text, p_last_state text DEFAULT NULL::text, p_details jsonb DEFAULT NULL::jsonb) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_tenant_id UUID;
    v_provider TEXT;
    v_existing_id UUID;
    v_new_id UUID;
BEGIN
    -- Busca tenant/provider do channel
    SELECT tenant_id, provider INTO v_tenant_id, v_provider
        FROM public.notification_channels
        WHERE id = p_channel_id
          AND deleted_at IS NULL;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'channel_not_found';
    END IF;

    IF p_kind NOT IN ('disconnected', 'error', 'qr_pending', 'connecting', 'unknown') THEN
        RAISE EXCEPTION 'invalid_kind: %', p_kind;
    END IF;

    -- Ja tem aberto? Retorna o mesmo id (idempotente)
    SELECT id INTO v_existing_id
        FROM public.whatsapp_connection_incidents
        WHERE channel_id = p_channel_id
          AND resolved_at IS NULL;

    IF FOUND THEN
        -- Atualiza o incident existente com detalhes frescos
        UPDATE public.whatsapp_connection_incidents
            SET last_state = COALESCE(p_last_state, last_state),
                details = COALESCE(p_details, details),
                kind = p_kind  -- pode mudar de qr_pending → disconnected, por ex
            WHERE id = v_existing_id;
        RETURN v_existing_id;
    END IF;

    -- Abre novo
    INSERT INTO public.whatsapp_connection_incidents
        (tenant_id, channel_id, provider, kind, last_state, details)
    VALUES
        (v_tenant_id, p_channel_id, v_provider, p_kind, p_last_state, p_details)
    RETURNING id INTO v_new_id;

    RETURN v_new_id;
END;
$$;


--
-- Name: whatsapp_heartbeat_resolve_open_incidents(uuid); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.whatsapp_heartbeat_resolve_open_incidents(p_channel_id uuid) RETURNS integer
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
    v_count INT := 0;
BEGIN
    UPDATE public.whatsapp_connection_incidents
        SET resolved_at = now(),
            duration_seconds = EXTRACT(EPOCH FROM (now() - started_at))::INT
        WHERE channel_id = p_channel_id
          AND resolved_at IS NULL;

    GET DIAGNOSTICS v_count = ROW_COUNT;
    RETURN v_count;
END;
$$;


--
-- Name: whoami(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.whoami() RETURNS TABLE(uid uuid, role text)
    LANGUAGE sql STABLE
    AS $$
  select auth.uid() as uid, auth.role() as role;
$$;


--
-- Name: apply_rls(jsonb, integer); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.apply_rls(wal jsonb, max_record_bytes integer DEFAULT (1024 * 1024)) RETURNS SETOF realtime.wal_rls
    LANGUAGE plpgsql
    AS $$
declare
-- Regclass of the table e.g. public.notes
entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;

-- I, U, D, T: insert, update ...
action realtime.action = (
    case wal ->> 'action'
        when 'I' then 'INSERT'
        when 'U' then 'UPDATE'
        when 'D' then 'DELETE'
        else 'ERROR'
    end
);

-- Is row level security enabled for the table
is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;

subscriptions realtime.subscription[] = array_agg(subs)
    from
        realtime.subscription subs
    where
        subs.entity = entity_;

-- Subscription vars
roles regrole[] = array_agg(distinct us.claims_role::text)
    from
        unnest(subscriptions) us;

working_role regrole;
claimed_role regrole;
claims jsonb;

subscription_id uuid;
subscription_has_access bool;
visible_to_subscription_ids uuid[] = '{}';

-- structured info for wal's columns
columns realtime.wal_column[];
-- previous identity values for update/delete
old_columns realtime.wal_column[];

error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;

-- Primary jsonb output for record
output jsonb;

begin
perform set_config('role', null, true);

columns =
    array_agg(
        (
            x->>'name',
            x->>'type',
            x->>'typeoid',
            realtime.cast(
                (x->'value') #>> '{}',
                coalesce(
                    (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4
                    (x->>'type')::regtype
                )
            ),
            (pks ->> 'name') is not null,
            true
        )::realtime.wal_column
    )
    from
        jsonb_array_elements(wal -> 'columns') x
        left join jsonb_array_elements(wal -> 'pk') pks
            on (x ->> 'name') = (pks ->> 'name');

old_columns =
    array_agg(
        (
            x->>'name',
            x->>'type',
            x->>'typeoid',
            realtime.cast(
                (x->'value') #>> '{}',
                coalesce(
                    (x->>'typeoid')::regtype, -- null when wal2json version <= 2.4
                    (x->>'type')::regtype
                )
            ),
            (pks ->> 'name') is not null,
            true
        )::realtime.wal_column
    )
    from
        jsonb_array_elements(wal -> 'identity') x
        left join jsonb_array_elements(wal -> 'pk') pks
            on (x ->> 'name') = (pks ->> 'name');

for working_role in select * from unnest(roles) loop

    -- Update `is_selectable` for columns and old_columns
    columns =
        array_agg(
            (
                c.name,
                c.type_name,
                c.type_oid,
                c.value,
                c.is_pkey,
                pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')
            )::realtime.wal_column
        )
        from
            unnest(columns) c;

    old_columns =
            array_agg(
                (
                    c.name,
                    c.type_name,
                    c.type_oid,
                    c.value,
                    c.is_pkey,
                    pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')
                )::realtime.wal_column
            )
            from
                unnest(old_columns) c;

    if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then
        return next (
            jsonb_build_object(
                'schema', wal ->> 'schema',
                'table', wal ->> 'table',
                'type', action
            ),
            is_rls_enabled,
            -- subscriptions is already filtered by entity
            (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),
            array['Error 400: Bad Request, no primary key']
        )::realtime.wal_rls;

    -- The claims role does not have SELECT permission to the primary key of entity
    elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then
        return next (
            jsonb_build_object(
                'schema', wal ->> 'schema',
                'table', wal ->> 'table',
                'type', action
            ),
            is_rls_enabled,
            (select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),
            array['Error 401: Unauthorized']
        )::realtime.wal_rls;

    else
        output = jsonb_build_object(
            'schema', wal ->> 'schema',
            'table', wal ->> 'table',
            'type', action,
            'commit_timestamp', to_char(
                ((wal ->> 'timestamp')::timestamptz at time zone 'utc'),
                'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
            ),
            'columns', (
                select
                    jsonb_agg(
                        jsonb_build_object(
                            'name', pa.attname,
                            'type', pt.typname
                        )
                        order by pa.attnum asc
                    )
                from
                    pg_attribute pa
                    join pg_type pt
                        on pa.atttypid = pt.oid
                where
                    attrelid = entity_
                    and attnum > 0
                    and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')
            )
        )
        -- Add "record" key for insert and update
        || case
            when action in ('INSERT', 'UPDATE') then
                jsonb_build_object(
                    'record',
                    (
                        select
                            jsonb_object_agg(
                                -- if unchanged toast, get column name and value from old record
                                coalesce((c).name, (oc).name),
                                case
                                    when (c).name is null then (oc).value
                                    else (c).value
                                end
                            )
                        from
                            unnest(columns) c
                            full outer join unnest(old_columns) oc
                                on (c).name = (oc).name
                        where
                            coalesce((c).is_selectable, (oc).is_selectable)
                            and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))
                    )
                )
            else '{}'::jsonb
        end
        -- Add "old_record" key for update and delete
        || case
            when action = 'UPDATE' then
                jsonb_build_object(
                        'old_record',
                        (
                            select jsonb_object_agg((c).name, (c).value)
                            from unnest(old_columns) c
                            where
                                (c).is_selectable
                                and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))
                        )
                    )
            when action = 'DELETE' then
                jsonb_build_object(
                    'old_record',
                    (
                        select jsonb_object_agg((c).name, (c).value)
                        from unnest(old_columns) c
                        where
                            (c).is_selectable
                            and ( not error_record_exceeds_max_size or (octet_length((c).value::text) <= 64))
                            and ( not is_rls_enabled or (c).is_pkey ) -- if RLS enabled, we can't secure deletes so filter to pkey
                    )
                )
            else '{}'::jsonb
        end;

        -- Create the prepared statement
        if is_rls_enabled and action <> 'DELETE' then
            if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then
                deallocate walrus_rls_stmt;
            end if;
            execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);
        end if;

        visible_to_subscription_ids = '{}';

        for subscription_id, claims in (
                select
                    subs.subscription_id,
                    subs.claims
                from
                    unnest(subscriptions) subs
                where
                    subs.entity = entity_
                    and subs.claims_role = working_role
                    and (
                        realtime.is_visible_through_filters(columns, subs.filters)
                        or (
                          action = 'DELETE'
                          and realtime.is_visible_through_filters(old_columns, subs.filters)
                        )
                    )
        ) loop

            if not is_rls_enabled or action = 'DELETE' then
                visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;
            else
                -- Check if RLS allows the role to see the record
                perform
                    -- Trim leading and trailing quotes from working_role because set_config
                    -- doesn't recognize the role as valid if they are included
                    set_config('role', trim(both '"' from working_role::text), true),
                    set_config('request.jwt.claims', claims::text, true);

                execute 'execute walrus_rls_stmt' into subscription_has_access;

                if subscription_has_access then
                    visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;
                end if;
            end if;
        end loop;

        perform set_config('role', null, true);

        return next (
            output,
            is_rls_enabled,
            visible_to_subscription_ids,
            case
                when error_record_exceeds_max_size then array['Error 413: Payload Too Large']
                else '{}'
            end
        )::realtime.wal_rls;

    end if;
end loop;

perform set_config('role', null, true);
end;
$$;


--
-- Name: broadcast_changes(text, text, text, text, text, record, record, text); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.broadcast_changes(topic_name text, event_name text, operation text, table_name text, table_schema text, new record, old record, level text DEFAULT 'ROW'::text) RETURNS void
    LANGUAGE plpgsql
    AS $$
DECLARE
    -- Declare a variable to hold the JSONB representation of the row
    row_data jsonb := '{}'::jsonb;
BEGIN
    IF level = 'STATEMENT' THEN
        RAISE EXCEPTION 'function can only be triggered for each row, not for each statement';
    END IF;
    -- Check the operation type and handle accordingly
    IF operation = 'INSERT' OR operation = 'UPDATE' OR operation = 'DELETE' THEN
        row_data := jsonb_build_object('old_record', OLD, 'record', NEW, 'operation', operation, 'table', table_name, 'schema', table_schema);
        PERFORM realtime.send (row_data, event_name, topic_name);
    ELSE
        RAISE EXCEPTION 'Unexpected operation type: %', operation;
    END IF;
EXCEPTION
    WHEN OTHERS THEN
        RAISE EXCEPTION 'Failed to process the row: %', SQLERRM;
END;

$$;


--
-- Name: build_prepared_statement_sql(text, regclass, realtime.wal_column[]); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.build_prepared_statement_sql(prepared_statement_name text, entity regclass, columns realtime.wal_column[]) RETURNS text
    LANGUAGE sql
    AS $$
      /*
      Builds a sql string that, if executed, creates a prepared statement to
      tests retrive a row from *entity* by its primary key columns.
      Example
          select realtime.build_prepared_statement_sql('public.notes', '{"id"}'::text[], '{"bigint"}'::text[])
      */
          select
      'prepare ' || prepared_statement_name || ' as
          select
              exists(
                  select
                      1
                  from
                      ' || entity || '
                  where
                      ' || string_agg(quote_ident(pkc.name) || '=' || quote_nullable(pkc.value #>> '{}') , ' and ') || '
              )'
          from
              unnest(columns) pkc
          where
              pkc.is_pkey
          group by
              entity
      $$;


--
-- Name: cast(text, regtype); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime."cast"(val text, type_ regtype) RETURNS jsonb
    LANGUAGE plpgsql IMMUTABLE
    AS $$
    declare
      res jsonb;
    begin
      execute format('select to_jsonb(%L::'|| type_::text || ')', val)  into res;
      return res;
    end
    $$;


--
-- Name: check_equality_op(realtime.equality_op, regtype, text, text); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.check_equality_op(op realtime.equality_op, type_ regtype, val_1 text, val_2 text) RETURNS boolean
    LANGUAGE plpgsql IMMUTABLE
    AS $$
      /*
      Casts *val_1* and *val_2* as type *type_* and check the *op* condition for truthiness
      */
      declare
          op_symbol text = (
              case
                  when op = 'eq' then '='
                  when op = 'neq' then '!='
                  when op = 'lt' then '<'
                  when op = 'lte' then '<='
                  when op = 'gt' then '>'
                  when op = 'gte' then '>='
                  when op = 'in' then '= any'
                  else 'UNKNOWN OP'
              end
          );
          res boolean;
      begin
          execute format(
              'select %L::'|| type_::text || ' ' || op_symbol
              || ' ( %L::'
              || (
                  case
                      when op = 'in' then type_::text || '[]'
                      else type_::text end
              )
              || ')', val_1, val_2) into res;
          return res;
      end;
      $$;


--
-- Name: is_visible_through_filters(realtime.wal_column[], realtime.user_defined_filter[]); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.is_visible_through_filters(columns realtime.wal_column[], filters realtime.user_defined_filter[]) RETURNS boolean
    LANGUAGE sql IMMUTABLE
    AS $_$
    /*
    Should the record be visible (true) or filtered out (false) after *filters* are applied
    */
        select
            -- Default to allowed when no filters present
            $2 is null -- no filters. this should not happen because subscriptions has a default
            or array_length($2, 1) is null -- array length of an empty array is null
            or bool_and(
                coalesce(
                    realtime.check_equality_op(
                        op:=f.op,
                        type_:=coalesce(
                            col.type_oid::regtype, -- null when wal2json version <= 2.4
                            col.type_name::regtype
                        ),
                        -- cast jsonb to text
                        val_1:=col.value #>> '{}',
                        val_2:=f.value
                    ),
                    false -- if null, filter does not match
                )
            )
        from
            unnest(filters) f
            join unnest(columns) col
                on f.column_name = col.name;
    $_$;


--
-- Name: list_changes(name, name, integer, integer); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.list_changes(publication name, slot_name name, max_changes integer, max_record_bytes integer) RETURNS SETOF realtime.wal_rls
    LANGUAGE sql
    SET log_min_messages TO 'fatal'
    AS $$
      with pub as (
        select
          concat_ws(
            ',',
            case when bool_or(pubinsert) then 'insert' else null end,
            case when bool_or(pubupdate) then 'update' else null end,
            case when bool_or(pubdelete) then 'delete' else null end
          ) as w2j_actions,
          coalesce(
            string_agg(
              realtime.quote_wal2json(format('%I.%I', schemaname, tablename)::regclass),
              ','
            ) filter (where ppt.tablename is not null and ppt.tablename not like '% %'),
            ''
          ) w2j_add_tables
        from
          pg_publication pp
          left join pg_publication_tables ppt
            on pp.pubname = ppt.pubname
        where
          pp.pubname = publication
        group by
          pp.pubname
        limit 1
      ),
      w2j as (
        select
          x.*, pub.w2j_add_tables
        from
          pub,
          pg_logical_slot_get_changes(
            slot_name, null, max_changes,
            'include-pk', 'true',
            'include-transaction', 'false',
            'include-timestamp', 'true',
            'include-type-oids', 'true',
            'format-version', '2',
            'actions', pub.w2j_actions,
            'add-tables', pub.w2j_add_tables
          ) x
      )
      select
        xyz.wal,
        xyz.is_rls_enabled,
        xyz.subscription_ids,
        xyz.errors
      from
        w2j,
        realtime.apply_rls(
          wal := w2j.data::jsonb,
          max_record_bytes := max_record_bytes
        ) xyz(wal, is_rls_enabled, subscription_ids, errors)
      where
        w2j.w2j_add_tables <> ''
        and xyz.subscription_ids[1] is not null
    $$;


--
-- Name: quote_wal2json(regclass); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.quote_wal2json(entity regclass) RETURNS text
    LANGUAGE sql IMMUTABLE STRICT
    AS $$
      select
        (
          select string_agg('' || ch,'')
          from unnest(string_to_array(nsp.nspname::text, null)) with ordinality x(ch, idx)
          where
            not (x.idx = 1 and x.ch = '"')
            and not (
              x.idx = array_length(string_to_array(nsp.nspname::text, null), 1)
              and x.ch = '"'
            )
        )
        || '.'
        || (
          select string_agg('' || ch,'')
          from unnest(string_to_array(pc.relname::text, null)) with ordinality x(ch, idx)
          where
            not (x.idx = 1 and x.ch = '"')
            and not (
              x.idx = array_length(string_to_array(nsp.nspname::text, null), 1)
              and x.ch = '"'
            )
          )
      from
        pg_class pc
        join pg_namespace nsp
          on pc.relnamespace = nsp.oid
      where
        pc.oid = entity
    $$;


--
-- Name: send(jsonb, text, text, boolean); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.send(payload jsonb, event text, topic text, private boolean DEFAULT true) RETURNS void
    LANGUAGE plpgsql
    AS $$
DECLARE
  generated_id uuid;
  final_payload jsonb;
BEGIN
  BEGIN
    -- Generate a new UUID for the id
    generated_id := gen_random_uuid();

    -- Check if payload has an 'id' key, if not, add the generated UUID
    IF payload ? 'id' THEN
      final_payload := payload;
    ELSE
      final_payload := jsonb_set(payload, '{id}', to_jsonb(generated_id));
    END IF;

    -- Set the topic configuration
    EXECUTE format('SET LOCAL realtime.topic TO %L', topic);

    -- Attempt to insert the message
    INSERT INTO realtime.messages (id, payload, event, topic, private, extension)
    VALUES (generated_id, final_payload, event, topic, private, 'broadcast');
  EXCEPTION
    WHEN OTHERS THEN
      -- Capture and notify the error
      RAISE WARNING 'ErrorSendingBroadcastMessage: %', SQLERRM;
  END;
END;
$$;


--
-- Name: subscription_check_filters(); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.subscription_check_filters() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
    /*
    Validates that the user defined filters for a subscription:
    - refer to valid columns that the claimed role may access
    - values are coercable to the correct column type
    */
    declare
        col_names text[] = coalesce(
                array_agg(c.column_name order by c.ordinal_position),
                '{}'::text[]
            )
            from
                information_schema.columns c
            where
                format('%I.%I', c.table_schema, c.table_name)::regclass = new.entity
                and pg_catalog.has_column_privilege(
                    (new.claims ->> 'role'),
                    format('%I.%I', c.table_schema, c.table_name)::regclass,
                    c.column_name,
                    'SELECT'
                );
        filter realtime.user_defined_filter;
        col_type regtype;

        in_val jsonb;
    begin
        for filter in select * from unnest(new.filters) loop
            -- Filtered column is valid
            if not filter.column_name = any(col_names) then
                raise exception 'invalid column for filter %', filter.column_name;
            end if;

            -- Type is sanitized and safe for string interpolation
            col_type = (
                select atttypid::regtype
                from pg_catalog.pg_attribute
                where attrelid = new.entity
                      and attname = filter.column_name
            );
            if col_type is null then
                raise exception 'failed to lookup type for column %', filter.column_name;
            end if;

            -- Set maximum number of entries for in filter
            if filter.op = 'in'::realtime.equality_op then
                in_val = realtime.cast(filter.value, (col_type::text || '[]')::regtype);
                if coalesce(jsonb_array_length(in_val), 0) > 100 then
                    raise exception 'too many values for `in` filter. Maximum 100';
                end if;
            else
                -- raises an exception if value is not coercable to type
                perform realtime.cast(filter.value, col_type);
            end if;

        end loop;

        -- Apply consistent order to filters so the unique constraint on
        -- (subscription_id, entity, filters) can't be tricked by a different filter order
        new.filters = coalesce(
            array_agg(f order by f.column_name, f.op, f.value),
            '{}'
        ) from unnest(new.filters) f;

        return new;
    end;
    $$;


--
-- Name: to_regrole(text); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.to_regrole(role_name text) RETURNS regrole
    LANGUAGE sql IMMUTABLE
    AS $$ select role_name::regrole $$;


--
-- Name: topic(); Type: FUNCTION; Schema: realtime; Owner: -
--

CREATE FUNCTION realtime.topic() RETURNS text
    LANGUAGE sql STABLE
    AS $$
select nullif(current_setting('realtime.topic', true), '')::text;
$$;


--
-- Name: can_insert_object(text, text, uuid, jsonb); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.can_insert_object(bucketid text, name text, owner uuid, metadata jsonb) RETURNS void
    LANGUAGE plpgsql
    AS $$
BEGIN
  INSERT INTO "storage"."objects" ("bucket_id", "name", "owner", "metadata") VALUES (bucketid, name, owner, metadata);
  -- hack to rollback the successful insert
  RAISE sqlstate 'PT200' using
  message = 'ROLLBACK',
  detail = 'rollback successful insert';
END
$$;


--
-- Name: enforce_bucket_name_length(); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.enforce_bucket_name_length() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
    if length(new.name) > 100 then
        raise exception 'bucket name "%" is too long (% characters). Max is 100.', new.name, length(new.name);
    end if;
    return new;
end;
$$;


--
-- Name: extension(text); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.extension(name text) RETURNS text
    LANGUAGE plpgsql
    AS $$
DECLARE
_parts text[];
_filename text;
BEGIN
	select string_to_array(name, '/') into _parts;
	select _parts[array_length(_parts,1)] into _filename;
	-- @todo return the last part instead of 2
	return reverse(split_part(reverse(_filename), '.', 1));
END
$$;


--
-- Name: filename(text); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.filename(name text) RETURNS text
    LANGUAGE plpgsql
    AS $$
DECLARE
_parts text[];
BEGIN
	select string_to_array(name, '/') into _parts;
	return _parts[array_length(_parts,1)];
END
$$;


--
-- Name: foldername(text); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.foldername(name text) RETURNS text[]
    LANGUAGE plpgsql
    AS $$
DECLARE
_parts text[];
BEGIN
	select string_to_array(name, '/') into _parts;
	return _parts[1:array_length(_parts,1)-1];
END
$$;


--
-- Name: get_common_prefix(text, text, text); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.get_common_prefix(p_key text, p_prefix text, p_delimiter text) RETURNS text
    LANGUAGE sql IMMUTABLE
    AS $$
SELECT CASE
    WHEN position(p_delimiter IN substring(p_key FROM length(p_prefix) + 1)) > 0
    THEN left(p_key, length(p_prefix) + position(p_delimiter IN substring(p_key FROM length(p_prefix) + 1)))
    ELSE NULL
END;
$$;


--
-- Name: get_size_by_bucket(); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.get_size_by_bucket() RETURNS TABLE(size bigint, bucket_id text)
    LANGUAGE plpgsql
    AS $$
BEGIN
    return query
        select sum((metadata->>'size')::int) as size, obj.bucket_id
        from "storage".objects as obj
        group by obj.bucket_id;
END
$$;


--
-- Name: list_multipart_uploads_with_delimiter(text, text, text, integer, text, text); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.list_multipart_uploads_with_delimiter(bucket_id text, prefix_param text, delimiter_param text, max_keys integer DEFAULT 100, next_key_token text DEFAULT ''::text, next_upload_token text DEFAULT ''::text) RETURNS TABLE(key text, id text, created_at timestamp with time zone)
    LANGUAGE plpgsql
    AS $_$
BEGIN
    RETURN QUERY EXECUTE
        'SELECT DISTINCT ON(key COLLATE "C") * from (
            SELECT
                CASE
                    WHEN position($2 IN substring(key from length($1) + 1)) > 0 THEN
                        substring(key from 1 for length($1) + position($2 IN substring(key from length($1) + 1)))
                    ELSE
                        key
                END AS key, id, created_at
            FROM
                storage.s3_multipart_uploads
            WHERE
                bucket_id = $5 AND
                key ILIKE $1 || ''%'' AND
                CASE
                    WHEN $4 != '''' AND $6 = '''' THEN
                        CASE
                            WHEN position($2 IN substring(key from length($1) + 1)) > 0 THEN
                                substring(key from 1 for length($1) + position($2 IN substring(key from length($1) + 1))) COLLATE "C" > $4
                            ELSE
                                key COLLATE "C" > $4
                            END
                    ELSE
                        true
                END AND
                CASE
                    WHEN $6 != '''' THEN
                        id COLLATE "C" > $6
                    ELSE
                        true
                    END
            ORDER BY
                key COLLATE "C" ASC, created_at ASC) as e order by key COLLATE "C" LIMIT $3'
        USING prefix_param, delimiter_param, max_keys, next_key_token, bucket_id, next_upload_token;
END;
$_$;


--
-- Name: list_objects_with_delimiter(text, text, text, integer, text, text, text); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.list_objects_with_delimiter(_bucket_id text, prefix_param text, delimiter_param text, max_keys integer DEFAULT 100, start_after text DEFAULT ''::text, next_token text DEFAULT ''::text, sort_order text DEFAULT 'asc'::text) RETURNS TABLE(name text, id uuid, metadata jsonb, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone)
    LANGUAGE plpgsql STABLE
    AS $_$
DECLARE
    v_peek_name TEXT;
    v_current RECORD;
    v_common_prefix TEXT;

    -- Configuration
    v_is_asc BOOLEAN;
    v_prefix TEXT;
    v_start TEXT;
    v_upper_bound TEXT;
    v_file_batch_size INT;

    -- Seek state
    v_next_seek TEXT;
    v_count INT := 0;

    -- Dynamic SQL for batch query only
    v_batch_query TEXT;

BEGIN
    -- ========================================================================
    -- INITIALIZATION
    -- ========================================================================
    v_is_asc := lower(coalesce(sort_order, 'asc')) = 'asc';
    v_prefix := coalesce(prefix_param, '');
    v_start := CASE WHEN coalesce(next_token, '') <> '' THEN next_token ELSE coalesce(start_after, '') END;
    v_file_batch_size := LEAST(GREATEST(max_keys * 2, 100), 1000);

    -- Calculate upper bound for prefix filtering (bytewise, using COLLATE "C")
    IF v_prefix = '' THEN
        v_upper_bound := NULL;
    ELSIF right(v_prefix, 1) = delimiter_param THEN
        v_upper_bound := left(v_prefix, -1) || chr(ascii(delimiter_param) + 1);
    ELSE
        v_upper_bound := left(v_prefix, -1) || chr(ascii(right(v_prefix, 1)) + 1);
    END IF;

    -- Build batch query (dynamic SQL - called infrequently, amortized over many rows)
    IF v_is_asc THEN
        IF v_upper_bound IS NOT NULL THEN
            v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
                'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" >= $2 ' ||
                'AND o.name COLLATE "C" < $3 ORDER BY o.name COLLATE "C" ASC LIMIT $4';
        ELSE
            v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
                'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" >= $2 ' ||
                'ORDER BY o.name COLLATE "C" ASC LIMIT $4';
        END IF;
    ELSE
        IF v_upper_bound IS NOT NULL THEN
            v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
                'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" < $2 ' ||
                'AND o.name COLLATE "C" >= $3 ORDER BY o.name COLLATE "C" DESC LIMIT $4';
        ELSE
            v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
                'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" < $2 ' ||
                'ORDER BY o.name COLLATE "C" DESC LIMIT $4';
        END IF;
    END IF;

    -- ========================================================================
    -- SEEK INITIALIZATION: Determine starting position
    -- ========================================================================
    IF v_start = '' THEN
        IF v_is_asc THEN
            v_next_seek := v_prefix;
        ELSE
            -- DESC without cursor: find the last item in range
            IF v_upper_bound IS NOT NULL THEN
                SELECT o.name INTO v_next_seek FROM storage.objects o
                WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_prefix AND o.name COLLATE "C" < v_upper_bound
                ORDER BY o.name COLLATE "C" DESC LIMIT 1;
            ELSIF v_prefix <> '' THEN
                SELECT o.name INTO v_next_seek FROM storage.objects o
                WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_prefix
                ORDER BY o.name COLLATE "C" DESC LIMIT 1;
            ELSE
                SELECT o.name INTO v_next_seek FROM storage.objects o
                WHERE o.bucket_id = _bucket_id
                ORDER BY o.name COLLATE "C" DESC LIMIT 1;
            END IF;

            IF v_next_seek IS NOT NULL THEN
                v_next_seek := v_next_seek || delimiter_param;
            ELSE
                RETURN;
            END IF;
        END IF;
    ELSE
        -- Cursor provided: determine if it refers to a folder or leaf
        IF EXISTS (
            SELECT 1 FROM storage.objects o
            WHERE o.bucket_id = _bucket_id
              AND o.name COLLATE "C" LIKE v_start || delimiter_param || '%'
            LIMIT 1
        ) THEN
            -- Cursor refers to a folder
            IF v_is_asc THEN
                v_next_seek := v_start || chr(ascii(delimiter_param) + 1);
            ELSE
                v_next_seek := v_start || delimiter_param;
            END IF;
        ELSE
            -- Cursor refers to a leaf object
            IF v_is_asc THEN
                v_next_seek := v_start || delimiter_param;
            ELSE
                v_next_seek := v_start;
            END IF;
        END IF;
    END IF;

    -- ========================================================================
    -- MAIN LOOP: Hybrid peek-then-batch algorithm
    -- Uses STATIC SQL for peek (hot path) and DYNAMIC SQL for batch
    -- ========================================================================
    LOOP
        EXIT WHEN v_count >= max_keys;

        -- STEP 1: PEEK using STATIC SQL (plan cached, very fast)
        IF v_is_asc THEN
            IF v_upper_bound IS NOT NULL THEN
                SELECT o.name INTO v_peek_name FROM storage.objects o
                WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_next_seek AND o.name COLLATE "C" < v_upper_bound
                ORDER BY o.name COLLATE "C" ASC LIMIT 1;
            ELSE
                SELECT o.name INTO v_peek_name FROM storage.objects o
                WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_next_seek
                ORDER BY o.name COLLATE "C" ASC LIMIT 1;
            END IF;
        ELSE
            IF v_upper_bound IS NOT NULL THEN
                SELECT o.name INTO v_peek_name FROM storage.objects o
                WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" < v_next_seek AND o.name COLLATE "C" >= v_prefix
                ORDER BY o.name COLLATE "C" DESC LIMIT 1;
            ELSIF v_prefix <> '' THEN
                SELECT o.name INTO v_peek_name FROM storage.objects o
                WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" < v_next_seek AND o.name COLLATE "C" >= v_prefix
                ORDER BY o.name COLLATE "C" DESC LIMIT 1;
            ELSE
                SELECT o.name INTO v_peek_name FROM storage.objects o
                WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" < v_next_seek
                ORDER BY o.name COLLATE "C" DESC LIMIT 1;
            END IF;
        END IF;

        EXIT WHEN v_peek_name IS NULL;

        -- STEP 2: Check if this is a FOLDER or FILE
        v_common_prefix := storage.get_common_prefix(v_peek_name, v_prefix, delimiter_param);

        IF v_common_prefix IS NOT NULL THEN
            -- FOLDER: Emit and skip to next folder (no heap access needed)
            name := rtrim(v_common_prefix, delimiter_param);
            id := NULL;
            updated_at := NULL;
            created_at := NULL;
            last_accessed_at := NULL;
            metadata := NULL;
            RETURN NEXT;
            v_count := v_count + 1;

            -- Advance seek past the folder range
            IF v_is_asc THEN
                v_next_seek := left(v_common_prefix, -1) || chr(ascii(delimiter_param) + 1);
            ELSE
                v_next_seek := v_common_prefix;
            END IF;
        ELSE
            -- FILE: Batch fetch using DYNAMIC SQL (overhead amortized over many rows)
            -- For ASC: upper_bound is the exclusive upper limit (< condition)
            -- For DESC: prefix is the inclusive lower limit (>= condition)
            FOR v_current IN EXECUTE v_batch_query USING _bucket_id, v_next_seek,
                CASE WHEN v_is_asc THEN COALESCE(v_upper_bound, v_prefix) ELSE v_prefix END, v_file_batch_size
            LOOP
                v_common_prefix := storage.get_common_prefix(v_current.name, v_prefix, delimiter_param);

                IF v_common_prefix IS NOT NULL THEN
                    -- Hit a folder: exit batch, let peek handle it
                    v_next_seek := v_current.name;
                    EXIT;
                END IF;

                -- Emit file
                name := v_current.name;
                id := v_current.id;
                updated_at := v_current.updated_at;
                created_at := v_current.created_at;
                last_accessed_at := v_current.last_accessed_at;
                metadata := v_current.metadata;
                RETURN NEXT;
                v_count := v_count + 1;

                -- Advance seek past this file
                IF v_is_asc THEN
                    v_next_seek := v_current.name || delimiter_param;
                ELSE
                    v_next_seek := v_current.name;
                END IF;

                EXIT WHEN v_count >= max_keys;
            END LOOP;
        END IF;
    END LOOP;
END;
$_$;


--
-- Name: operation(); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.operation() RETURNS text
    LANGUAGE plpgsql STABLE
    AS $$
BEGIN
    RETURN current_setting('storage.operation', true);
END;
$$;


--
-- Name: protect_delete(); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.protect_delete() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
    -- Check if storage.allow_delete_query is set to 'true'
    IF COALESCE(current_setting('storage.allow_delete_query', true), 'false') != 'true' THEN
        RAISE EXCEPTION 'Direct deletion from storage tables is not allowed. Use the Storage API instead.'
            USING HINT = 'This prevents accidental data loss from orphaned objects.',
                  ERRCODE = '42501';
    END IF;
    RETURN NULL;
END;
$$;


--
-- Name: search(text, text, integer, integer, integer, text, text, text); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.search(prefix text, bucketname text, limits integer DEFAULT 100, levels integer DEFAULT 1, offsets integer DEFAULT 0, search text DEFAULT ''::text, sortcolumn text DEFAULT 'name'::text, sortorder text DEFAULT 'asc'::text) RETURNS TABLE(name text, id uuid, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone, metadata jsonb)
    LANGUAGE plpgsql STABLE
    AS $_$
DECLARE
    v_peek_name TEXT;
    v_current RECORD;
    v_common_prefix TEXT;
    v_delimiter CONSTANT TEXT := '/';

    -- Configuration
    v_limit INT;
    v_prefix TEXT;
    v_prefix_lower TEXT;
    v_is_asc BOOLEAN;
    v_order_by TEXT;
    v_sort_order TEXT;
    v_upper_bound TEXT;
    v_file_batch_size INT;

    -- Dynamic SQL for batch query only
    v_batch_query TEXT;

    -- Seek state
    v_next_seek TEXT;
    v_count INT := 0;
    v_skipped INT := 0;
BEGIN
    -- ========================================================================
    -- INITIALIZATION
    -- ========================================================================
    v_limit := LEAST(coalesce(limits, 100), 1500);
    v_prefix := coalesce(prefix, '') || coalesce(search, '');
    v_prefix_lower := lower(v_prefix);
    v_is_asc := lower(coalesce(sortorder, 'asc')) = 'asc';
    v_file_batch_size := LEAST(GREATEST(v_limit * 2, 100), 1000);

    -- Validate sort column
    CASE lower(coalesce(sortcolumn, 'name'))
        WHEN 'name' THEN v_order_by := 'name';
        WHEN 'updated_at' THEN v_order_by := 'updated_at';
        WHEN 'created_at' THEN v_order_by := 'created_at';
        WHEN 'last_accessed_at' THEN v_order_by := 'last_accessed_at';
        ELSE v_order_by := 'name';
    END CASE;

    v_sort_order := CASE WHEN v_is_asc THEN 'asc' ELSE 'desc' END;

    -- ========================================================================
    -- NON-NAME SORTING: Use path_tokens approach (unchanged)
    -- ========================================================================
    IF v_order_by != 'name' THEN
        RETURN QUERY EXECUTE format(
            $sql$
            WITH folders AS (
                SELECT path_tokens[$1] AS folder
                FROM storage.objects
                WHERE objects.name ILIKE $2 || '%%'
                  AND bucket_id = $3
                  AND array_length(objects.path_tokens, 1) <> $1
                GROUP BY folder
                ORDER BY folder %s
            )
            (SELECT folder AS "name",
                   NULL::uuid AS id,
                   NULL::timestamptz AS updated_at,
                   NULL::timestamptz AS created_at,
                   NULL::timestamptz AS last_accessed_at,
                   NULL::jsonb AS metadata FROM folders)
            UNION ALL
            (SELECT path_tokens[$1] AS "name",
                   id, updated_at, created_at, last_accessed_at, metadata
             FROM storage.objects
             WHERE objects.name ILIKE $2 || '%%'
               AND bucket_id = $3
               AND array_length(objects.path_tokens, 1) = $1
             ORDER BY %I %s)
            LIMIT $4 OFFSET $5
            $sql$, v_sort_order, v_order_by, v_sort_order
        ) USING levels, v_prefix, bucketname, v_limit, offsets;
        RETURN;
    END IF;

    -- ========================================================================
    -- NAME SORTING: Hybrid skip-scan with batch optimization
    -- ========================================================================

    -- Calculate upper bound for prefix filtering
    IF v_prefix_lower = '' THEN
        v_upper_bound := NULL;
    ELSIF right(v_prefix_lower, 1) = v_delimiter THEN
        v_upper_bound := left(v_prefix_lower, -1) || chr(ascii(v_delimiter) + 1);
    ELSE
        v_upper_bound := left(v_prefix_lower, -1) || chr(ascii(right(v_prefix_lower, 1)) + 1);
    END IF;

    -- Build batch query (dynamic SQL - called infrequently, amortized over many rows)
    IF v_is_asc THEN
        IF v_upper_bound IS NOT NULL THEN
            v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
                'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" >= $2 ' ||
                'AND lower(o.name) COLLATE "C" < $3 ORDER BY lower(o.name) COLLATE "C" ASC LIMIT $4';
        ELSE
            v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
                'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" >= $2 ' ||
                'ORDER BY lower(o.name) COLLATE "C" ASC LIMIT $4';
        END IF;
    ELSE
        IF v_upper_bound IS NOT NULL THEN
            v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
                'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" < $2 ' ||
                'AND lower(o.name) COLLATE "C" >= $3 ORDER BY lower(o.name) COLLATE "C" DESC LIMIT $4';
        ELSE
            v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' ||
                'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" < $2 ' ||
                'ORDER BY lower(o.name) COLLATE "C" DESC LIMIT $4';
        END IF;
    END IF;

    -- Initialize seek position
    IF v_is_asc THEN
        v_next_seek := v_prefix_lower;
    ELSE
        -- DESC: find the last item in range first (static SQL)
        IF v_upper_bound IS NOT NULL THEN
            SELECT o.name INTO v_peek_name FROM storage.objects o
            WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_prefix_lower AND lower(o.name) COLLATE "C" < v_upper_bound
            ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
        ELSIF v_prefix_lower <> '' THEN
            SELECT o.name INTO v_peek_name FROM storage.objects o
            WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_prefix_lower
            ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
        ELSE
            SELECT o.name INTO v_peek_name FROM storage.objects o
            WHERE o.bucket_id = bucketname
            ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
        END IF;

        IF v_peek_name IS NOT NULL THEN
            v_next_seek := lower(v_peek_name) || v_delimiter;
        ELSE
            RETURN;
        END IF;
    END IF;

    -- ========================================================================
    -- MAIN LOOP: Hybrid peek-then-batch algorithm
    -- Uses STATIC SQL for peek (hot path) and DYNAMIC SQL for batch
    -- ========================================================================
    LOOP
        EXIT WHEN v_count >= v_limit;

        -- STEP 1: PEEK using STATIC SQL (plan cached, very fast)
        IF v_is_asc THEN
            IF v_upper_bound IS NOT NULL THEN
                SELECT o.name INTO v_peek_name FROM storage.objects o
                WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_next_seek AND lower(o.name) COLLATE "C" < v_upper_bound
                ORDER BY lower(o.name) COLLATE "C" ASC LIMIT 1;
            ELSE
                SELECT o.name INTO v_peek_name FROM storage.objects o
                WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_next_seek
                ORDER BY lower(o.name) COLLATE "C" ASC LIMIT 1;
            END IF;
        ELSE
            IF v_upper_bound IS NOT NULL THEN
                SELECT o.name INTO v_peek_name FROM storage.objects o
                WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek AND lower(o.name) COLLATE "C" >= v_prefix_lower
                ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
            ELSIF v_prefix_lower <> '' THEN
                SELECT o.name INTO v_peek_name FROM storage.objects o
                WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek AND lower(o.name) COLLATE "C" >= v_prefix_lower
                ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
            ELSE
                SELECT o.name INTO v_peek_name FROM storage.objects o
                WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek
                ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1;
            END IF;
        END IF;

        EXIT WHEN v_peek_name IS NULL;

        -- STEP 2: Check if this is a FOLDER or FILE
        v_common_prefix := storage.get_common_prefix(lower(v_peek_name), v_prefix_lower, v_delimiter);

        IF v_common_prefix IS NOT NULL THEN
            -- FOLDER: Handle offset, emit if needed, skip to next folder
            IF v_skipped < offsets THEN
                v_skipped := v_skipped + 1;
            ELSE
                name := split_part(rtrim(v_common_prefix, v_delimiter), v_delimiter, levels);
                id := NULL;
                updated_at := NULL;
                created_at := NULL;
                last_accessed_at := NULL;
                metadata := NULL;
                RETURN NEXT;
                v_count := v_count + 1;
            END IF;

            -- Advance seek past the folder range
            IF v_is_asc THEN
                v_next_seek := lower(left(v_common_prefix, -1)) || chr(ascii(v_delimiter) + 1);
            ELSE
                v_next_seek := lower(v_common_prefix);
            END IF;
        ELSE
            -- FILE: Batch fetch using DYNAMIC SQL (overhead amortized over many rows)
            -- For ASC: upper_bound is the exclusive upper limit (< condition)
            -- For DESC: prefix_lower is the inclusive lower limit (>= condition)
            FOR v_current IN EXECUTE v_batch_query
                USING bucketname, v_next_seek,
                    CASE WHEN v_is_asc THEN COALESCE(v_upper_bound, v_prefix_lower) ELSE v_prefix_lower END, v_file_batch_size
            LOOP
                v_common_prefix := storage.get_common_prefix(lower(v_current.name), v_prefix_lower, v_delimiter);

                IF v_common_prefix IS NOT NULL THEN
                    -- Hit a folder: exit batch, let peek handle it
                    v_next_seek := lower(v_current.name);
                    EXIT;
                END IF;

                -- Handle offset skipping
                IF v_skipped < offsets THEN
                    v_skipped := v_skipped + 1;
                ELSE
                    -- Emit file
                    name := split_part(v_current.name, v_delimiter, levels);
                    id := v_current.id;
                    updated_at := v_current.updated_at;
                    created_at := v_current.created_at;
                    last_accessed_at := v_current.last_accessed_at;
                    metadata := v_current.metadata;
                    RETURN NEXT;
                    v_count := v_count + 1;
                END IF;

                -- Advance seek past this file
                IF v_is_asc THEN
                    v_next_seek := lower(v_current.name) || v_delimiter;
                ELSE
                    v_next_seek := lower(v_current.name);
                END IF;

                EXIT WHEN v_count >= v_limit;
            END LOOP;
        END IF;
    END LOOP;
END;
$_$;


--
-- Name: search_by_timestamp(text, text, integer, integer, text, text, text, text); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.search_by_timestamp(p_prefix text, p_bucket_id text, p_limit integer, p_level integer, p_start_after text, p_sort_order text, p_sort_column text, p_sort_column_after text) RETURNS TABLE(key text, name text, id uuid, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone, metadata jsonb)
    LANGUAGE plpgsql STABLE
    AS $_$
DECLARE
    v_cursor_op text;
    v_query text;
    v_prefix text;
BEGIN
    v_prefix := coalesce(p_prefix, '');

    IF p_sort_order = 'asc' THEN
        v_cursor_op := '>';
    ELSE
        v_cursor_op := '<';
    END IF;

    v_query := format($sql$
        WITH raw_objects AS (
            SELECT
                o.name AS obj_name,
                o.id AS obj_id,
                o.updated_at AS obj_updated_at,
                o.created_at AS obj_created_at,
                o.last_accessed_at AS obj_last_accessed_at,
                o.metadata AS obj_metadata,
                storage.get_common_prefix(o.name, $1, '/') AS common_prefix
            FROM storage.objects o
            WHERE o.bucket_id = $2
              AND o.name COLLATE "C" LIKE $1 || '%%'
        ),
        -- Aggregate common prefixes (folders)
        -- Both created_at and updated_at use MIN(obj_created_at) to match the old prefixes table behavior
        aggregated_prefixes AS (
            SELECT
                rtrim(common_prefix, '/') AS name,
                NULL::uuid AS id,
                MIN(obj_created_at) AS updated_at,
                MIN(obj_created_at) AS created_at,
                NULL::timestamptz AS last_accessed_at,
                NULL::jsonb AS metadata,
                TRUE AS is_prefix
            FROM raw_objects
            WHERE common_prefix IS NOT NULL
            GROUP BY common_prefix
        ),
        leaf_objects AS (
            SELECT
                obj_name AS name,
                obj_id AS id,
                obj_updated_at AS updated_at,
                obj_created_at AS created_at,
                obj_last_accessed_at AS last_accessed_at,
                obj_metadata AS metadata,
                FALSE AS is_prefix
            FROM raw_objects
            WHERE common_prefix IS NULL
        ),
        combined AS (
            SELECT * FROM aggregated_prefixes
            UNION ALL
            SELECT * FROM leaf_objects
        ),
        filtered AS (
            SELECT *
            FROM combined
            WHERE (
                $5 = ''
                OR ROW(
                    date_trunc('milliseconds', %I),
                    name COLLATE "C"
                ) %s ROW(
                    COALESCE(NULLIF($6, '')::timestamptz, 'epoch'::timestamptz),
                    $5
                )
            )
        )
        SELECT
            split_part(name, '/', $3) AS key,
            name,
            id,
            updated_at,
            created_at,
            last_accessed_at,
            metadata
        FROM filtered
        ORDER BY
            COALESCE(date_trunc('milliseconds', %I), 'epoch'::timestamptz) %s,
            name COLLATE "C" %s
        LIMIT $4
    $sql$,
        p_sort_column,
        v_cursor_op,
        p_sort_column,
        p_sort_order,
        p_sort_order
    );

    RETURN QUERY EXECUTE v_query
    USING v_prefix, p_bucket_id, p_level, p_limit, p_start_after, p_sort_column_after;
END;
$_$;


--
-- Name: search_v2(text, text, integer, integer, text, text, text, text); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.search_v2(prefix text, bucket_name text, limits integer DEFAULT 100, levels integer DEFAULT 1, start_after text DEFAULT ''::text, sort_order text DEFAULT 'asc'::text, sort_column text DEFAULT 'name'::text, sort_column_after text DEFAULT ''::text) RETURNS TABLE(key text, name text, id uuid, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone, metadata jsonb)
    LANGUAGE plpgsql STABLE
    AS $$
DECLARE
    v_sort_col text;
    v_sort_ord text;
    v_limit int;
BEGIN
    -- Cap limit to maximum of 1500 records
    v_limit := LEAST(coalesce(limits, 100), 1500);

    -- Validate and normalize sort_order
    v_sort_ord := lower(coalesce(sort_order, 'asc'));
    IF v_sort_ord NOT IN ('asc', 'desc') THEN
        v_sort_ord := 'asc';
    END IF;

    -- Validate and normalize sort_column
    v_sort_col := lower(coalesce(sort_column, 'name'));
    IF v_sort_col NOT IN ('name', 'updated_at', 'created_at') THEN
        v_sort_col := 'name';
    END IF;

    -- Route to appropriate implementation
    IF v_sort_col = 'name' THEN
        -- Use list_objects_with_delimiter for name sorting (most efficient: O(k * log n))
        RETURN QUERY
        SELECT
            split_part(l.name, '/', levels) AS key,
            l.name AS name,
            l.id,
            l.updated_at,
            l.created_at,
            l.last_accessed_at,
            l.metadata
        FROM storage.list_objects_with_delimiter(
            bucket_name,
            coalesce(prefix, ''),
            '/',
            v_limit,
            start_after,
            '',
            v_sort_ord
        ) l;
    ELSE
        -- Use aggregation approach for timestamp sorting
        -- Not efficient for large datasets but supports correct pagination
        RETURN QUERY SELECT * FROM storage.search_by_timestamp(
            prefix, bucket_name, v_limit, levels, start_after,
            v_sort_ord, v_sort_col, sort_column_after
        );
    END IF;
END;
$$;


--
-- Name: update_updated_at_column(); Type: FUNCTION; Schema: storage; Owner: -
--

CREATE FUNCTION storage.update_updated_at_column() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
    NEW.updated_at = now();
    RETURN NEW; 
END;
$$;


--
-- Name: http_request(); Type: FUNCTION; Schema: supabase_functions; Owner: -
--

CREATE FUNCTION supabase_functions.http_request() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'supabase_functions'
    AS $$
  DECLARE
    request_id bigint;
    payload jsonb;
    url text := TG_ARGV[0]::text;
    method text := TG_ARGV[1]::text;
    headers jsonb DEFAULT '{}'::jsonb;
    params jsonb DEFAULT '{}'::jsonb;
    timeout_ms integer DEFAULT 1000;
  BEGIN
    IF url IS NULL OR url = 'null' THEN
      RAISE EXCEPTION 'url argument is missing';
    END IF;

    IF method IS NULL OR method = 'null' THEN
      RAISE EXCEPTION 'method argument is missing';
    END IF;

    IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
      headers = '{"Content-Type": "application/json"}'::jsonb;
    ELSE
      headers = TG_ARGV[2]::jsonb;
    END IF;

    IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
      params = '{}'::jsonb;
    ELSE
      params = TG_ARGV[3]::jsonb;
    END IF;

    IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
      timeout_ms = 1000;
    ELSE
      timeout_ms = TG_ARGV[4]::integer;
    END IF;

    CASE
      WHEN method = 'GET' THEN
        SELECT http_get INTO request_id FROM net.http_get(
          url,
          params,
          headers,
          timeout_ms
        );
      WHEN method = 'POST' THEN
        payload = jsonb_build_object(
          'old_record', OLD,
          'record', NEW,
          'type', TG_OP,
          'table', TG_TABLE_NAME,
          'schema', TG_TABLE_SCHEMA
        );

        SELECT http_post INTO request_id FROM net.http_post(
          url,
          payload,
          params,
          headers,
          timeout_ms
        );
      ELSE
        RAISE EXCEPTION 'method argument % is invalid', method;
    END CASE;

    INSERT INTO supabase_functions.hooks
      (hook_table_id, hook_name, request_id)
    VALUES
      (TG_RELID, TG_NAME, request_id);

    RETURN NEW;
  END
$$;


--
-- Name: extensions; Type: TABLE; Schema: _realtime; Owner: -
--

CREATE TABLE _realtime.extensions (
    id uuid NOT NULL,
    type text,
    settings jsonb,
    tenant_external_id text,
    inserted_at timestamp(0) without time zone NOT NULL,
    updated_at timestamp(0) without time zone NOT NULL
);


--
-- Name: schema_migrations; Type: TABLE; Schema: _realtime; Owner: -
--

CREATE TABLE _realtime.schema_migrations (
    version bigint NOT NULL,
    inserted_at timestamp(0) without time zone
);


--
-- Name: tenants; Type: TABLE; Schema: _realtime; Owner: -
--

CREATE TABLE _realtime.tenants (
    id uuid NOT NULL,
    name text,
    external_id text,
    jwt_secret text,
    max_concurrent_users integer DEFAULT 200 NOT NULL,
    inserted_at timestamp(0) without time zone NOT NULL,
    updated_at timestamp(0) without time zone NOT NULL,
    max_events_per_second integer DEFAULT 100 NOT NULL,
    postgres_cdc_default text DEFAULT 'postgres_cdc_rls'::text,
    max_bytes_per_second integer DEFAULT 100000 NOT NULL,
    max_channels_per_client integer DEFAULT 100 NOT NULL,
    max_joins_per_second integer DEFAULT 500 NOT NULL,
    suspend boolean DEFAULT false,
    jwt_jwks jsonb,
    notify_private_alpha boolean DEFAULT false,
    private_only boolean DEFAULT false NOT NULL,
    migrations_ran integer DEFAULT 0,
    broadcast_adapter character varying(255) DEFAULT 'gen_rpc'::character varying,
    max_presence_events_per_second integer DEFAULT 1000,
    max_payload_size_in_kb integer DEFAULT 3000,
    CONSTRAINT jwt_secret_or_jwt_jwks_required CHECK (((jwt_secret IS NOT NULL) OR (jwt_jwks IS NOT NULL)))
);


--
-- Name: audit_log_entries; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.audit_log_entries (
    instance_id uuid,
    id uuid NOT NULL,
    payload json,
    created_at timestamp with time zone,
    ip_address character varying(64) DEFAULT ''::character varying NOT NULL
);


--
-- Name: TABLE audit_log_entries; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.audit_log_entries IS 'Auth: Audit trail for user actions.';


--
-- Name: flow_state; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.flow_state (
    id uuid NOT NULL,
    user_id uuid,
    auth_code text,
    code_challenge_method auth.code_challenge_method,
    code_challenge text,
    provider_type text NOT NULL,
    provider_access_token text,
    provider_refresh_token text,
    created_at timestamp with time zone,
    updated_at timestamp with time zone,
    authentication_method text NOT NULL,
    auth_code_issued_at timestamp with time zone,
    invite_token text,
    referrer text,
    oauth_client_state_id uuid,
    linking_target_id uuid,
    email_optional boolean DEFAULT false NOT NULL
);


--
-- Name: TABLE flow_state; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.flow_state IS 'Stores metadata for all OAuth/SSO login flows';


--
-- Name: identities; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.identities (
    provider_id text NOT NULL,
    user_id uuid NOT NULL,
    identity_data jsonb NOT NULL,
    provider text NOT NULL,
    last_sign_in_at timestamp with time zone,
    created_at timestamp with time zone,
    updated_at timestamp with time zone,
    email text GENERATED ALWAYS AS (lower((identity_data ->> 'email'::text))) STORED,
    id uuid DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: TABLE identities; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.identities IS 'Auth: Stores identities associated to a user.';


--
-- Name: COLUMN identities.email; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON COLUMN auth.identities.email IS 'Auth: Email is a generated column that references the optional email property in the identity_data';


--
-- Name: instances; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.instances (
    id uuid NOT NULL,
    uuid uuid,
    raw_base_config text,
    created_at timestamp with time zone,
    updated_at timestamp with time zone
);


--
-- Name: TABLE instances; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.instances IS 'Auth: Manages users across multiple sites.';


--
-- Name: mfa_amr_claims; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.mfa_amr_claims (
    session_id uuid NOT NULL,
    created_at timestamp with time zone NOT NULL,
    updated_at timestamp with time zone NOT NULL,
    authentication_method text NOT NULL,
    id uuid NOT NULL
);


--
-- Name: TABLE mfa_amr_claims; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.mfa_amr_claims IS 'auth: stores authenticator method reference claims for multi factor authentication';


--
-- Name: mfa_challenges; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.mfa_challenges (
    id uuid NOT NULL,
    factor_id uuid NOT NULL,
    created_at timestamp with time zone NOT NULL,
    verified_at timestamp with time zone,
    ip_address inet NOT NULL,
    otp_code text,
    web_authn_session_data jsonb
);


--
-- Name: TABLE mfa_challenges; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.mfa_challenges IS 'auth: stores metadata about challenge requests made';


--
-- Name: mfa_factors; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.mfa_factors (
    id uuid NOT NULL,
    user_id uuid NOT NULL,
    friendly_name text,
    factor_type auth.factor_type NOT NULL,
    status auth.factor_status NOT NULL,
    created_at timestamp with time zone NOT NULL,
    updated_at timestamp with time zone NOT NULL,
    secret text,
    phone text,
    last_challenged_at timestamp with time zone,
    web_authn_credential jsonb,
    web_authn_aaguid uuid,
    last_webauthn_challenge_data jsonb
);


--
-- Name: TABLE mfa_factors; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.mfa_factors IS 'auth: stores metadata about factors';


--
-- Name: COLUMN mfa_factors.last_webauthn_challenge_data; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON COLUMN auth.mfa_factors.last_webauthn_challenge_data IS 'Stores the latest WebAuthn challenge data including attestation/assertion for customer verification';


--
-- Name: oauth_authorizations; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.oauth_authorizations (
    id uuid NOT NULL,
    authorization_id text NOT NULL,
    client_id uuid NOT NULL,
    user_id uuid,
    redirect_uri text NOT NULL,
    scope text NOT NULL,
    state text,
    resource text,
    code_challenge text,
    code_challenge_method auth.code_challenge_method,
    response_type auth.oauth_response_type DEFAULT 'code'::auth.oauth_response_type NOT NULL,
    status auth.oauth_authorization_status DEFAULT 'pending'::auth.oauth_authorization_status NOT NULL,
    authorization_code text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    expires_at timestamp with time zone DEFAULT (now() + '00:03:00'::interval) NOT NULL,
    approved_at timestamp with time zone,
    nonce text,
    CONSTRAINT oauth_authorizations_authorization_code_length CHECK ((char_length(authorization_code) <= 255)),
    CONSTRAINT oauth_authorizations_code_challenge_length CHECK ((char_length(code_challenge) <= 128)),
    CONSTRAINT oauth_authorizations_expires_at_future CHECK ((expires_at > created_at)),
    CONSTRAINT oauth_authorizations_nonce_length CHECK ((char_length(nonce) <= 255)),
    CONSTRAINT oauth_authorizations_redirect_uri_length CHECK ((char_length(redirect_uri) <= 2048)),
    CONSTRAINT oauth_authorizations_resource_length CHECK ((char_length(resource) <= 2048)),
    CONSTRAINT oauth_authorizations_scope_length CHECK ((char_length(scope) <= 4096)),
    CONSTRAINT oauth_authorizations_state_length CHECK ((char_length(state) <= 4096))
);


--
-- Name: oauth_client_states; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.oauth_client_states (
    id uuid NOT NULL,
    provider_type text NOT NULL,
    code_verifier text,
    created_at timestamp with time zone NOT NULL
);


--
-- Name: TABLE oauth_client_states; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.oauth_client_states IS 'Stores OAuth states for third-party provider authentication flows where Supabase acts as the OAuth client.';


--
-- Name: oauth_clients; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.oauth_clients (
    id uuid NOT NULL,
    client_secret_hash text,
    registration_type auth.oauth_registration_type NOT NULL,
    redirect_uris text NOT NULL,
    grant_types text NOT NULL,
    client_name text,
    client_uri text,
    logo_uri text,
    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,
    client_type auth.oauth_client_type DEFAULT 'confidential'::auth.oauth_client_type NOT NULL,
    token_endpoint_auth_method text NOT NULL,
    CONSTRAINT oauth_clients_client_name_length CHECK ((char_length(client_name) <= 1024)),
    CONSTRAINT oauth_clients_client_uri_length CHECK ((char_length(client_uri) <= 2048)),
    CONSTRAINT oauth_clients_logo_uri_length CHECK ((char_length(logo_uri) <= 2048)),
    CONSTRAINT oauth_clients_token_endpoint_auth_method_check CHECK ((token_endpoint_auth_method = ANY (ARRAY['client_secret_basic'::text, 'client_secret_post'::text, 'none'::text])))
);


--
-- Name: oauth_consents; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.oauth_consents (
    id uuid NOT NULL,
    user_id uuid NOT NULL,
    client_id uuid NOT NULL,
    scopes text NOT NULL,
    granted_at timestamp with time zone DEFAULT now() NOT NULL,
    revoked_at timestamp with time zone,
    CONSTRAINT oauth_consents_revoked_after_granted CHECK (((revoked_at IS NULL) OR (revoked_at >= granted_at))),
    CONSTRAINT oauth_consents_scopes_length CHECK ((char_length(scopes) <= 2048)),
    CONSTRAINT oauth_consents_scopes_not_empty CHECK ((char_length(TRIM(BOTH FROM scopes)) > 0))
);


--
-- Name: one_time_tokens; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.one_time_tokens (
    id uuid NOT NULL,
    user_id uuid NOT NULL,
    token_type auth.one_time_token_type NOT NULL,
    token_hash text NOT NULL,
    relates_to text NOT NULL,
    created_at timestamp without time zone DEFAULT now() NOT NULL,
    updated_at timestamp without time zone DEFAULT now() NOT NULL,
    CONSTRAINT one_time_tokens_token_hash_check CHECK ((char_length(token_hash) > 0))
);


--
-- Name: refresh_tokens; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.refresh_tokens (
    instance_id uuid,
    id bigint NOT NULL,
    token character varying(255),
    user_id character varying(255),
    revoked boolean,
    created_at timestamp with time zone,
    updated_at timestamp with time zone,
    parent character varying(255),
    session_id uuid
);


--
-- Name: TABLE refresh_tokens; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.refresh_tokens IS 'Auth: Store of tokens used to refresh JWT tokens once they expire.';


--
-- Name: refresh_tokens_id_seq; Type: SEQUENCE; Schema: auth; Owner: -
--

CREATE SEQUENCE auth.refresh_tokens_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: refresh_tokens_id_seq; Type: SEQUENCE OWNED BY; Schema: auth; Owner: -
--

ALTER SEQUENCE auth.refresh_tokens_id_seq OWNED BY auth.refresh_tokens.id;


--
-- Name: saml_providers; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.saml_providers (
    id uuid NOT NULL,
    sso_provider_id uuid NOT NULL,
    entity_id text NOT NULL,
    metadata_xml text NOT NULL,
    metadata_url text,
    attribute_mapping jsonb,
    created_at timestamp with time zone,
    updated_at timestamp with time zone,
    name_id_format text,
    CONSTRAINT "entity_id not empty" CHECK ((char_length(entity_id) > 0)),
    CONSTRAINT "metadata_url not empty" CHECK (((metadata_url = NULL::text) OR (char_length(metadata_url) > 0))),
    CONSTRAINT "metadata_xml not empty" CHECK ((char_length(metadata_xml) > 0))
);


--
-- Name: TABLE saml_providers; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.saml_providers IS 'Auth: Manages SAML Identity Provider connections.';


--
-- Name: saml_relay_states; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.saml_relay_states (
    id uuid NOT NULL,
    sso_provider_id uuid NOT NULL,
    request_id text NOT NULL,
    for_email text,
    redirect_to text,
    created_at timestamp with time zone,
    updated_at timestamp with time zone,
    flow_state_id uuid,
    CONSTRAINT "request_id not empty" CHECK ((char_length(request_id) > 0))
);


--
-- Name: TABLE saml_relay_states; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.saml_relay_states IS 'Auth: Contains SAML Relay State information for each Service Provider initiated login.';


--
-- Name: schema_migrations; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.schema_migrations (
    version character varying(255) NOT NULL
);


--
-- Name: TABLE schema_migrations; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.schema_migrations IS 'Auth: Manages updates to the auth system.';


--
-- Name: sessions; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.sessions (
    id uuid NOT NULL,
    user_id uuid NOT NULL,
    created_at timestamp with time zone,
    updated_at timestamp with time zone,
    factor_id uuid,
    aal auth.aal_level,
    not_after timestamp with time zone,
    refreshed_at timestamp without time zone,
    user_agent text,
    ip inet,
    tag text,
    oauth_client_id uuid,
    refresh_token_hmac_key text,
    refresh_token_counter bigint,
    scopes text,
    CONSTRAINT sessions_scopes_length CHECK ((char_length(scopes) <= 4096))
);


--
-- Name: TABLE sessions; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.sessions IS 'Auth: Stores session data associated to a user.';


--
-- Name: COLUMN sessions.not_after; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON COLUMN auth.sessions.not_after IS 'Auth: Not after is a nullable column that contains a timestamp after which the session should be regarded as expired.';


--
-- Name: COLUMN sessions.refresh_token_hmac_key; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON COLUMN auth.sessions.refresh_token_hmac_key IS 'Holds a HMAC-SHA256 key used to sign refresh tokens for this session.';


--
-- Name: COLUMN sessions.refresh_token_counter; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON COLUMN auth.sessions.refresh_token_counter IS 'Holds the ID (counter) of the last issued refresh token.';


--
-- Name: sso_domains; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.sso_domains (
    id uuid NOT NULL,
    sso_provider_id uuid NOT NULL,
    domain text NOT NULL,
    created_at timestamp with time zone,
    updated_at timestamp with time zone,
    CONSTRAINT "domain not empty" CHECK ((char_length(domain) > 0))
);


--
-- Name: TABLE sso_domains; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.sso_domains IS 'Auth: Manages SSO email address domain mapping to an SSO Identity Provider.';


--
-- Name: sso_providers; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.sso_providers (
    id uuid NOT NULL,
    resource_id text,
    created_at timestamp with time zone,
    updated_at timestamp with time zone,
    disabled boolean,
    CONSTRAINT "resource_id not empty" CHECK (((resource_id = NULL::text) OR (char_length(resource_id) > 0)))
);


--
-- Name: TABLE sso_providers; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.sso_providers IS 'Auth: Manages SSO identity provider information; see saml_providers for SAML.';


--
-- Name: COLUMN sso_providers.resource_id; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON COLUMN auth.sso_providers.resource_id IS 'Auth: Uniquely identifies a SSO provider according to a user-chosen resource ID (case insensitive), useful in infrastructure as code.';


--
-- Name: users; Type: TABLE; Schema: auth; Owner: -
--

CREATE TABLE auth.users (
    instance_id uuid,
    id uuid NOT NULL,
    aud character varying(255),
    role character varying(255),
    email character varying(255),
    encrypted_password character varying(255),
    email_confirmed_at timestamp with time zone,
    invited_at timestamp with time zone,
    confirmation_token character varying(255),
    confirmation_sent_at timestamp with time zone,
    recovery_token character varying(255),
    recovery_sent_at timestamp with time zone,
    email_change_token_new character varying(255),
    email_change character varying(255),
    email_change_sent_at timestamp with time zone,
    last_sign_in_at timestamp with time zone,
    raw_app_meta_data jsonb,
    raw_user_meta_data jsonb,
    is_super_admin boolean,
    created_at timestamp with time zone,
    updated_at timestamp with time zone,
    phone text DEFAULT NULL::character varying,
    phone_confirmed_at timestamp with time zone,
    phone_change text DEFAULT ''::character varying,
    phone_change_token character varying(255) DEFAULT ''::character varying,
    phone_change_sent_at timestamp with time zone,
    confirmed_at timestamp with time zone GENERATED ALWAYS AS (LEAST(email_confirmed_at, phone_confirmed_at)) STORED,
    email_change_token_current character varying(255) DEFAULT ''::character varying,
    email_change_confirm_status smallint DEFAULT 0,
    banned_until timestamp with time zone,
    reauthentication_token character varying(255) DEFAULT ''::character varying,
    reauthentication_sent_at timestamp with time zone,
    is_sso_user boolean DEFAULT false NOT NULL,
    deleted_at timestamp with time zone,
    is_anonymous boolean DEFAULT false NOT NULL,
    CONSTRAINT users_email_change_confirm_status_check CHECK (((email_change_confirm_status >= 0) AND (email_change_confirm_status <= 2)))
);


--
-- Name: TABLE users; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON TABLE auth.users IS 'Auth: Stores user login data within a secure schema.';


--
-- Name: COLUMN users.is_sso_user; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON COLUMN auth.users.is_sso_user IS 'Auth: Set this column to true when the account comes from SSO. These accounts can have duplicate emails.';


--
-- Name: _db_migrations; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public._db_migrations (
    id integer NOT NULL,
    filename text NOT NULL,
    hash text NOT NULL,
    category text DEFAULT 'migration'::text NOT NULL,
    applied_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: _db_migrations_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public._db_migrations_id_seq
    AS integer
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: _db_migrations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public._db_migrations_id_seq OWNED BY public._db_migrations.id;


--
-- Name: addon_credits; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.addon_credits (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    owner_id uuid,
    addon_type text NOT NULL,
    balance integer DEFAULT 0 NOT NULL,
    total_purchased integer DEFAULT 0 NOT NULL,
    total_consumed integer DEFAULT 0 NOT NULL,
    low_balance_threshold integer DEFAULT 10,
    low_balance_notified boolean DEFAULT false,
    daily_limit integer,
    hourly_limit integer,
    daily_used integer DEFAULT 0,
    hourly_used integer DEFAULT 0,
    daily_reset_at timestamp with time zone,
    hourly_reset_at timestamp with time zone,
    from_number_override text,
    expires_at timestamp with time zone,
    is_active boolean DEFAULT true,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now(),
    CONSTRAINT addon_credits_balance_nonneg_chk CHECK ((balance >= 0)),
    CONSTRAINT addon_credits_consumed_nonneg_chk CHECK ((total_consumed >= 0)),
    CONSTRAINT addon_credits_purchased_nonneg_chk CHECK ((total_purchased >= 0))
);


--
-- Name: TABLE addon_credits; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.addon_credits IS 'Saldo de cr??ditos de add-ons por tenant. Um registro por (tenant_id, addon_type).';


--
-- Name: addon_products; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.addon_products (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    slug text NOT NULL,
    name text NOT NULL,
    description text,
    addon_type text NOT NULL,
    icon text DEFAULT 'pi pi-box'::text,
    credits_amount integer DEFAULT 0,
    price_cents integer DEFAULT 0 NOT NULL,
    currency text DEFAULT 'BRL'::text,
    is_active boolean DEFAULT true,
    is_visible boolean DEFAULT true,
    sort_order integer DEFAULT 0,
    metadata jsonb DEFAULT '{}'::jsonb,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now(),
    deleted_at timestamp with time zone
);


--
-- Name: TABLE addon_products; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.addon_products IS 'Cat??logo de recursos extras vendidos pela plataforma (SMS, servidor, dom??nio, etc.)';


--
-- Name: addon_transactions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.addon_transactions (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    owner_id uuid,
    addon_type text NOT NULL,
    type text NOT NULL,
    amount integer NOT NULL,
    balance_before integer DEFAULT 0 NOT NULL,
    balance_after integer DEFAULT 0 NOT NULL,
    product_id uuid,
    queue_id uuid,
    description text,
    admin_user_id uuid,
    payment_method text,
    payment_reference text,
    price_cents integer,
    currency text DEFAULT 'BRL'::text,
    created_at timestamp with time zone DEFAULT now(),
    metadata jsonb DEFAULT '{}'::jsonb
);


--
-- Name: TABLE addon_transactions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.addon_transactions IS 'Hist??rico de todas as transa????es de cr??ditos: compras, consumo, ajustes, reembolsos.';


--
-- Name: agenda_bloqueios; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.agenda_bloqueios (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid,
    tipo text NOT NULL,
    titulo text NOT NULL,
    data_inicio date NOT NULL,
    data_fim date,
    hora_inicio time without time zone,
    hora_fim time without time zone,
    recorrente boolean DEFAULT false NOT NULL,
    dia_semana smallint,
    observacao text,
    origem text DEFAULT 'manual'::text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT agenda_bloqueios_tipo_check CHECK ((tipo = ANY (ARRAY['feriado_nacional'::text, 'feriado_municipal'::text, 'bloqueio'::text])))
);


--
-- Name: agenda_configuracoes; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.agenda_configuracoes (
    owner_id uuid NOT NULL,
    duracao_padrao_minutos integer DEFAULT 50 NOT NULL,
    intervalo_padrao_minutos integer DEFAULT 0 NOT NULL,
    timezone text DEFAULT 'America/Sao_Paulo'::text NOT NULL,
    usar_horario_admin_custom boolean DEFAULT false NOT NULL,
    admin_inicio_visualizacao time without time zone,
    admin_fim_visualizacao time without time zone,
    admin_slot_visual_minutos integer DEFAULT 30 NOT NULL,
    online_ativo boolean DEFAULT false NOT NULL,
    online_min_antecedencia_horas integer DEFAULT 24 NOT NULL,
    online_max_dias_futuro integer DEFAULT 60 NOT NULL,
    online_cancelar_ate_horas integer DEFAULT 12 NOT NULL,
    online_reagendar_ate_horas integer DEFAULT 12 NOT NULL,
    online_limite_agendamentos_futuros integer DEFAULT 1 NOT NULL,
    online_modo text DEFAULT 'automatico'::text NOT NULL,
    online_buffer_antes_min integer DEFAULT 0 NOT NULL,
    online_buffer_depois_min integer DEFAULT 0 NOT NULL,
    online_modalidade text DEFAULT 'ambos'::text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    usar_granularidade_custom boolean DEFAULT false NOT NULL,
    granularidade_min integer,
    setup_concluido boolean DEFAULT false NOT NULL,
    setup_concluido_em timestamp with time zone,
    agenda_view_mode text DEFAULT 'full_24h'::text NOT NULL,
    agenda_custom_start time without time zone,
    agenda_custom_end time without time zone,
    session_duration_min integer DEFAULT 50 NOT NULL,
    session_break_min integer DEFAULT 10 NOT NULL,
    pausas_semanais jsonb DEFAULT '[]'::jsonb NOT NULL,
    setup_clinica_concluido boolean DEFAULT false NOT NULL,
    setup_clinica_concluido_em timestamp with time zone,
    tenant_id uuid,
    jornada_igual_todos boolean DEFAULT true,
    slot_mode text DEFAULT 'fixed'::text NOT NULL,
    atendimento_mode text DEFAULT 'particular'::text,
    CONSTRAINT agenda_configuracoes_admin_slot_visual_minutos_check CHECK ((admin_slot_visual_minutos = ANY (ARRAY[5, 10, 15, 20, 30, 60]))),
    CONSTRAINT agenda_configuracoes_atendimento_mode_check CHECK (((atendimento_mode IS NULL) OR (atendimento_mode = ANY (ARRAY['particular'::text, 'convenio'::text, 'ambos'::text])))),
    CONSTRAINT agenda_configuracoes_check CHECK (((usar_horario_admin_custom = false) OR ((admin_inicio_visualizacao IS NOT NULL) AND (admin_fim_visualizacao IS NOT NULL) AND (admin_fim_visualizacao > admin_inicio_visualizacao)))),
    CONSTRAINT agenda_configuracoes_duracao_padrao_minutos_check CHECK (((duracao_padrao_minutos >= 10) AND (duracao_padrao_minutos <= 240))),
    CONSTRAINT agenda_configuracoes_granularidade_min_check CHECK (((granularidade_min IS NULL) OR (granularidade_min = ANY (ARRAY[5, 10, 15, 20, 30, 45, 50, 60])))),
    CONSTRAINT agenda_configuracoes_intervalo_padrao_minutos_check CHECK (((intervalo_padrao_minutos >= 0) AND (intervalo_padrao_minutos <= 120))),
    CONSTRAINT agenda_configuracoes_online_buffer_antes_min_check CHECK (((online_buffer_antes_min >= 0) AND (online_buffer_antes_min <= 120))),
    CONSTRAINT agenda_configuracoes_online_buffer_depois_min_check CHECK (((online_buffer_depois_min >= 0) AND (online_buffer_depois_min <= 120))),
    CONSTRAINT agenda_configuracoes_online_cancelar_ate_horas_check CHECK (((online_cancelar_ate_horas >= 0) AND (online_cancelar_ate_horas <= 720))),
    CONSTRAINT agenda_configuracoes_online_limite_agendamentos_futuros_check CHECK (((online_limite_agendamentos_futuros >= 1) AND (online_limite_agendamentos_futuros <= 10))),
    CONSTRAINT agenda_configuracoes_online_max_dias_futuro_check CHECK (((online_max_dias_futuro >= 1) AND (online_max_dias_futuro <= 365))),
    CONSTRAINT agenda_configuracoes_online_min_antecedencia_horas_check CHECK (((online_min_antecedencia_horas >= 0) AND (online_min_antecedencia_horas <= 720))),
    CONSTRAINT agenda_configuracoes_online_modalidade_check CHECK ((online_modalidade = ANY (ARRAY['online'::text, 'presencial'::text, 'ambos'::text]))),
    CONSTRAINT agenda_configuracoes_online_modo_check CHECK ((online_modo = ANY (ARRAY['automatico'::text, 'aprovacao'::text]))),
    CONSTRAINT agenda_configuracoes_online_reagendar_ate_horas_check CHECK (((online_reagendar_ate_horas >= 0) AND (online_reagendar_ate_horas <= 720))),
    CONSTRAINT agenda_configuracoes_slot_mode_chk CHECK ((slot_mode = ANY (ARRAY['fixed'::text, 'dynamic'::text]))),
    CONSTRAINT session_break_min_chk CHECK (((session_break_min >= 0) AND (session_break_min <= 60))),
    CONSTRAINT session_duration_min_chk CHECK (((session_duration_min >= 10) AND (session_duration_min <= 240)))
);


--
-- Name: COLUMN agenda_configuracoes.atendimento_mode; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.agenda_configuracoes.atendimento_mode IS 'Modo de atendimento: particular | convenio | ambos';


--
-- Name: agenda_eventos; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.agenda_eventos (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tipo public.tipo_evento_agenda DEFAULT 'sessao'::public.tipo_evento_agenda NOT NULL,
    status public.status_evento_agenda DEFAULT 'agendado'::public.status_evento_agenda NOT NULL,
    titulo text,
    observacoes text,
    inicio_em timestamp with time zone NOT NULL,
    fim_em timestamp with time zone NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    terapeuta_id uuid,
    tenant_id uuid NOT NULL,
    visibility_scope text DEFAULT 'public'::text NOT NULL,
    mirror_of_event_id uuid,
    mirror_source text,
    patient_id uuid,
    determined_commitment_id uuid,
    link_online text,
    titulo_custom text,
    extra_fields jsonb,
    recurrence_id uuid,
    recurrence_date date,
    modalidade text DEFAULT 'presencial'::text,
    price numeric(10,2),
    billing_contract_id uuid,
    billed boolean DEFAULT false NOT NULL,
    services_customized boolean DEFAULT false NOT NULL,
    insurance_plan_id uuid,
    insurance_guide_number text,
    insurance_value numeric(10,2),
    insurance_plan_service_id uuid,
    CONSTRAINT agenda_eventos_check CHECK ((fim_em > inicio_em))
);


--
-- Name: COLUMN agenda_eventos.price; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.agenda_eventos.price IS 'Valor da sess??o em BRL. Preenchido automaticamente pela tabela professional_pricing do profissional.';


--
-- Name: agenda_excecoes; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.agenda_excecoes (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    data date NOT NULL,
    hora_inicio time without time zone,
    hora_fim time without time zone,
    tipo public.tipo_excecao_agenda NOT NULL,
    motivo text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    status public.status_excecao_agenda DEFAULT 'ativo'::public.status_excecao_agenda NOT NULL,
    fonte text DEFAULT 'manual'::text NOT NULL,
    aplicavel_online boolean DEFAULT true NOT NULL,
    tenant_id uuid NOT NULL,
    CONSTRAINT agenda_excecoes_check CHECK ((((hora_inicio IS NULL) AND (hora_fim IS NULL)) OR ((hora_inicio IS NOT NULL) AND (hora_fim IS NOT NULL) AND (hora_fim > hora_inicio)))),
    CONSTRAINT agenda_excecoes_fonte_check CHECK ((fonte = ANY (ARRAY['manual'::text, 'feriado_google'::text, 'sistema'::text])))
);


--
-- Name: agenda_online_slots; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.agenda_online_slots (
    id bigint NOT NULL,
    owner_id uuid NOT NULL,
    weekday integer NOT NULL,
    "time" time without time zone NOT NULL,
    enabled 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,
    tenant_id uuid NOT NULL,
    CONSTRAINT agenda_online_slots_weekday_check CHECK ((weekday = ANY (ARRAY[0, 1, 2, 3, 4, 5, 6])))
);


--
-- Name: agenda_online_slots_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.agenda_online_slots_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: agenda_online_slots_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.agenda_online_slots_id_seq OWNED BY public.agenda_online_slots.id;


--
-- Name: agenda_regras_semanais; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.agenda_regras_semanais (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    dia_semana smallint NOT NULL,
    hora_inicio time without time zone NOT NULL,
    hora_fim time without time zone NOT NULL,
    modalidade text DEFAULT 'ambos'::text NOT NULL,
    ativo 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,
    tenant_id uuid NOT NULL,
    CONSTRAINT agenda_regras_semanais_check CHECK ((hora_fim > hora_inicio)),
    CONSTRAINT agenda_regras_semanais_dia_semana_check CHECK (((dia_semana >= 0) AND (dia_semana <= 6))),
    CONSTRAINT agenda_regras_semanais_modalidade_check CHECK (((modalidade = ANY (ARRAY['online'::text, 'presencial'::text, 'ambos'::text])) OR (modalidade IS NULL)))
);


--
-- Name: agenda_slots_bloqueados_semanais; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.agenda_slots_bloqueados_semanais (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    dia_semana smallint NOT NULL,
    hora_inicio time without time zone NOT NULL,
    motivo text,
    ativo 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,
    tenant_id uuid NOT NULL,
    CONSTRAINT agenda_slots_bloqueados_semanais_dia_semana_check CHECK (((dia_semana >= 0) AND (dia_semana <= 6)))
);


--
-- Name: agenda_slots_regras; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.agenda_slots_regras (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    dia_semana smallint NOT NULL,
    passo_minutos integer NOT NULL,
    offset_minutos integer DEFAULT 0 NOT NULL,
    buffer_antes_min integer DEFAULT 0 NOT NULL,
    buffer_depois_min integer DEFAULT 0 NOT NULL,
    min_antecedencia_horas integer DEFAULT 0 NOT NULL,
    ativo 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,
    tenant_id uuid NOT NULL,
    CONSTRAINT agenda_slots_regras_buffer_antes_min_check CHECK (((buffer_antes_min >= 0) AND (buffer_antes_min <= 240))),
    CONSTRAINT agenda_slots_regras_buffer_depois_min_check CHECK (((buffer_depois_min >= 0) AND (buffer_depois_min <= 240))),
    CONSTRAINT agenda_slots_regras_dia_semana_check CHECK (((dia_semana >= 0) AND (dia_semana <= 6))),
    CONSTRAINT agenda_slots_regras_min_antecedencia_horas_check CHECK (((min_antecedencia_horas >= 0) AND (min_antecedencia_horas <= 720))),
    CONSTRAINT agenda_slots_regras_offset_minutos_check CHECK (((offset_minutos >= 0) AND (offset_minutos <= 55))),
    CONSTRAINT agenda_slots_regras_passo_minutos_check CHECK (((passo_minutos >= 5) AND (passo_minutos <= 240)))
);


--
-- Name: agendador_configuracoes; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.agendador_configuracoes (
    owner_id uuid NOT NULL,
    tenant_id uuid,
    ativo boolean DEFAULT false NOT NULL,
    link_slug text,
    imagem_fundo_url text,
    imagem_header_url text,
    logomarca_url text,
    cor_primaria text DEFAULT '#4b6bff'::text,
    nome_exibicao text,
    endereco text,
    botao_como_chegar_ativo boolean DEFAULT true NOT NULL,
    maps_url text,
    modo_aprovacao text DEFAULT 'aprovacao'::text NOT NULL,
    modalidade text DEFAULT 'presencial'::text NOT NULL,
    tipos_habilitados jsonb DEFAULT '["primeira", "retorno"]'::jsonb NOT NULL,
    duracao_sessao_min integer DEFAULT 50 NOT NULL,
    antecedencia_minima_horas integer DEFAULT 24 NOT NULL,
    prazo_resposta_horas integer DEFAULT 2 NOT NULL,
    reserva_horas integer DEFAULT 2 NOT NULL,
    pagamento_obrigatorio boolean DEFAULT false NOT NULL,
    pix_chave text,
    pix_countdown_minutos integer DEFAULT 20 NOT NULL,
    triagem_motivo boolean DEFAULT true NOT NULL,
    triagem_como_conheceu boolean DEFAULT false NOT NULL,
    verificacao_email boolean DEFAULT false NOT NULL,
    exigir_aceite_lgpd boolean DEFAULT true NOT NULL,
    mensagem_boas_vindas text,
    texto_como_se_preparar text,
    texto_termos_lgpd text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    pagamento_modo text DEFAULT 'sem_pagamento'::text NOT NULL,
    pagamento_metodos_visiveis text[] DEFAULT '{}'::text[] NOT NULL,
    CONSTRAINT agendador_configuracoes_antecedencia_check CHECK (((antecedencia_minima_horas >= 0) AND (antecedencia_minima_horas <= 720))),
    CONSTRAINT agendador_configuracoes_duracao_check CHECK (((duracao_sessao_min >= 10) AND (duracao_sessao_min <= 240))),
    CONSTRAINT agendador_configuracoes_modalidade_check CHECK ((modalidade = ANY (ARRAY['presencial'::text, 'online'::text, 'ambos'::text]))),
    CONSTRAINT agendador_configuracoes_modo_check CHECK ((modo_aprovacao = ANY (ARRAY['automatico'::text, 'aprovacao'::text]))),
    CONSTRAINT agendador_configuracoes_pix_countdown_check CHECK (((pix_countdown_minutos >= 5) AND (pix_countdown_minutos <= 120))),
    CONSTRAINT agendador_configuracoes_prazo_check CHECK (((prazo_resposta_horas >= 1) AND (prazo_resposta_horas <= 72))),
    CONSTRAINT agendador_configuracoes_reserva_check CHECK (((reserva_horas >= 1) AND (reserva_horas <= 48)))
);


--
-- Name: COLUMN agendador_configuracoes.pagamento_modo; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.agendador_configuracoes.pagamento_modo IS 'sem_pagamento | pagar_na_hora | pix_antecipado';


--
-- Name: COLUMN agendador_configuracoes.pagamento_metodos_visiveis; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.agendador_configuracoes.pagamento_metodos_visiveis IS 'M??todos exibidos ao paciente quando pagamento_modo = pagar_na_hora. Ex: {pix, deposito, dinheiro, cartao, convenio}';


--
-- Name: agendador_solicitacoes; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.agendador_solicitacoes (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid,
    paciente_nome text NOT NULL,
    paciente_sobrenome text,
    paciente_email text NOT NULL,
    paciente_celular text,
    paciente_cpf text,
    tipo text NOT NULL,
    modalidade text NOT NULL,
    data_solicitada date NOT NULL,
    hora_solicitada time without time zone NOT NULL,
    reservado_ate timestamp with time zone,
    motivo text,
    como_conheceu text,
    pix_status text DEFAULT 'pendente'::text,
    pix_pago_em timestamp with time zone,
    status text DEFAULT 'pendente'::text NOT NULL,
    recusado_motivo text,
    autorizado_em timestamp with time zone,
    autorizado_por uuid,
    user_id uuid,
    patient_id uuid,
    evento_id uuid,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT agendador_sol_modalidade_check CHECK ((modalidade = ANY (ARRAY['presencial'::text, 'online'::text]))),
    CONSTRAINT agendador_sol_pix_check CHECK (((pix_status IS NULL) OR (pix_status = ANY (ARRAY['pendente'::text, 'pago'::text, 'expirado'::text])))),
    CONSTRAINT agendador_sol_status_check CHECK ((status = ANY (ARRAY['pendente'::text, 'autorizado'::text, 'recusado'::text, 'expirado'::text, 'convertido'::text]))),
    CONSTRAINT agendador_sol_tipo_check CHECK ((tipo = ANY (ARRAY['primeira'::text, 'retorno'::text, 'reagendar'::text])))
);


--
-- Name: audit_logs; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.audit_logs (
    id bigint NOT NULL,
    tenant_id uuid NOT NULL,
    user_id uuid,
    entity_type text NOT NULL,
    entity_id text,
    action text NOT NULL,
    old_values jsonb,
    new_values jsonb,
    changed_fields text[],
    metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT audit_logs_action_check CHECK ((action = ANY (ARRAY['insert'::text, 'update'::text, 'delete'::text])))
);

ALTER TABLE ONLY public.audit_logs FORCE ROW LEVEL SECURITY;


--
-- Name: TABLE audit_logs; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.audit_logs IS 'Registro imutavel de operacoes de tratamento (LGPD Art. 37). INSERT apenas via trigger SECURITY DEFINER.';


--
-- Name: COLUMN audit_logs.old_values; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.audit_logs.old_values IS 'Estado anterior (jsonb); NULL em INSERT; campos pesados removidos';


--
-- Name: COLUMN audit_logs.new_values; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.audit_logs.new_values IS 'Estado posterior (jsonb); NULL em DELETE; campos pesados removidos';


--
-- Name: COLUMN audit_logs.changed_fields; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.audit_logs.changed_fields IS 'Lista de campos alterados em UPDATE (NULL em INSERT/DELETE)';


--
-- Name: document_access_logs; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.document_access_logs (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    documento_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    acao text NOT NULL,
    user_id uuid,
    ip inet,
    user_agent text,
    acessado_em timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT dal_acao_check CHECK ((acao = ANY (ARRAY['visualizou'::text, 'baixou'::text, 'imprimiu'::text, 'compartilhou'::text, 'assinou'::text])))
);


--
-- Name: TABLE document_access_logs; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.document_access_logs IS 'Log imutavel de acessos a documentos. Conformidade CFP e LGPD. Sem UPDATE/DELETE.';


--
-- Name: COLUMN document_access_logs.acao; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_access_logs.acao IS 'visualizou|baixou|imprimiu|compartilhou|assinou.';


--
-- Name: notification_logs; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.notification_logs (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    owner_id uuid NOT NULL,
    queue_id uuid,
    agenda_evento_id uuid,
    patient_id uuid NOT NULL,
    channel text NOT NULL,
    template_key text NOT NULL,
    schedule_key text,
    recipient_address text NOT NULL,
    resolved_message text,
    resolved_vars jsonb,
    status text NOT NULL,
    provider text,
    provider_message_id text,
    provider_status text,
    provider_response jsonb,
    sent_at timestamp with time zone,
    delivered_at timestamp with time zone,
    read_at timestamp with time zone,
    failed_at timestamp with time zone,
    failure_reason text,
    estimated_cost_brl numeric(8,4) DEFAULT 0,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT notification_logs_status_check CHECK ((status = ANY (ARRAY['sent'::text, 'delivered'::text, 'read'::text, 'failed'::text, 'bounced'::text, 'opted_out'::text])))
);


--
-- Name: patient_status_history; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_status_history (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    patient_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    status_anterior text,
    status_novo text NOT NULL,
    motivo text,
    encaminhado_para text,
    data_saida date,
    alterado_por uuid,
    alterado_em timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT psh_status_novo_check CHECK ((status_novo = ANY (ARRAY['Ativo'::text, 'Inativo'::text, 'Alta'::text, 'Encaminhado'::text, 'Arquivado'::text])))
);


--
-- Name: TABLE patient_status_history; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.patient_status_history IS 'Histórico imutável de todas as mudanças de status do paciente — não editar, apenas inserir';


--
-- Name: audit_log_unified; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.audit_log_unified WITH (security_invoker='true') AS
 SELECT ('audit:'::text || (al.id)::text) AS uid,
    al.tenant_id,
    al.user_id,
    al.entity_type,
    al.entity_id,
    al.action,
        CASE al.action
            WHEN 'insert'::text THEN ('Criou '::text || al.entity_type)
            WHEN 'update'::text THEN (('Alterou '::text || al.entity_type) || COALESCE(((' ('::text || array_to_string(al.changed_fields, ', '::text)) || ')'::text), ''::text))
            WHEN 'delete'::text THEN ('Excluiu '::text || al.entity_type)
            ELSE NULL::text
        END AS description,
    al.created_at AS occurred_at,
    'audit_logs'::text AS source,
    jsonb_build_object('old_values', al.old_values, 'new_values', al.new_values, 'changed_fields', al.changed_fields) AS details
   FROM public.audit_logs al
UNION ALL
 SELECT ('doc_access:'::text || (dal.id)::text) AS uid,
    dal.tenant_id,
    dal.user_id,
    'document'::text AS entity_type,
    (dal.documento_id)::text AS entity_id,
    dal.acao AS action,
        CASE dal.acao
            WHEN 'visualizou'::text THEN 'Visualizou documento'::text
            WHEN 'baixou'::text THEN 'Baixou documento'::text
            WHEN 'imprimiu'::text THEN 'Imprimiu documento'::text
            WHEN 'compartilhou'::text THEN 'Compartilhou documento'::text
            WHEN 'assinou'::text THEN 'Assinou documento'::text
            ELSE dal.acao
        END AS description,
    dal.acessado_em AS occurred_at,
    'document_access_logs'::text AS source,
    jsonb_build_object('ip', (dal.ip)::text, 'user_agent', dal.user_agent) AS details
   FROM public.document_access_logs dal
UNION ALL
 SELECT ('psh:'::text || (psh.id)::text) AS uid,
    psh.tenant_id,
    psh.alterado_por AS user_id,
    'patient_status'::text AS entity_type,
    (psh.patient_id)::text AS entity_id,
    'status_change'::text AS action,
    (((('Status do paciente: '::text || COALESCE(psh.status_anterior, '—'::text)) || ' → '::text) || psh.status_novo) || COALESCE(((' ('::text || psh.motivo) || ')'::text), ''::text)) AS description,
    psh.alterado_em AS occurred_at,
    'patient_status_history'::text AS source,
    jsonb_build_object('status_anterior', psh.status_anterior, 'status_novo', psh.status_novo, 'motivo', psh.motivo, 'encaminhado_para', psh.encaminhado_para, 'data_saida', psh.data_saida) AS details
   FROM public.patient_status_history psh
UNION ALL
 SELECT ('notif:'::text || (nl.id)::text) AS uid,
    nl.tenant_id,
    nl.owner_id AS user_id,
    'notification'::text AS entity_type,
    (nl.patient_id)::text AS entity_id,
    nl.status AS action,
    (((('Notificação '::text || nl.channel) || ' '::text) || nl.status) || COALESCE((' para '::text || nl.recipient_address), ''::text)) AS description,
    nl.created_at AS occurred_at,
    'notification_logs'::text AS source,
    jsonb_build_object('channel', nl.channel, 'template_key', nl.template_key, 'status', nl.status, 'provider', nl.provider, 'failure_reason', nl.failure_reason) AS details
   FROM public.notification_logs nl
UNION ALL
 SELECT ('addon:'::text || (at.id)::text) AS uid,
    at.tenant_id,
    at.admin_user_id AS user_id,
    'addon_transaction'::text AS entity_type,
    (at.id)::text AS entity_id,
    at.type AS action,
        CASE at.type
            WHEN 'purchase'::text THEN ((('Compra de '::text || at.amount) || ' créditos de '::text) || at.addon_type)
            WHEN 'consumption'::text THEN ((('Consumo de '::text || abs(at.amount)) || ' crédito(s) '::text) || at.addon_type)
            WHEN 'adjustment'::text THEN ('Ajuste de créditos '::text || at.addon_type)
            WHEN 'refund'::text THEN ((('Reembolso de '::text || abs(at.amount)) || ' créditos '::text) || at.addon_type)
            ELSE ((at.type || ' '::text) || at.addon_type)
        END AS description,
    at.created_at AS occurred_at,
    'addon_transactions'::text AS source,
    jsonb_build_object('addon_type', at.addon_type, 'amount', at.amount, 'balance_after', at.balance_after, 'price_cents', at.price_cents, 'payment_reference', at.payment_reference) AS details
   FROM public.addon_transactions at;


--
-- Name: VIEW audit_log_unified; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON VIEW public.audit_log_unified IS 'Timeline unificada de eventos auditaveis (LGPD). Herda RLS das tabelas base via security_invoker.';


--
-- Name: audit_logs_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.audit_logs_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: audit_logs_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.audit_logs_id_seq OWNED BY public.audit_logs.id;


--
-- Name: billing_contracts; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.billing_contracts (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    patient_id uuid NOT NULL,
    type text NOT NULL,
    total_sessions integer,
    sessions_used integer DEFAULT 0,
    package_price numeric(10,2),
    amount numeric(10,2),
    billing_interval text,
    active_from timestamp with time zone DEFAULT now(),
    active_to timestamp with time zone,
    status text DEFAULT 'active'::text NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    CONSTRAINT billing_contracts_interval_chk CHECK (((billing_interval IS NULL) OR (billing_interval = ANY (ARRAY['monthly'::text, 'weekly'::text])))),
    CONSTRAINT billing_contracts_sess_used_chk CHECK (((sessions_used IS NULL) OR (sessions_used >= 0))),
    CONSTRAINT billing_contracts_status_chk CHECK ((status = ANY (ARRAY['active'::text, 'completed'::text, 'cancelled'::text]))),
    CONSTRAINT billing_contracts_total_sess_chk CHECK (((total_sessions IS NULL) OR (total_sessions > 0))),
    CONSTRAINT billing_contracts_type_chk CHECK ((type = ANY (ARRAY['per_session'::text, 'package'::text, 'subscription'::text])))
);


--
-- Name: commitment_services; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.commitment_services (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    commitment_id uuid NOT NULL,
    service_id uuid NOT NULL,
    quantity integer DEFAULT 1 NOT NULL,
    unit_price numeric(10,2) NOT NULL,
    discount_pct numeric(5,2) DEFAULT 0,
    discount_flat numeric(10,2) DEFAULT 0,
    final_price numeric(10,2) NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    CONSTRAINT commitment_services_disc_flat_chk CHECK ((discount_flat >= (0)::numeric)),
    CONSTRAINT commitment_services_disc_pct_chk CHECK (((discount_pct >= (0)::numeric) AND (discount_pct <= (100)::numeric))),
    CONSTRAINT commitment_services_final_price_chk CHECK ((final_price >= (0)::numeric)),
    CONSTRAINT commitment_services_quantity_chk CHECK ((quantity > 0))
);


--
-- Name: commitment_time_logs; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.commitment_time_logs (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    commitment_id uuid NOT NULL,
    calendar_event_id uuid,
    source public.commitment_log_source DEFAULT 'manual'::public.commitment_log_source NOT NULL,
    started_at timestamp with time zone NOT NULL,
    ended_at timestamp with time zone NOT NULL,
    minutes integer NOT NULL,
    created_by uuid,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: company_profiles; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.company_profiles (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    nome_fantasia text,
    razao_social text,
    tipo_empresa text,
    cnpj text,
    ie text,
    im text,
    cep text,
    logradouro text,
    numero text,
    complemento text,
    bairro text,
    cidade text,
    estado text,
    email text,
    telefone text,
    site text,
    logo_url text,
    redes_sociais 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
);


--
-- Name: contact_email_types; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.contact_email_types (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid,
    name text NOT NULL,
    slug text NOT NULL,
    icon text,
    is_system boolean DEFAULT false NOT NULL,
    "position" integer DEFAULT 100 NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT contact_email_types_name_check CHECK (((length(name) > 0) AND (length(name) <= 40))),
    CONSTRAINT contact_email_types_slug_check CHECK ((slug ~ '^[a-z0-9_-]{1,40}$'::text))
);


--
-- Name: TABLE contact_email_types; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.contact_email_types IS 'Tipos de email (Principal, Comercial, Pessoal, ...). System (tenant_id NULL) + custom.';


--
-- Name: contact_emails; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.contact_emails (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    entity_type text NOT NULL,
    entity_id uuid NOT NULL,
    contact_email_type_id uuid NOT NULL,
    email text NOT NULL,
    is_primary boolean DEFAULT false NOT NULL,
    notes text,
    "position" integer DEFAULT 100 NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT contact_emails_email_check CHECK ((email ~* '^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$'::text)),
    CONSTRAINT contact_emails_entity_type_check CHECK ((entity_type = ANY (ARRAY['patient'::text, 'medico'::text])))
);


--
-- Name: TABLE contact_emails; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.contact_emails IS 'Emails polimorficos (patients, medicos, ...). Max 1 primary por entidade. Triggers sincronizam campos legados.';


--
-- Name: contact_phones; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.contact_phones (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    entity_type text NOT NULL,
    entity_id uuid NOT NULL,
    contact_type_id uuid NOT NULL,
    number text NOT NULL,
    is_primary boolean DEFAULT false NOT NULL,
    whatsapp_linked_at timestamp with time zone,
    notes text,
    "position" integer DEFAULT 100 NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT contact_phones_entity_type_check CHECK ((entity_type = ANY (ARRAY['patient'::text, 'medico'::text]))),
    CONSTRAINT contact_phones_number_check CHECK ((number ~ '^\d{8,15}$'::text))
);


--
-- Name: TABLE contact_phones; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.contact_phones IS 'Telefones polimorficos (patients, medicos, ...). Max 1 primary por entidade. Triggers sincronizam campos legados.';


--
-- Name: contact_types; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.contact_types (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid,
    name text NOT NULL,
    slug text NOT NULL,
    icon text,
    is_mobile boolean DEFAULT true NOT NULL,
    is_system boolean DEFAULT false NOT NULL,
    "position" integer DEFAULT 100 NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT contact_types_name_check CHECK (((length(name) > 0) AND (length(name) <= 40))),
    CONSTRAINT contact_types_slug_check CHECK ((slug ~ '^[a-z0-9_-]{1,40}$'::text))
);


--
-- Name: TABLE contact_types; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.contact_types IS 'Tipos de contato (Celular, Fixo, WhatsApp, ...). System (tenant_id NULL) visiveis a todos; custom por tenant.';


--
-- Name: conversation_assignments; Type: TABLE; Schema: public; Owner: -
--

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
);


--
-- Name: TABLE conversation_assignments; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_assignments IS 'Atribuicao de threads de conversa a membros do tenant. Uma linha por (tenant_id, thread_key). assigned_to=NULL significa nao atribuida.';


--
-- Name: conversation_autoreply_log; Type: TABLE; Schema: public; Owner: -
--

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
);


--
-- Name: TABLE conversation_autoreply_log; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_autoreply_log IS 'Log de auto-replies enviados. Usado pra respeitar cooldown por thread.';


--
-- Name: conversation_autoreply_log_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.conversation_autoreply_log_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: conversation_autoreply_log_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.conversation_autoreply_log_id_seq OWNED BY public.conversation_autoreply_log.id;


--
-- Name: conversation_autoreply_settings; Type: TABLE; Schema: public; Owner: -
--

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])))
);


--
-- Name: TABLE conversation_autoreply_settings; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_autoreply_settings IS 'Configuracao por tenant do auto-reply fora do horario.';


--
-- Name: conversation_bot_sessions; Type: TABLE; Schema: public; Owner: -
--

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])))
);


--
-- Name: TABLE conversation_bot_sessions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_bot_sessions IS 'Estado do bot por thread. UNIQUE parcial garante 1 ativa por (tenant, thread).';


--
-- Name: conversation_bots; Type: TABLE; Schema: public; Owner: -
--

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])))
);


--
-- Name: TABLE conversation_bots; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_bots IS 'Config do bot de triagem WhatsApp por tenant. steps contem array de perguntas.';


--
-- Name: conversation_messages; Type: TABLE; Schema: public; Owner: -
--

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])))
);

ALTER TABLE ONLY public.conversation_messages REPLICA IDENTITY FULL;

ALTER TABLE ONLY public.conversation_messages FORCE ROW LEVEL SECURITY;


--
-- Name: TABLE conversation_messages; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_messages IS 'Mensagens in/out de WhatsApp/SMS/email. Timeline de conversas do tenant com pacientes.';


--
-- Name: conversation_messages_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.conversation_messages_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: conversation_messages_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.conversation_messages_id_seq OWNED BY public.conversation_messages.id;


--
-- Name: conversation_notes; Type: TABLE; Schema: public; Owner: -
--

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)))
);


--
-- Name: TABLE conversation_notes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_notes IS 'Notas internas por thread de conversa. Visiveis apenas aos membros do tenant; nao enviadas ao paciente.';


--
-- Name: conversation_optout_keywords; Type: TABLE; Schema: public; Owner: -
--

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)))
);


--
-- Name: TABLE conversation_optout_keywords; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_optout_keywords IS 'Palavras-chave que disparam opt-out quando paciente envia. Sistema (tenant_id NULL) + custom do tenant.';


--
-- Name: conversation_optouts; Type: TABLE; Schema: public; Owner: -
--

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])))
);


--
-- Name: TABLE conversation_optouts; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_optouts IS 'Numeros que pediram pra nao receber mensagens automaticas. LGPD Art. 18 Sec.2.';


--
-- Name: conversation_sla_breaches; Type: TABLE; Schema: public; Owner: -
--

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
);


--
-- Name: TABLE conversation_sla_breaches; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_sla_breaches IS 'Estouros de SLA detectados pelo cron. Max 1 aberto por thread (UNIQUE parcial). Resolve automatico via trigger outbound.';


--
-- Name: conversation_sla_rules; Type: TABLE; Schema: public; Owner: -
--

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)))
);


--
-- Name: TABLE conversation_sla_rules; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_sla_rules IS 'Configuracao de SLA por tenant. 1 linha por tenant. Threshold global.';


--
-- Name: conversation_tags; Type: TABLE; Schema: public; Owner: -
--

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))
);


--
-- Name: TABLE conversation_tags; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_tags IS 'Definicoes de tags aplicaveis a threads. tenant_id NULL = tag do sistema (todos veem).';


--
-- Name: conversation_thread_tags; Type: TABLE; Schema: public; Owner: -
--

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
);


--
-- Name: TABLE conversation_thread_tags; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.conversation_thread_tags IS 'Join de tags aplicadas a cada thread de conversa.';


--
-- Name: patients; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patients (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    nome_completo text NOT NULL,
    email_principal text,
    telefone text,
    created_at timestamp with time zone DEFAULT now(),
    owner_id uuid,
    avatar_url text,
    status text DEFAULT 'Ativo'::text,
    last_attended_at timestamp with time zone,
    is_native boolean DEFAULT false,
    naturalidade text,
    data_nascimento date,
    rg text,
    cpf text,
    identification_color text,
    genero text,
    estado_civil text,
    email_alternativo text,
    pais text DEFAULT 'Brasil'::text,
    cep text,
    cidade text,
    estado text,
    endereco text,
    numero text,
    bairro text,
    complemento text,
    escolaridade text,
    profissao text,
    nome_parente text,
    grau_parentesco text,
    telefone_alternativo text,
    onde_nos_conheceu text,
    encaminhado_por text,
    nome_responsavel text,
    telefone_responsavel text,
    cpf_responsavel text,
    observacao_responsavel text,
    cobranca_no_responsavel boolean DEFAULT false,
    observacoes text,
    notas_internas text,
    updated_at timestamp with time zone DEFAULT now(),
    telefone_parente text,
    tenant_id uuid NOT NULL,
    responsible_member_id uuid NOT NULL,
    user_id uuid,
    patient_scope text DEFAULT 'clinic'::text NOT NULL,
    therapist_member_id uuid,
    nome_social text,
    pronomes text,
    etnia text,
    religiao text,
    faixa_renda text,
    canal_preferido text DEFAULT 'whatsapp'::text,
    horario_contato_inicio time without time zone DEFAULT '08:00:00'::time without time zone,
    horario_contato_fim time without time zone DEFAULT '20:00:00'::time without time zone,
    idioma text DEFAULT 'pt-BR'::text,
    origem text,
    metodo_pagamento_preferido text,
    motivo_saida text,
    data_saida date,
    encaminhado_para text,
    risco_elevado boolean DEFAULT false NOT NULL,
    risco_nota text,
    risco_sinalizado_em timestamp with time zone,
    risco_sinalizado_por uuid,
    horario_contato text,
    convenio text,
    convenio_id uuid,
    CONSTRAINT cpf_responsavel_format_check CHECK (((cpf_responsavel IS NULL) OR (cpf_responsavel ~ '^\d{11}$'::text))),
    CONSTRAINT patients_cpf_format_check CHECK (((cpf IS NULL) OR (cpf ~ '^\d{11}$'::text))),
    CONSTRAINT patients_faixa_renda_check CHECK (((faixa_renda IS NULL) OR (faixa_renda = ANY (ARRAY['ate_1sm'::text, '1_3sm'::text, '3_6sm'::text, '6_10sm'::text, 'acima_10sm'::text, 'nao_informado'::text])))),
    CONSTRAINT patients_metodo_pagamento_check CHECK (((metodo_pagamento_preferido IS NULL) OR (metodo_pagamento_preferido = ANY (ARRAY['pix'::text, 'cartao'::text, 'dinheiro'::text, 'deposito'::text, 'convenio'::text])))),
    CONSTRAINT patients_risco_consistency_check CHECK (((risco_elevado = false) OR ((risco_elevado = true) AND (risco_nota IS NOT NULL) AND (risco_sinalizado_por IS NOT NULL)))),
    CONSTRAINT patients_status_check CHECK ((status = ANY (ARRAY['Ativo'::text, 'Em espera'::text, 'Inativo'::text, 'Alta'::text, 'Encaminhado'::text, 'Arquivado'::text])))
);


--
-- Name: COLUMN patients.avatar_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.avatar_url IS 'URL p??blica da imagem de avatar armazenada no Supabase Storage';


--
-- Name: COLUMN patients.nome_social; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.nome_social IS 'Nome social / como prefere ser chamado(a) no atendimento.';


--
-- Name: COLUMN patients.pronomes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.pronomes IS 'Pronomes de tratamento. Ex: ela/dela, ele/dele. Exibido no header do perfil.';


--
-- Name: COLUMN patients.etnia; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.etnia IS 'Etnia / raça autodeclarada. Exibida no card "Dados pessoais".';


--
-- Name: COLUMN patients.religiao; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.religiao IS 'Religião ou espiritualidade (opcional, relevante clinicamente)';


--
-- Name: COLUMN patients.faixa_renda; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.faixa_renda IS 'Faixa de renda em salários mínimos — usado para precificação solidária';


--
-- Name: COLUMN patients.canal_preferido; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.canal_preferido IS 'Canal preferido de contato. Ex: WhatsApp, Telefone, E-mail.';


--
-- Name: COLUMN patients.horario_contato_inicio; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.horario_contato_inicio IS 'Início da janela de horário preferida para contato';


--
-- Name: COLUMN patients.horario_contato_fim; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.horario_contato_fim IS 'Fim da janela de horário preferida para contato';


--
-- Name: COLUMN patients.origem; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.origem IS 'Como o paciente chegou: indicacao, agendador, redes_sociais, encaminhamento, outro';


--
-- Name: COLUMN patients.metodo_pagamento_preferido; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.metodo_pagamento_preferido IS 'Método de pagamento preferido. Ex: PIX, Cartão crédito. Exibido no card Origem.';


--
-- Name: COLUMN patients.motivo_saida; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.motivo_saida IS 'Motivo de encerramento do acompanhamento. Exibido no card Origem quando preenchido.';


--
-- Name: COLUMN patients.data_saida; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.data_saida IS 'Data em que o paciente foi desligado/encaminhado';


--
-- Name: COLUMN patients.encaminhado_para; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.encaminhado_para IS 'Nome ou serviço para onde o paciente foi encaminhado';


--
-- Name: COLUMN patients.risco_elevado; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.risco_elevado IS 'Flag de atenção clínica — exibe alerta no topo do cadastro e prontuário';


--
-- Name: COLUMN patients.risco_nota; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.risco_nota IS 'Descrição do risco (obrigatória quando risco_elevado = true)';


--
-- Name: COLUMN patients.risco_sinalizado_em; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.risco_sinalizado_em IS 'Timestamp em que o risco foi sinalizado';


--
-- Name: COLUMN patients.risco_sinalizado_por; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.risco_sinalizado_por IS 'Usuário que sinalizou o risco';


--
-- Name: COLUMN patients.horario_contato; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.horario_contato IS 'Horário preferido para contato. Ex: 08h–18h.';


--
-- Name: COLUMN patients.convenio; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.convenio IS 'Nome do convênio para exibição (badge azul no header). Derivado de convenio_id.';


--
-- Name: COLUMN patients.convenio_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patients.convenio_id IS 'FK para insurance_plans.id. Vincula o paciente ao convênio cadastrado.';


--
-- Name: conversation_threads; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.conversation_threads WITH (security_invoker='true') AS
 WITH base AS (
         SELECT cm.id,
            cm.tenant_id,
            cm.patient_id,
            cm.channel,
            cm.body,
            cm.direction,
            cm.kanban_status,
            cm.read_at,
            cm.created_at,
                CASE
                    WHEN (cm.direction = 'inbound'::text) THEN cm.from_number
                    ELSE cm.to_number
                END AS contact_number,
            COALESCE((cm.patient_id)::text, ('anon:'::text || COALESCE(
                CASE
                    WHEN (cm.direction = 'inbound'::text) THEN cm.from_number
                    ELSE cm.to_number
                END, 'unknown'::text))) AS thread_key
           FROM public.conversation_messages cm
        ), latest AS (
         SELECT DISTINCT ON (base.tenant_id, base.thread_key) base.tenant_id,
            base.thread_key,
            base.patient_id,
            base.channel,
            base.contact_number,
            base.body AS last_message_body,
            base.direction AS last_message_direction,
            base.kanban_status,
            base.created_at AS last_message_at
           FROM base
          ORDER BY base.tenant_id, base.thread_key, base.created_at DESC
        ), counts AS (
         SELECT base.tenant_id,
            base.thread_key,
            count(*) AS message_count,
            count(*) FILTER (WHERE ((base.direction = 'inbound'::text) AND (base.read_at IS NULL))) AS unread_count
           FROM base
          GROUP BY base.tenant_id, base.thread_key
        )
 SELECT l.tenant_id,
    l.thread_key,
    l.patient_id,
    p.nome_completo AS patient_name,
    l.contact_number,
    l.channel,
    c.message_count,
    c.unread_count,
    l.last_message_at,
    l.last_message_body,
    l.last_message_direction,
    l.kanban_status,
    ca.assigned_to,
    ca.assigned_at
   FROM (((latest l
     JOIN counts c ON (((c.tenant_id = l.tenant_id) AND (c.thread_key = l.thread_key))))
     LEFT JOIN public.patients p ON ((p.id = l.patient_id)))
     LEFT JOIN public.conversation_assignments ca ON (((ca.tenant_id = l.tenant_id) AND (ca.thread_key = l.thread_key))));


--
-- Name: VIEW conversation_threads; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON VIEW public.conversation_threads IS 'Agregado de conversas por paciente ou numero anonimo. Base do Kanban. Inclui assignment atual do thread.';


--
-- Name: current_tenant_id; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.current_tenant_id AS
 SELECT current_setting('request.jwt.claim.tenant_id'::text, true) AS current_setting;


--
-- Name: determined_commitment_fields; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.determined_commitment_fields (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    commitment_id uuid NOT NULL,
    key text NOT NULL,
    label text NOT NULL,
    field_type public.determined_field_type DEFAULT 'text'::public.determined_field_type NOT NULL,
    required boolean DEFAULT false NOT NULL,
    sort_order 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
);


--
-- Name: determined_commitments; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.determined_commitments (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    created_by uuid,
    is_native boolean DEFAULT false NOT NULL,
    native_key text,
    is_locked boolean DEFAULT false NOT NULL,
    active boolean DEFAULT true NOT NULL,
    name text NOT NULL,
    description text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    bg_color text,
    text_color text
);


--
-- Name: dev_auditoria_items; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_auditoria_items (
    id bigint NOT NULL,
    categoria character varying(120),
    titulo text NOT NULL,
    descricao_problema text,
    solucao text,
    severidade character varying(20),
    status character varying(20) DEFAULT 'aberto'::character varying NOT NULL,
    resolvido_em date,
    sessao_resolucao character varying(160),
    arquivo_afetado text,
    tags text[] DEFAULT '{}'::text[],
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    ordem integer DEFAULT 0 NOT NULL,
    CONSTRAINT dev_auditoria_items_severidade_check CHECK (((severidade IS NULL) OR ((severidade)::text = ANY ((ARRAY['critico'::character varying, 'alto'::character varying, 'medio'::character varying, 'baixo'::character varying])::text[])))),
    CONSTRAINT dev_auditoria_items_status_check CHECK (((status)::text = ANY ((ARRAY['aberto'::character varying, 'em_analise'::character varying, 'resolvido'::character varying, 'wontfix'::character varying, 'duplicado'::character varying])::text[])))
);


--
-- Name: TABLE dev_auditoria_items; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.dev_auditoria_items IS 'Bugs, dívidas técnicas e decisões arquiteturais.';


--
-- Name: dev_auditoria_items_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.dev_auditoria_items_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: dev_auditoria_items_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.dev_auditoria_items_id_seq OWNED BY public.dev_auditoria_items.id;


--
-- Name: dev_comparison_competitor_status; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_comparison_competitor_status (
    id bigint NOT NULL,
    comparison_id bigint NOT NULL,
    competitor_id bigint NOT NULL,
    status character varying(20) DEFAULT 'a_definir'::character varying NOT NULL,
    nota text,
    fonte character varying(20),
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT dev_comparison_competitor_status_fonte_check CHECK (((fonte IS NULL) OR ((fonte)::text = ANY ((ARRAY['fetched'::character varying, 'observacao'::character varying, 'publico'::character varying, 'hipotese'::character varying])::text[])))),
    CONSTRAINT dev_comparison_competitor_status_status_check CHECK (((status)::text = ANY ((ARRAY['tem'::character varying, 'parcial'::character varying, 'gap'::character varying, 'na'::character varying, 'a_definir'::character varying])::text[])))
);


--
-- Name: TABLE dev_comparison_competitor_status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.dev_comparison_competitor_status IS 'Qual concorrente tem qual feature (ponte N-N com matrix).';


--
-- Name: dev_comparison_competitor_status_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.dev_comparison_competitor_status_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: dev_comparison_competitor_status_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.dev_comparison_competitor_status_id_seq OWNED BY public.dev_comparison_competitor_status.id;


--
-- Name: dev_comparison_matrix; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_comparison_matrix (
    id bigint NOT NULL,
    dominio character varying(120),
    feature text NOT NULL,
    nosso_status character varying(20) DEFAULT 'a_definir'::character varying NOT NULL,
    nossa_nota text,
    importancia character varying(20),
    ordem 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 dev_comparison_matrix_importancia_check CHECK (((importancia IS NULL) OR ((importancia)::text = ANY ((ARRAY['alta'::character varying, 'media'::character varying, 'baixa'::character varying])::text[])))),
    CONSTRAINT dev_comparison_matrix_nosso_status_check CHECK (((nosso_status)::text = ANY ((ARRAY['tem'::character varying, 'parcial'::character varying, 'gap'::character varying, 'na'::character varying, 'a_definir'::character varying])::text[])))
);


--
-- Name: TABLE dev_comparison_matrix; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.dev_comparison_matrix IS 'Matriz de comparação AgenciaPsi × features esperadas do mercado.';


--
-- Name: dev_comparison_matrix_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.dev_comparison_matrix_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: dev_comparison_matrix_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.dev_comparison_matrix_id_seq OWNED BY public.dev_comparison_matrix.id;


--
-- Name: dev_competitor_features; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_competitor_features (
    id bigint NOT NULL,
    competitor_id bigint NOT NULL,
    categoria character varying(120),
    nome text NOT NULL,
    descricao text,
    fonte character varying(20) DEFAULT 'publico'::character varying NOT NULL,
    fonte_url text,
    data_fonte date,
    destaque 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,
    ordem integer DEFAULT 0 NOT NULL,
    CONSTRAINT dev_competitor_features_fonte_check CHECK (((fonte)::text = ANY ((ARRAY['fetched'::character varying, 'observacao'::character varying, 'publico'::character varying, 'hipotese'::character varying])::text[])))
);


--
-- Name: TABLE dev_competitor_features; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.dev_competitor_features IS 'Features catalogadas de cada concorrente.';


--
-- Name: dev_competitor_features_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.dev_competitor_features_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: dev_competitor_features_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.dev_competitor_features_id_seq OWNED BY public.dev_competitor_features.id;


--
-- Name: dev_competitors; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_competitors (
    id bigint NOT NULL,
    slug character varying(80) NOT NULL,
    nome character varying(160) NOT NULL,
    pais character varying(40),
    foco character varying(160),
    pricing text,
    posicionamento text,
    url text,
    ultima_pesquisa date,
    notas text,
    ativo boolean DEFAULT true NOT NULL,
    ordem 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
);


--
-- Name: TABLE dev_competitors; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.dev_competitors IS 'Concorrentes analisados no benchmark.';


--
-- Name: dev_competitors_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.dev_competitors_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: dev_competitors_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.dev_competitors_id_seq OWNED BY public.dev_competitors.id;


--
-- Name: dev_generation_log; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_generation_log (
    id bigint NOT NULL,
    tipo character varying(40) NOT NULL,
    comando text,
    sucesso boolean DEFAULT false NOT NULL,
    stdout text,
    stderr text,
    duration_ms integer,
    metadata jsonb DEFAULT '{}'::jsonb,
    trigger_user_id uuid,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE dev_generation_log; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.dev_generation_log IS 'Histórico de execuções (backup, dashboard, export, seed, etc).';


--
-- Name: dev_generation_log_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.dev_generation_log_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: dev_generation_log_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.dev_generation_log_id_seq OWNED BY public.dev_generation_log.id;


--
-- Name: dev_roadmap_items; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_roadmap_items (
    id bigint NOT NULL,
    phase_id bigint NOT NULL,
    numero integer,
    bloco character varying(160),
    feature text NOT NULL,
    descricao text,
    esforco character varying(4),
    prioridade character varying(20),
    status character varying(20) DEFAULT 'pendente'::character varying NOT NULL,
    notas text,
    assignee character varying(120),
    data_inicio date,
    data_conclusao date,
    ordem integer DEFAULT 0 NOT NULL,
    tags text[] DEFAULT '{}'::text[],
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT dev_roadmap_items_esforco_check CHECK (((esforco IS NULL) OR ((esforco)::text = ANY ((ARRAY['S'::character varying, 'M'::character varying, 'L'::character varying, 'XL'::character varying])::text[])))),
    CONSTRAINT dev_roadmap_items_prioridade_check CHECK (((prioridade IS NULL) OR ((prioridade)::text = ANY ((ARRAY['bloqueador'::character varying, 'alta'::character varying, 'media'::character varying, 'diferencial'::character varying])::text[])))),
    CONSTRAINT dev_roadmap_items_status_check CHECK (((status)::text = ANY ((ARRAY['pendente'::character varying, 'em_andamento'::character varying, 'concluido'::character varying, 'cancelado'::character varying, 'bloqueado'::character varying])::text[])))
);


--
-- Name: TABLE dev_roadmap_items; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.dev_roadmap_items IS 'Itens de cada fase do roadmap.';


--
-- Name: dev_roadmap_items_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.dev_roadmap_items_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: dev_roadmap_items_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.dev_roadmap_items_id_seq OWNED BY public.dev_roadmap_items.id;


--
-- Name: dev_roadmap_phases; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_roadmap_phases (
    id bigint NOT NULL,
    numero integer NOT NULL,
    nome character varying(160) NOT NULL,
    objetivo text,
    timeline_sugerida character varying(160),
    criterio_saida text,
    status character varying(20) DEFAULT 'planejada'::character varying NOT NULL,
    data_inicio date,
    data_fim date,
    ordem 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 dev_roadmap_phases_status_check CHECK (((status)::text = ANY ((ARRAY['planejada'::character varying, 'em_andamento'::character varying, 'concluida'::character varying, 'arquivada'::character varying])::text[])))
);


--
-- Name: TABLE dev_roadmap_phases; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.dev_roadmap_phases IS 'Fases do roadmap (MVP, Paridade, Diferenciação). Visível só pra saas_admins.';


--
-- Name: dev_roadmap_phases_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.dev_roadmap_phases_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: dev_roadmap_phases_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.dev_roadmap_phases_id_seq OWNED BY public.dev_roadmap_phases.id;


--
-- Name: dev_test_items; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_test_items (
    id bigint NOT NULL,
    area character varying(80) NOT NULL,
    categoria character varying(120),
    titulo text NOT NULL,
    arquivo text,
    descricao text,
    total_tests integer DEFAULT 0,
    passing integer DEFAULT 0,
    failing integer DEFAULT 0,
    skipped integer DEFAULT 0,
    cobertura_pct numeric(5,2),
    status character varying(20) DEFAULT 'ok'::character varying NOT NULL,
    last_run_at timestamp with time zone,
    sessao_criacao character varying(160),
    notas text,
    tags text[] DEFAULT '{}'::text[],
    ordem 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 dev_test_items_status_check CHECK (((status)::text = ANY ((ARRAY['ok'::character varying, 'falhando'::character varying, 'pendente'::character varying, 'obsoleto'::character varying, 'a_escrever'::character varying])::text[])))
);


--
-- Name: TABLE dev_test_items; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.dev_test_items IS 'Catálogo de suítes de teste por área. Responde "o que está testado?" sem precisar rodar npm test.';


--
-- Name: dev_test_items_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.dev_test_items_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: dev_test_items_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.dev_test_items_id_seq OWNED BY public.dev_test_items.id;


--
-- Name: dev_user_credentials; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_user_credentials (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    user_id uuid,
    email text NOT NULL,
    password_dev text NOT NULL,
    kind text DEFAULT 'custom'::text NOT NULL,
    note text,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: dev_verificacoes_items; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.dev_verificacoes_items (
    id bigint NOT NULL,
    area character varying(80) NOT NULL,
    categoria character varying(120),
    titulo text NOT NULL,
    descricao text,
    resultado text,
    acao_sugerida text,
    severidade character varying(20),
    status character varying(20) DEFAULT 'pendente'::character varying NOT NULL,
    verificado_em date,
    sessao_verificacao character varying(160),
    arquivo_afetado text,
    auditoria_item_id bigint,
    tags text[] DEFAULT '{}'::text[],
    ordem 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 dev_verificacoes_items_severidade_check CHECK (((severidade IS NULL) OR ((severidade)::text = ANY ((ARRAY['critico'::character varying, 'alto'::character varying, 'medio'::character varying, 'baixo'::character varying])::text[])))),
    CONSTRAINT dev_verificacoes_items_status_check CHECK (((status)::text = ANY ((ARRAY['pendente'::character varying, 'verificando'::character varying, 'ok'::character varying, 'problema'::character varying, 'corrigido'::character varying, 'wontfix'::character varying])::text[])))
);


--
-- Name: TABLE dev_verificacoes_items; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.dev_verificacoes_items IS 'Revisão sênior por área/sessão — o que foi verificado e o que foi encontrado.';


--
-- Name: COLUMN dev_verificacoes_items.area; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.dev_verificacoes_items.area IS 'Domínio revisado: auth, router, agenda, financeiro, pacientes, comunicacao, etc.';


--
-- Name: COLUMN dev_verificacoes_items.auditoria_item_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.dev_verificacoes_items.auditoria_item_id IS 'Link opcional: se a verificação virou um bug em dev_auditoria_items.';


--
-- Name: dev_verificacoes_items_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.dev_verificacoes_items_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: dev_verificacoes_items_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.dev_verificacoes_items_id_seq OWNED BY public.dev_verificacoes_items.id;


--
-- Name: document_generated; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.document_generated (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    template_id uuid NOT NULL,
    patient_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    dados_preenchidos jsonb DEFAULT '{}'::jsonb NOT NULL,
    pdf_path text NOT NULL,
    storage_bucket text DEFAULT 'generated-docs'::text NOT NULL,
    documento_id uuid,
    gerado_por uuid NOT NULL,
    gerado_em timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE document_generated; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.document_generated IS 'Registro de cada documento PDF gerado a partir de um template.';


--
-- Name: COLUMN document_generated.template_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_generated.template_id IS 'Template usado para gerar o documento.';


--
-- Name: COLUMN document_generated.dados_preenchidos; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_generated.dados_preenchidos IS 'Snapshot JSON dos dados usados no preenchimento. Permite auditoria futura.';


--
-- Name: COLUMN document_generated.pdf_path; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_generated.pdf_path IS 'Caminho do PDF gerado no Supabase Storage bucket.';


--
-- Name: COLUMN document_generated.documento_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_generated.documento_id IS 'FK opcional para documents — se o PDF gerado tambem foi registrado como documento do paciente.';


--
-- Name: COLUMN document_generated.gerado_por; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_generated.gerado_por IS 'Usuario que gerou o documento (auth.uid()).';


--
-- Name: document_share_links; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.document_share_links (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    documento_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    token text DEFAULT encode(extensions.gen_random_bytes(32), 'hex'::text) NOT NULL,
    expira_em timestamp with time zone NOT NULL,
    usos_max smallint DEFAULT 5 NOT NULL,
    usos smallint DEFAULT 0 NOT NULL,
    criado_por uuid NOT NULL,
    criado_em timestamp with time zone DEFAULT now(),
    ativo boolean DEFAULT true NOT NULL
);


--
-- Name: TABLE document_share_links; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.document_share_links IS 'Links temporarios assinados para compartilhar documento com profissional externo.';


--
-- Name: COLUMN document_share_links.token; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_share_links.token IS 'Token unico gerado automaticamente (32 bytes hex).';


--
-- Name: COLUMN document_share_links.expira_em; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_share_links.expira_em IS 'Data/hora de expiracao do link.';


--
-- Name: COLUMN document_share_links.usos_max; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_share_links.usos_max IS 'Numero maximo de acessos permitidos.';


--
-- Name: COLUMN document_share_links.usos; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_share_links.usos IS 'Numero de vezes que o link ja foi acessado.';


--
-- Name: document_signatures; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.document_signatures (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    documento_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    signatario_tipo text NOT NULL,
    signatario_id uuid,
    signatario_nome text,
    signatario_email text,
    ordem smallint DEFAULT 1 NOT NULL,
    status text DEFAULT 'pendente'::text NOT NULL,
    ip inet,
    user_agent text,
    assinado_em timestamp with time zone,
    hash_documento text,
    criado_em timestamp with time zone DEFAULT now(),
    atualizado_em timestamp with time zone DEFAULT now(),
    CONSTRAINT ds_signatario_tipo_check CHECK ((signatario_tipo = ANY (ARRAY['paciente'::text, 'responsavel_legal'::text, 'terapeuta'::text]))),
    CONSTRAINT ds_status_check CHECK ((status = ANY (ARRAY['pendente'::text, 'enviado'::text, 'assinado'::text, 'recusado'::text, 'expirado'::text])))
);


--
-- Name: TABLE document_signatures; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.document_signatures IS 'Assinaturas eletronicas de documentos. Cada signatario tem seu registro.';


--
-- Name: COLUMN document_signatures.signatario_tipo; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_signatures.signatario_tipo IS 'paciente|responsavel_legal|terapeuta.';


--
-- Name: COLUMN document_signatures.status; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_signatures.status IS 'pendente|enviado|assinado|recusado|expirado.';


--
-- Name: COLUMN document_signatures.ip; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_signatures.ip IS 'IP do signatario no momento da assinatura.';


--
-- Name: COLUMN document_signatures.hash_documento; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_signatures.hash_documento IS 'Hash SHA-256 do documento no momento da assinatura. Garante integridade.';


--
-- Name: document_templates; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.document_templates (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid,
    owner_id uuid,
    nome_template text NOT NULL,
    tipo text DEFAULT 'outro'::text NOT NULL,
    descricao text,
    corpo_html text DEFAULT ''::text NOT NULL,
    cabecalho_html text,
    rodape_html text,
    variaveis text[] DEFAULT '{}'::text[],
    logo_url text,
    is_global boolean DEFAULT false NOT NULL,
    ativo boolean DEFAULT true NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now(),
    CONSTRAINT dt_tipo_check CHECK ((tipo = ANY (ARRAY['declaracao_comparecimento'::text, 'atestado_psicologico'::text, 'relatorio_acompanhamento'::text, 'recibo_pagamento'::text, 'termo_consentimento'::text, 'encaminhamento'::text, 'contrato_servicos'::text, 'tcle'::text, 'autorizacao_menor'::text, 'laudo_psicologico'::text, 'parecer_psicologico'::text, 'termo_sigilo'::text, 'declaracao_inicio_tratamento'::text, 'termo_alta'::text, 'tcle_online'::text, 'outro'::text])))
);


--
-- Name: TABLE document_templates; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.document_templates IS 'Templates de documentos para geracao automatica (declaracao, atestado, recibo etc.).';


--
-- Name: COLUMN document_templates.nome_template; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_templates.nome_template IS 'Nome do template. Ex: Declaracao de Comparecimento.';


--
-- Name: COLUMN document_templates.tipo; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_templates.tipo IS 'declaracao_comparecimento|atestado_psicologico|relatorio_acompanhamento|recibo_pagamento|termo_consentimento|encaminhamento|outro.';


--
-- Name: COLUMN document_templates.corpo_html; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_templates.corpo_html IS 'Corpo do template em HTML com variaveis {{nome_variavel}}.';


--
-- Name: COLUMN document_templates.cabecalho_html; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_templates.cabecalho_html IS 'HTML do cabecalho (logo, nome da clinica etc.).';


--
-- Name: COLUMN document_templates.rodape_html; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_templates.rodape_html IS 'HTML do rodape (CRP, endereco, contato etc.).';


--
-- Name: COLUMN document_templates.variaveis; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_templates.variaveis IS 'Array com nomes das variaveis usadas no template. Ex: {paciente_nome, data_sessao}.';


--
-- Name: COLUMN document_templates.logo_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_templates.logo_url IS 'URL do logo personalizado para o cabecalho do documento.';


--
-- Name: COLUMN document_templates.is_global; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.document_templates.is_global IS 'true = template padrao do sistema visivel para todos. false = template do tenant.';


--
-- Name: documents; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.documents (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    patient_id uuid NOT NULL,
    bucket_path text NOT NULL,
    storage_bucket text DEFAULT 'documents'::text NOT NULL,
    nome_original text NOT NULL,
    mime_type text,
    tamanho_bytes bigint,
    tipo_documento text DEFAULT 'outro'::text NOT NULL,
    categoria text,
    descricao text,
    tags text[] DEFAULT '{}'::text[],
    agenda_evento_id uuid,
    session_note_id uuid,
    visibilidade text DEFAULT 'privado'::text NOT NULL,
    compartilhado_portal boolean DEFAULT false NOT NULL,
    compartilhado_supervisor boolean DEFAULT false NOT NULL,
    compartilhado_em timestamp with time zone,
    expira_compartilhamento timestamp with time zone,
    enviado_pelo_paciente boolean DEFAULT false NOT NULL,
    status_revisao text DEFAULT 'aprovado'::text,
    revisado_por uuid,
    revisado_em timestamp with time zone,
    uploaded_by uuid NOT NULL,
    uploaded_at timestamp with time zone DEFAULT now() NOT NULL,
    deleted_at timestamp with time zone,
    deleted_by uuid,
    retencao_ate timestamp with time zone,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now(),
    content_sha256 text,
    CONSTRAINT documents_status_revisao_check CHECK ((status_revisao = ANY (ARRAY['pendente'::text, 'aprovado'::text, 'rejeitado'::text]))),
    CONSTRAINT documents_tipo_check CHECK ((tipo_documento = ANY (ARRAY['laudo'::text, 'receita'::text, 'exame'::text, 'termo_assinado'::text, 'relatorio_externo'::text, 'identidade'::text, 'convenio'::text, 'declaracao'::text, 'atestado'::text, 'recibo'::text, 'outro'::text]))),
    CONSTRAINT documents_visibilidade_check CHECK ((visibilidade = ANY (ARRAY['privado'::text, 'compartilhado_supervisor'::text, 'compartilhado_portal'::text])))
);


--
-- Name: TABLE documents; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.documents IS 'Documentos e arquivos vinculados a pacientes. Armazenados no Supabase Storage.';


--
-- Name: COLUMN documents.owner_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.owner_id IS 'Terapeuta dono do documento (auth.uid()).';


--
-- Name: COLUMN documents.tenant_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.tenant_id IS 'Tenant do terapeuta.';


--
-- Name: COLUMN documents.patient_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.patient_id IS 'Paciente ao qual o documento pertence.';


--
-- Name: COLUMN documents.bucket_path; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.bucket_path IS 'Caminho do arquivo no Supabase Storage bucket.';


--
-- Name: COLUMN documents.storage_bucket; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.storage_bucket IS 'Nome do bucket no Storage. Default: documents.';


--
-- Name: COLUMN documents.nome_original; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.nome_original IS 'Nome original do arquivo enviado.';


--
-- Name: COLUMN documents.mime_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.mime_type IS 'MIME type do arquivo. Ex: application/pdf, image/jpeg.';


--
-- Name: COLUMN documents.tamanho_bytes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.tamanho_bytes IS 'Tamanho do arquivo em bytes.';


--
-- Name: COLUMN documents.tipo_documento; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.tipo_documento IS 'Tipo: laudo|receita|exame|termo_assinado|relatorio_externo|identidade|convenio|declaracao|atestado|recibo|outro.';


--
-- Name: COLUMN documents.categoria; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.categoria IS 'Categoria livre para organizacao adicional.';


--
-- Name: COLUMN documents.tags; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.tags IS 'Tags livres para busca e filtro. Array de text.';


--
-- Name: COLUMN documents.visibilidade; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.visibilidade IS 'privado|compartilhado_supervisor|compartilhado_portal.';


--
-- Name: COLUMN documents.compartilhado_portal; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.compartilhado_portal IS 'true = visivel para o paciente no portal.';


--
-- Name: COLUMN documents.compartilhado_supervisor; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.compartilhado_supervisor IS 'true = visivel para o supervisor.';


--
-- Name: COLUMN documents.enviado_pelo_paciente; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.enviado_pelo_paciente IS 'true = upload feito pelo paciente via portal.';


--
-- Name: COLUMN documents.status_revisao; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.status_revisao IS 'pendente|aprovado|rejeitado — para uploads do paciente.';


--
-- Name: COLUMN documents.deleted_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.deleted_at IS 'Soft delete: data da exclusao. NULL = ativo.';


--
-- Name: COLUMN documents.retencao_ate; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.retencao_ate IS 'LGPD/CFP: arquivo retido ate esta data mesmo apos soft delete.';


--
-- Name: COLUMN documents.content_sha256; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.documents.content_sha256 IS 'V#51: SHA-256 hex (64 chars) do conteúdo no momento do upload. Permite verificar integridade. NULL pra documentos legados pré-V#51.';


--
-- Name: email_layout_config; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.email_layout_config (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    header_config jsonb DEFAULT '{"layout": null, "content": "", "enabled": false}'::jsonb NOT NULL,
    footer_config jsonb DEFAULT '{"layout": null, "content": "", "enabled": false}'::jsonb NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: email_templates_global; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.email_templates_global (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    key text NOT NULL,
    domain text NOT NULL,
    channel text DEFAULT 'email'::text NOT NULL,
    subject text NOT NULL,
    body_html text NOT NULL,
    body_text text,
    version integer DEFAULT 1 NOT NULL,
    is_active boolean DEFAULT true NOT NULL,
    variables jsonb,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: email_templates_tenant; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.email_templates_tenant (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    owner_id uuid,
    template_key text NOT NULL,
    subject text,
    body_html text,
    body_text text,
    enabled boolean DEFAULT true NOT NULL,
    synced_version integer,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: entitlements_invalidation; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.entitlements_invalidation (
    owner_id uuid NOT NULL,
    changed_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: features; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.features (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    key text NOT NULL,
    description text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    descricao text DEFAULT ''::text NOT NULL,
    name text DEFAULT ''::text NOT NULL,
    is_active boolean DEFAULT true NOT NULL
);


--
-- Name: COLUMN features.descricao; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.features.descricao IS 'Descri????o humana da feature (exibi????o no admin e documenta????o).';


--
-- Name: COLUMN features.is_active; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.features.is_active IS 'V#40: false = feature depreciada, escondida no catálogo SaaS mas continua válida em planos/tenants existentes.';


--
-- Name: feriados; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.feriados (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid,
    owner_id uuid,
    tipo text DEFAULT 'municipal'::text NOT NULL,
    nome text NOT NULL,
    data date NOT NULL,
    cidade text,
    estado text,
    observacao text,
    bloqueia_sessoes boolean DEFAULT false NOT NULL,
    criado_em timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT feriados_tipo_check CHECK ((tipo = ANY (ARRAY['municipal'::text, 'personalizado'::text])))
);


--
-- Name: financial_categories; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.financial_categories (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    user_id uuid NOT NULL,
    name text NOT NULL,
    type public.financial_record_type DEFAULT 'receita'::public.financial_record_type NOT NULL,
    color text DEFAULT '#6366f1'::text,
    icon text DEFAULT 'pi pi-tag'::text,
    sort_order integer DEFAULT 0,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: financial_exceptions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.financial_exceptions (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid,
    tenant_id uuid NOT NULL,
    exception_type text NOT NULL,
    charge_mode text NOT NULL,
    charge_value numeric(10,2),
    charge_pct numeric(5,2),
    min_hours_notice integer,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now(),
    CONSTRAINT financial_exceptions_charge_chk CHECK ((charge_mode = ANY (ARRAY['none'::text, 'full'::text, 'fixed_fee'::text, 'percentage'::text]))),
    CONSTRAINT financial_exceptions_type_chk CHECK ((exception_type = ANY (ARRAY['patient_no_show'::text, 'patient_cancellation'::text, 'professional_cancellation'::text])))
);


--
-- Name: global_notices; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.global_notices (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    title text,
    message text DEFAULT ''::text NOT NULL,
    variant text DEFAULT 'info'::text NOT NULL,
    roles text[] DEFAULT '{}'::text[] NOT NULL,
    contexts text[] DEFAULT '{}'::text[] NOT NULL,
    starts_at timestamp with time zone,
    ends_at timestamp with time zone,
    is_active boolean DEFAULT true NOT NULL,
    priority integer DEFAULT 0 NOT NULL,
    dismissible boolean DEFAULT true NOT NULL,
    persist_dismiss boolean DEFAULT true NOT NULL,
    dismiss_scope text DEFAULT 'device'::text NOT NULL,
    show_once boolean DEFAULT false NOT NULL,
    max_views integer,
    cooldown_minutes integer,
    version integer DEFAULT 1 NOT NULL,
    action_type text DEFAULT 'none'::text NOT NULL,
    action_label text,
    action_url text,
    action_route text,
    views_count integer DEFAULT 0 NOT NULL,
    clicks_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,
    created_by uuid,
    content_align text DEFAULT 'left'::text NOT NULL,
    link_target text DEFAULT '_blank'::text NOT NULL,
    CONSTRAINT global_notices_action_type_check CHECK ((action_type = ANY (ARRAY['none'::text, 'internal'::text, 'external'::text]))),
    CONSTRAINT global_notices_content_align_check CHECK ((content_align = ANY (ARRAY['left'::text, 'center'::text, 'right'::text, 'justify'::text]))),
    CONSTRAINT global_notices_dismiss_scope_check CHECK ((dismiss_scope = ANY (ARRAY['session'::text, 'device'::text, 'user'::text]))),
    CONSTRAINT global_notices_link_target_check CHECK ((link_target = ANY (ARRAY['_blank'::text, '_self'::text, '_parent'::text, '_top'::text]))),
    CONSTRAINT global_notices_variant_check CHECK ((variant = ANY (ARRAY['info'::text, 'success'::text, 'warning'::text, 'error'::text])))
);


--
-- Name: insurance_plan_services; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.insurance_plan_services (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    insurance_plan_id uuid NOT NULL,
    name text NOT NULL,
    value numeric(10,2) NOT NULL,
    active boolean DEFAULT true NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: insurance_plans; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.insurance_plans (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    name text NOT NULL,
    notes text,
    default_value numeric(10,2),
    active boolean DEFAULT true NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: login_carousel_slides; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.login_carousel_slides (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    title text NOT NULL,
    body text NOT NULL,
    icon text DEFAULT 'pi-star'::text NOT NULL,
    ordem integer DEFAULT 0 NOT NULL,
    ativo boolean DEFAULT true NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: math_challenges; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.math_challenges (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    question text NOT NULL,
    answer integer NOT NULL,
    used boolean DEFAULT false NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    expires_at timestamp with time zone DEFAULT (now() + '00:05:00'::interval) NOT NULL
);


--
-- Name: TABLE math_challenges; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.math_challenges IS 'Challenges de math captcha. TTL 5min. Escrita/leitura apenas via RPC.';


--
-- Name: medicos; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.medicos (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    nome text NOT NULL,
    crm text,
    especialidade text,
    telefone_profissional text,
    telefone_pessoal text,
    email text,
    clinica text,
    cidade text,
    estado text DEFAULT 'SP'::text,
    observacoes text,
    ativo boolean DEFAULT true NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: TABLE medicos; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.medicos IS 'Médicos e profissionais de referência cadastrados pelo terapeuta.';


--
-- Name: COLUMN medicos.owner_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.owner_id IS 'Terapeuta dono do cadastro (auth.uid()).';


--
-- Name: COLUMN medicos.tenant_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.tenant_id IS 'Tenant do terapeuta.';


--
-- Name: COLUMN medicos.nome; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.nome IS 'Nome completo do médico/profissional.';


--
-- Name: COLUMN medicos.crm; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.crm IS 'CRM com UF. Ex: 123456/SP. Único por owner_id.';


--
-- Name: COLUMN medicos.especialidade; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.especialidade IS 'Especialidade médica. Ex: Psiquiatria, Neurologia.';


--
-- Name: COLUMN medicos.telefone_profissional; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.telefone_profissional IS 'Telefone do consultório ou clínica.';


--
-- Name: COLUMN medicos.telefone_pessoal; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.telefone_pessoal IS 'Telefone pessoal / WhatsApp. Campo sensível.';


--
-- Name: COLUMN medicos.email; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.email IS 'E-mail profissional.';


--
-- Name: COLUMN medicos.clinica; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.clinica IS 'Nome da clínica ou hospital onde atua.';


--
-- Name: COLUMN medicos.cidade; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.cidade IS 'Cidade de atuação.';


--
-- Name: COLUMN medicos.estado; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.estado IS 'UF de atuação. Default SP.';


--
-- Name: COLUMN medicos.observacoes; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.observacoes IS 'Notas internas do terapeuta sobre o médico.';


--
-- Name: COLUMN medicos.ativo; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.medicos.ativo IS 'Soft delete: false oculta da listagem.';


--
-- Name: module_features; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.module_features (
    module_id uuid NOT NULL,
    feature_id uuid NOT NULL,
    enabled boolean DEFAULT true NOT NULL,
    limits jsonb,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: modules; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.modules (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    key text NOT NULL,
    name text NOT NULL,
    description text,
    is_active boolean DEFAULT true NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: notice_dismissals; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.notice_dismissals (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    notice_id uuid NOT NULL,
    user_id uuid NOT NULL,
    version integer DEFAULT 1 NOT NULL,
    dismissed_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: notification_channels; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.notification_channels (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    owner_id uuid NOT NULL,
    channel text NOT NULL,
    provider text NOT NULL,
    is_active boolean DEFAULT false NOT NULL,
    display_name text,
    sender_address text,
    credentials jsonb DEFAULT '{}'::jsonb NOT NULL,
    connection_status text DEFAULT 'disconnected'::text,
    last_health_check timestamp with time zone,
    metadata jsonb DEFAULT '{}'::jsonb,
    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,
    twilio_subaccount_sid text,
    twilio_phone_number text,
    twilio_phone_sid text,
    webhook_url text,
    cost_per_message_usd numeric(8,6) DEFAULT 0,
    price_per_message_brl numeric(8,4) DEFAULT 0,
    provisioned_at timestamp with time zone,
    CONSTRAINT notification_channels_channel_check CHECK ((channel = ANY (ARRAY['whatsapp'::text, 'email'::text, 'sms'::text]))),
    CONSTRAINT notification_channels_connection_status_check CHECK ((connection_status = ANY (ARRAY['connected'::text, 'disconnected'::text, 'connecting'::text, 'qr_pending'::text, 'error'::text]))),
    CONSTRAINT notification_channels_provider_check CHECK ((provider = ANY (ARRAY['evolution_api'::text, 'meta_official'::text, 'twilio'::text, 'zenvia'::text, 'sendgrid'::text, 'resend'::text, 'smtp'::text, 'zapi'::text])))
);


--
-- Name: COLUMN notification_channels.twilio_subaccount_sid; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.notification_channels.twilio_subaccount_sid IS 'SID da subconta Twilio criada para este tenant';


--
-- Name: COLUMN notification_channels.twilio_phone_number; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.notification_channels.twilio_phone_number IS 'Número WhatsApp provisionado (E.164, ex: +5511999990000)';


--
-- Name: COLUMN notification_channels.twilio_phone_sid; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.notification_channels.twilio_phone_sid IS 'SID do número de telefone na subconta Twilio';


--
-- Name: COLUMN notification_channels.webhook_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.notification_channels.webhook_url IS 'URL do webhook configurada na Twilio para receber callbacks de status';


--
-- Name: COLUMN notification_channels.cost_per_message_usd; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.notification_channels.cost_per_message_usd IS 'Custo real Twilio por mensagem WhatsApp (USD)';


--
-- Name: COLUMN notification_channels.price_per_message_brl; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.notification_channels.price_per_message_brl IS 'Valor cobrado do tenant por mensagem (BRL, inclui margem SaaS)';


--
-- Name: COLUMN notification_channels.provisioned_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.notification_channels.provisioned_at IS 'Timestamp do provisionamento da subconta';


--
-- Name: notification_preferences; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.notification_preferences (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    owner_id uuid NOT NULL,
    patient_id uuid NOT NULL,
    whatsapp_opt_in boolean DEFAULT true NOT NULL,
    email_opt_in boolean DEFAULT true NOT NULL,
    sms_opt_in boolean DEFAULT false NOT NULL,
    preferred_time_start time without time zone DEFAULT '08:00:00'::time without time zone,
    preferred_time_end time without time zone DEFAULT '20:00:00'::time without time zone,
    lgpd_consent_given boolean DEFAULT false NOT NULL,
    lgpd_consent_date timestamp with time zone,
    lgpd_consent_version text,
    lgpd_consent_ip inet,
    lgpd_opt_out_date timestamp with time zone,
    lgpd_opt_out_reason text,
    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
);


--
-- Name: notification_queue; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.notification_queue (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    owner_id uuid NOT NULL,
    agenda_evento_id uuid,
    patient_id uuid NOT NULL,
    channel text NOT NULL,
    template_key text NOT NULL,
    schedule_key text NOT NULL,
    resolved_vars jsonb DEFAULT '{}'::jsonb NOT NULL,
    recipient_address text NOT NULL,
    status text DEFAULT 'pendente'::text NOT NULL,
    scheduled_at timestamp with time zone NOT NULL,
    sent_at timestamp with time zone,
    next_retry_at timestamp with time zone,
    attempts integer DEFAULT 0 NOT NULL,
    max_attempts integer DEFAULT 5 NOT NULL,
    last_error text,
    idempotency_key text NOT NULL,
    provider_message_id text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT notification_queue_channel_check CHECK ((channel = ANY (ARRAY['whatsapp'::text, 'email'::text, 'sms'::text]))),
    CONSTRAINT notification_queue_status_check CHECK ((status = ANY (ARRAY['pendente'::text, 'processando'::text, 'enviado'::text, 'falhou'::text, 'cancelado'::text, 'ignorado'::text])))
);


--
-- Name: notification_schedules; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.notification_schedules (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    owner_id uuid NOT NULL,
    schedule_key text NOT NULL,
    event_type text NOT NULL,
    trigger_type text NOT NULL,
    offset_minutes integer DEFAULT 0,
    whatsapp_enabled boolean DEFAULT true NOT NULL,
    email_enabled boolean DEFAULT true NOT NULL,
    sms_enabled boolean DEFAULT false NOT NULL,
    allowed_time_start time without time zone DEFAULT '08:00:00'::time without time zone,
    allowed_time_end time without time zone DEFAULT '20:00:00'::time without time zone,
    skip_weekends boolean DEFAULT false,
    skip_holidays boolean DEFAULT false,
    is_active boolean DEFAULT true NOT NULL,
    sort_order integer DEFAULT 0,
    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 notification_schedules_event_type_check CHECK ((event_type = ANY (ARRAY['lembrete_sessao'::text, 'confirmacao_sessao'::text, 'cancelamento_sessao'::text, 'reagendamento'::text, 'cobranca_pendente'::text, 'boas_vindas_paciente'::text]))),
    CONSTRAINT notification_schedules_trigger_type_check CHECK ((trigger_type = ANY (ARRAY['before_event'::text, 'after_event'::text, 'immediate'::text])))
);


--
-- Name: notification_templates; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.notification_templates (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid,
    owner_id uuid,
    key text NOT NULL,
    domain text NOT NULL,
    channel text NOT NULL,
    event_type text NOT NULL,
    body_text text NOT NULL,
    meta_template_name text,
    meta_template_namespace text,
    meta_components jsonb,
    meta_status text DEFAULT 'draft'::text,
    variables jsonb DEFAULT '[]'::jsonb,
    version integer DEFAULT 1 NOT NULL,
    is_active boolean DEFAULT true NOT NULL,
    is_default 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,
    deleted_at timestamp with time zone,
    CONSTRAINT notification_templates_channel_check CHECK ((channel = ANY (ARRAY['whatsapp'::text, 'sms'::text]))),
    CONSTRAINT notification_templates_domain_check CHECK ((domain = ANY (ARRAY['session'::text, 'intake'::text, 'billing'::text, 'system'::text]))),
    CONSTRAINT notification_templates_event_type_check CHECK ((event_type = ANY (ARRAY['lembrete_sessao'::text, 'confirmacao_sessao'::text, 'cancelamento_sessao'::text, 'reagendamento'::text, 'cobranca_pendente'::text, 'boas_vindas_paciente'::text, 'intake_recebido'::text, 'intake_aprovado'::text, 'intake_rejeitado'::text]))),
    CONSTRAINT notification_templates_meta_status_check CHECK ((meta_status = ANY (ARRAY['draft'::text, 'pending_approval'::text, 'approved'::text, 'rejected'::text])))
);


--
-- Name: notifications; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.notifications (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid,
    type text NOT NULL,
    ref_id uuid,
    ref_table text,
    payload jsonb DEFAULT '{}'::jsonb NOT NULL,
    read_at timestamp with time zone,
    archived boolean DEFAULT false NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT notifications_type_check CHECK ((type = ANY (ARRAY['new_scheduling'::text, 'new_patient'::text, 'recurrence_alert'::text, 'session_status'::text, 'inbound_message'::text, 'system_alert'::text])))
);


--
-- Name: plan_features; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.plan_features (
    plan_id uuid NOT NULL,
    feature_id uuid NOT NULL,
    enabled boolean DEFAULT true NOT NULL,
    limits jsonb,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: tenant_modules; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.tenant_modules (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    module_id uuid NOT NULL,
    status text DEFAULT 'active'::text NOT NULL,
    settings jsonb,
    provider text DEFAULT 'manual'::text NOT NULL,
    provider_item_id text,
    installed_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: owner_feature_entitlements; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.owner_feature_entitlements AS
 WITH base AS (
         SELECT s.user_id AS owner_id,
            f.key AS feature_key,
            pf.limits,
            'plan'::text AS source
           FROM ((public.subscriptions s
             JOIN public.plan_features pf ON (((pf.plan_id = s.plan_id) AND (pf.enabled = true))))
             JOIN public.features f ON ((f.id = pf.feature_id)))
          WHERE ((s.status = 'active'::text) AND (s.user_id IS NOT NULL))
        UNION ALL
         SELECT tm.owner_id,
            f.key AS feature_key,
            mf.limits,
            'module'::text AS source
           FROM (((public.tenant_modules tm
             JOIN public.modules m ON (((m.id = tm.module_id) AND (m.is_active = true))))
             JOIN public.module_features mf ON (((mf.module_id = m.id) AND (mf.enabled = true))))
             JOIN public.features f ON ((f.id = mf.feature_id)))
          WHERE ((tm.status = 'active'::text) AND (tm.owner_id IS NOT NULL))
        )
 SELECT owner_id,
    feature_key,
    array_agg(DISTINCT source) AS sources,
    jsonb_agg(limits) FILTER (WHERE (limits IS NOT NULL)) AS limits_list
   FROM base
  GROUP BY owner_id, feature_key;


--
-- Name: owner_users; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.owner_users (
    owner_id uuid NOT NULL,
    user_id uuid NOT NULL,
    role text DEFAULT 'admin'::text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: patient_contacts; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_contacts (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    patient_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    nome text NOT NULL,
    tipo text NOT NULL,
    relacao text,
    telefone text,
    email text,
    cpf text,
    especialidade text,
    registro_profissional text,
    is_primario boolean DEFAULT false NOT NULL,
    ativo 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 patient_contacts_tipo_check CHECK ((tipo = ANY (ARRAY['emergencia'::text, 'responsavel_legal'::text, 'profissional_saude'::text, 'outro'::text])))
);


--
-- Name: TABLE patient_contacts; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.patient_contacts IS 'Contatos vinculados ao paciente: emergência, responsável legal, outros profissionais de saúde';


--
-- Name: COLUMN patient_contacts.tipo; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patient_contacts.tipo IS 'Categoria do contato: emergencia | responsavel_legal | profissional_saude | outro';


--
-- Name: COLUMN patient_contacts.is_primario; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patient_contacts.is_primario IS 'Contato de emergência principal — exibido em destaque no cadastro';


--
-- Name: patient_discounts; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_discounts (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    patient_id uuid NOT NULL,
    discount_pct numeric(5,2) DEFAULT 0,
    discount_flat numeric(10,2) DEFAULT 0,
    reason text,
    active boolean DEFAULT true NOT NULL,
    active_from timestamp with time zone DEFAULT now(),
    active_to timestamp with time zone,
    created_at timestamp with time zone DEFAULT now()
);


--
-- Name: patient_group_patient; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_group_patient (
    patient_group_id uuid NOT NULL,
    patient_id uuid NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    tenant_id uuid NOT NULL
);


--
-- Name: patient_groups; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_groups (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    nome text NOT NULL,
    descricao text,
    cor text,
    is_active boolean DEFAULT true NOT NULL,
    is_system boolean DEFAULT false NOT NULL,
    owner_id uuid DEFAULT auth.uid() NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    therapist_id uuid,
    tenant_id uuid NOT NULL
);


--
-- Name: patient_intake_requests; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_intake_requests (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    token text NOT NULL,
    consent boolean DEFAULT false NOT NULL,
    status text DEFAULT 'new'::text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    converted_patient_id uuid,
    rejected_reason text,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    cpf text,
    rg text,
    cep text,
    nome_completo text,
    email_principal text,
    telefone text,
    pais text,
    cidade text,
    estado text,
    endereco text,
    numero text,
    bairro text,
    complemento text,
    data_nascimento date,
    naturalidade text,
    genero text,
    estado_civil text,
    onde_nos_conheceu text,
    encaminhado_por text,
    observacoes text,
    notas_internas text,
    email_alternativo text,
    telefone_alternativo text,
    profissao text,
    escolaridade text,
    nacionalidade text,
    avatar_url text,
    tenant_id uuid,
    last_progress_at timestamp with time zone,
    lead_thread_key text,
    CONSTRAINT chk_intakes_status CHECK ((status = ANY (ARRAY['new'::text, 'in_progress'::text, 'converted'::text, 'rejected'::text, 'abandoned_lead'::text])))
);


--
-- Name: COLUMN patient_intake_requests.last_progress_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patient_intake_requests.last_progress_at IS 'Ultima vez que o form salvou dados parciais. Cron usa pra detectar abandonados.';


--
-- Name: COLUMN patient_intake_requests.lead_thread_key; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patient_intake_requests.lead_thread_key IS 'Thread key da conversa criada no CRM quando vira lead abandonado.';


--
-- Name: patient_invite_attempts; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_invite_attempts (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    token text NOT NULL,
    ok boolean NOT NULL,
    error_code text,
    error_msg text,
    client_info text,
    owner_id uuid,
    tenant_id uuid,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE patient_invite_attempts; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.patient_invite_attempts IS 'Log de tentativas (ok e falhas) de submit do form público de cadastro externo. Base para monitoramento de flood/tentativas maliciosas. Sem IP direto — proteção LGPD.';


--
-- Name: COLUMN patient_invite_attempts.client_info; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patient_invite_attempts.client_info IS 'User-agent enviado pelo cliente (opcional). Limitado a 500 chars no insert. Não contém PII.';


--
-- Name: patient_invites; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_invites (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    token text NOT NULL,
    active boolean DEFAULT true NOT NULL,
    expires_at timestamp with time zone,
    max_uses integer,
    uses integer DEFAULT 0 NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    tenant_id uuid
);


--
-- Name: patient_patient_tag; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_patient_tag (
    owner_id uuid NOT NULL,
    patient_id uuid NOT NULL,
    tag_id uuid NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    tenant_id uuid NOT NULL
);


--
-- Name: patient_support_contacts; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_support_contacts (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    patient_id uuid NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    nome text,
    relacao text,
    tipo text,
    telefone text,
    email text,
    is_primario boolean DEFAULT false NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: TABLE patient_support_contacts; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.patient_support_contacts IS 'Rede de suporte do paciente. Exibida no card "Contatos & rede de suporte" do perfil.';


--
-- Name: COLUMN patient_support_contacts.tipo; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patient_support_contacts.tipo IS 'emergencia | familiar | profissional_saude | amigo | outro';


--
-- Name: COLUMN patient_support_contacts.is_primario; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patient_support_contacts.is_primario IS 'true = badge vermelho "emergência" no perfil do paciente.';


--
-- Name: patient_tags; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_tags (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    nome text NOT NULL,
    cor text,
    is_padrao boolean DEFAULT false NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone,
    tenant_id uuid NOT NULL
);


--
-- Name: patient_timeline; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_timeline (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    patient_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    evento_tipo text NOT NULL,
    titulo text NOT NULL,
    descricao text,
    icone_cor text DEFAULT 'gray'::text,
    link_ref_tipo text,
    link_ref_id uuid,
    gerado_por uuid,
    ocorrido_em timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT pt_evento_tipo_check CHECK ((evento_tipo = ANY (ARRAY['primeira_sessao'::text, 'sessao_realizada'::text, 'sessao_cancelada'::text, 'falta'::text, 'status_alterado'::text, 'risco_sinalizado'::text, 'risco_removido'::text, 'documento_assinado'::text, 'documento_adicionado'::text, 'escala_respondida'::text, 'escala_enviada'::text, 'pagamento_vencido'::text, 'pagamento_recebido'::text, 'tarefa_combinada'::text, 'contato_adicionado'::text, 'prontuario_editado'::text, 'nota_adicionada'::text, 'manual'::text]))),
    CONSTRAINT pt_icone_cor_check CHECK ((icone_cor = ANY (ARRAY['green'::text, 'blue'::text, 'amber'::text, 'red'::text, 'gray'::text, 'purple'::text])))
);


--
-- Name: TABLE patient_timeline; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.patient_timeline IS 'Feed cronológico de eventos do paciente — alimentado por triggers e inserções manuais';


--
-- Name: COLUMN patient_timeline.link_ref_tipo; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patient_timeline.link_ref_tipo IS 'Tipo da entidade referenciada (polimórfico): agenda_evento | financial_record | documento | escala';


--
-- Name: COLUMN patient_timeline.link_ref_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.patient_timeline.link_ref_id IS 'ID da entidade referenciada — sem FK formal para suportar múltiplos tipos';


--
-- Name: payment_settings; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.payment_settings (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid,
    pix_ativo boolean DEFAULT false NOT NULL,
    pix_tipo text DEFAULT 'cpf'::text NOT NULL,
    pix_chave text DEFAULT ''::text NOT NULL,
    pix_nome_titular text DEFAULT ''::text NOT NULL,
    deposito_ativo boolean DEFAULT false NOT NULL,
    deposito_banco text DEFAULT ''::text NOT NULL,
    deposito_agencia text DEFAULT ''::text NOT NULL,
    deposito_conta text DEFAULT ''::text NOT NULL,
    deposito_tipo_conta text DEFAULT 'corrente'::text NOT NULL,
    deposito_titular text DEFAULT ''::text NOT NULL,
    deposito_cpf_cnpj text DEFAULT ''::text NOT NULL,
    dinheiro_ativo boolean DEFAULT false NOT NULL,
    cartao_ativo boolean DEFAULT false NOT NULL,
    cartao_instrucao text DEFAULT ''::text NOT NULL,
    convenio_ativo boolean DEFAULT false NOT NULL,
    convenio_lista text DEFAULT ''::text NOT NULL,
    observacoes_pagamento text DEFAULT ''::text NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: plan_prices; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.plan_prices (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    plan_id uuid NOT NULL,
    currency text DEFAULT 'BRL'::text NOT NULL,
    "interval" text NOT NULL,
    amount_cents integer NOT NULL,
    is_active boolean DEFAULT true NOT NULL,
    active_from timestamp with time zone DEFAULT now() NOT NULL,
    active_to timestamp with time zone,
    source text DEFAULT 'manual'::text NOT NULL,
    provider text,
    provider_price_id text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT plan_prices_amount_cents_check CHECK ((amount_cents >= 0)),
    CONSTRAINT plan_prices_interval_check CHECK (("interval" = ANY (ARRAY['month'::text, 'year'::text])))
);


--
-- Name: TABLE plan_prices; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.plan_prices IS 'Hist??rico de pre??os por plano (fonte: manual/gateway).';


--
-- Name: plan_public; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.plan_public (
    plan_id uuid NOT NULL,
    public_name text DEFAULT ''::text NOT NULL,
    public_description text DEFAULT ''::text NOT NULL,
    badge text,
    is_featured boolean DEFAULT false NOT NULL,
    is_visible boolean DEFAULT true NOT NULL,
    sort_order 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
);


--
-- Name: TABLE plan_public; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.plan_public IS 'Configura????o de vitrine (p??gina p??blica) dos planos.';


--
-- Name: plan_public_bullets; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.plan_public_bullets (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    plan_id uuid NOT NULL,
    text text NOT NULL,
    sort_order integer DEFAULT 0 NOT NULL,
    highlight boolean DEFAULT false NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: plans; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.plans (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    key text NOT NULL,
    name text NOT NULL,
    description text,
    is_active boolean DEFAULT true NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    price_cents integer DEFAULT 0 NOT NULL,
    currency text DEFAULT 'BRL'::text NOT NULL,
    billing_interval text DEFAULT 'month'::text NOT NULL,
    target text,
    max_supervisees integer,
    CONSTRAINT plans_target_check CHECK ((target = ANY (ARRAY['patient'::text, 'therapist'::text, 'clinic'::text, 'supervisor'::text])))
);


--
-- Name: COLUMN plans.name; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.plans.name IS 'Nome interno do plano (admin). A key ?? t??cnica/imut??vel.';


--
-- Name: COLUMN plans.target; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.plans.target IS 'P??blico-alvo do plano: patient, therapist ou clinic.';


--
-- Name: COLUMN plans.max_supervisees; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.plans.max_supervisees IS 'Limite de terapeutas que podem ser supervisionados. Apenas para planos target=supervisor. NULL = sem limite.';


--
-- Name: professional_pricing; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.professional_pricing (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    determined_commitment_id uuid,
    price numeric(10,2) NOT NULL,
    notes text,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: TABLE professional_pricing; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.professional_pricing IS 'DEPRECATED: substitu??da por public.services. Manter at?? pr??xima release de limpeza.';


--
-- Name: profiles; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.profiles (
    id uuid NOT NULL,
    role text DEFAULT 'tenant_member'::text NOT NULL,
    full_name text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    avatar_url text,
    phone text,
    bio text,
    language text DEFAULT 'pt-BR'::text NOT NULL,
    timezone text DEFAULT 'America/Sao_Paulo'::text NOT NULL,
    notify_system_email boolean DEFAULT true NOT NULL,
    notify_reminders boolean DEFAULT true NOT NULL,
    notify_news boolean DEFAULT false NOT NULL,
    account_type text DEFAULT 'free'::text NOT NULL,
    platform_roles text[] DEFAULT '{}'::text[] NOT NULL,
    nickname text,
    work_description text,
    work_description_other text,
    site_url text,
    social_instagram text,
    social_youtube text,
    social_facebook text,
    social_x text,
    social_custom jsonb DEFAULT '[]'::jsonb NOT NULL,
    tenant_id uuid,
    CONSTRAINT profiles_account_type_check CHECK ((account_type = ANY (ARRAY['free'::text, 'patient'::text, 'therapist'::text, 'clinic'::text]))),
    CONSTRAINT profiles_role_check CHECK ((role = ANY (ARRAY['saas_admin'::text, 'tenant_member'::text, 'portal_user'::text, 'patient'::text])))
);


--
-- Name: COLUMN profiles.phone; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.phone IS 'WhatsApp / telefone no formato (99) 99999-9999';


--
-- Name: COLUMN profiles.bio; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.bio IS 'Breve apresenta????o p??blica (m??x 300 chars no front)';


--
-- Name: COLUMN profiles.account_type; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.account_type IS 'Tipo de conta: free=sem perfil ainda, patient=paciente (imut??vel), therapist=terapeuta (imut??vel), clinic=cl??nica (imut??vel).';


--
-- Name: COLUMN profiles.platform_roles; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.platform_roles IS 'Papéis globais de plataforma, independentes de tenant. Ex: editor de microlearning. Atribuído pelo saas_admin.';


--
-- Name: COLUMN profiles.nickname; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.nickname IS 'Apelido preferido para comunica????o interna (Ag??ncia PSI)';


--
-- Name: COLUMN profiles.work_description; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.work_description IS 'Categoria de trabalho selecionada no perfil p??blico';


--
-- Name: COLUMN profiles.work_description_other; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.work_description_other IS 'Descri????o livre quando work_description = ''outro''';


--
-- Name: COLUMN profiles.site_url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.site_url IS 'Endere??o do site pessoal ou profissional';


--
-- Name: COLUMN profiles.social_instagram; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.social_instagram IS 'Handle ou URL do Instagram';


--
-- Name: COLUMN profiles.social_youtube; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.social_youtube IS 'Handle ou URL do canal no YouTube';


--
-- Name: COLUMN profiles.social_facebook; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.social_facebook IS 'Handle ou URL da p??gina no Facebook';


--
-- Name: COLUMN profiles.social_x; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.social_x IS 'Handle ou URL do perfil no X (Twitter)';


--
-- Name: COLUMN profiles.social_custom; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.profiles.social_custom IS 'Array JSON com redes adicionais livres: [{name, url}]';


--
-- Name: public_submission_attempts; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.public_submission_attempts (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    endpoint text NOT NULL,
    ip_hash text,
    success boolean NOT NULL,
    error_code text,
    error_msg text,
    blocked_by text,
    user_agent text,
    metadata jsonb,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE public_submission_attempts; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.public_submission_attempts IS 'Log de tentativas em endpoints públicos (intake, signup, agendador). Escrita apenas via RPC SECURITY DEFINER.';


--
-- Name: recurrence_exceptions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.recurrence_exceptions (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    recurrence_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    original_date date NOT NULL,
    type public.recurrence_exception_type NOT NULL,
    new_date date,
    new_start_time time without time zone,
    new_end_time time without time zone,
    modalidade text,
    observacoes text,
    titulo_custom text,
    extra_fields jsonb,
    reason text,
    agenda_evento_id uuid,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: recurrence_rule_services; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.recurrence_rule_services (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    rule_id uuid NOT NULL,
    service_id uuid NOT NULL,
    quantity integer DEFAULT 1 NOT NULL,
    unit_price numeric(10,2) NOT NULL,
    discount_pct numeric(5,2) DEFAULT 0,
    discount_flat numeric(10,2) DEFAULT 0,
    final_price numeric(10,2) NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    CONSTRAINT recurrence_rule_services_disc_flat_chk CHECK ((discount_flat >= (0)::numeric)),
    CONSTRAINT recurrence_rule_services_disc_pct_chk CHECK (((discount_pct >= (0)::numeric) AND (discount_pct <= (100)::numeric))),
    CONSTRAINT recurrence_rule_services_final_price_chk CHECK ((final_price >= (0)::numeric)),
    CONSTRAINT recurrence_rule_services_quantity_chk CHECK ((quantity > 0))
);


--
-- Name: recurrence_rules; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.recurrence_rules (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    owner_id uuid NOT NULL,
    therapist_id uuid,
    patient_id uuid,
    determined_commitment_id uuid,
    type public.recurrence_type DEFAULT 'weekly'::public.recurrence_type NOT NULL,
    "interval" smallint DEFAULT 1 NOT NULL,
    weekdays smallint[] DEFAULT '{}'::smallint[] NOT NULL,
    start_time time without time zone NOT NULL,
    end_time time without time zone NOT NULL,
    timezone text DEFAULT 'America/Sao_Paulo'::text NOT NULL,
    duration_min smallint DEFAULT 50 NOT NULL,
    start_date date NOT NULL,
    end_date date,
    max_occurrences integer,
    open_ended boolean DEFAULT true NOT NULL,
    modalidade text DEFAULT 'presencial'::text,
    titulo_custom text,
    observacoes text,
    extra_fields jsonb,
    status text DEFAULT 'ativo'::text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    price numeric(10,2),
    insurance_plan_id uuid,
    insurance_guide_number text,
    insurance_value numeric(10,2),
    insurance_plan_service_id uuid,
    CONSTRAINT recurrence_rules_dates_chk CHECK (((end_date IS NULL) OR (end_date >= start_date))),
    CONSTRAINT recurrence_rules_interval_chk CHECK (("interval" >= 1)),
    CONSTRAINT recurrence_rules_status_check CHECK ((status = ANY (ARRAY['ativo'::text, 'pausado'::text, 'cancelado'::text]))),
    CONSTRAINT recurrence_rules_times_chk CHECK ((end_time > start_time))
);


--
-- Name: saas_admins; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.saas_admins (
    user_id uuid NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: saas_doc_votos; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.saas_doc_votos (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    doc_id uuid NOT NULL,
    user_id uuid NOT NULL,
    util boolean NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: saas_docs; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.saas_docs (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    titulo text NOT NULL,
    conteudo text DEFAULT ''::text NOT NULL,
    medias jsonb DEFAULT '[]'::jsonb NOT NULL,
    tipo_acesso text DEFAULT 'usuario'::text NOT NULL,
    pagina_path text NOT NULL,
    docs_relacionados uuid[] DEFAULT '{}'::uuid[] NOT NULL,
    ativo boolean DEFAULT true NOT NULL,
    ordem 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,
    categoria text,
    exibir_no_faq boolean DEFAULT false NOT NULL,
    votos_util integer DEFAULT 0 NOT NULL,
    votos_nao_util integer DEFAULT 0 NOT NULL,
    CONSTRAINT saas_docs_tipo_acesso_check CHECK ((tipo_acesso = ANY (ARRAY['admin'::text, 'usuario'::text])))
);


--
-- Name: COLUMN saas_docs.categoria; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.saas_docs.categoria IS 'Agrupa docs no portal FAQ (ex: Conta, Agenda, Pagamentos)';


--
-- Name: COLUMN saas_docs.exibir_no_faq; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.saas_docs.exibir_no_faq IS 'Se true, a doc e seus itens FAQ aparecem no portal de FAQ';


--
-- Name: saas_faq; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.saas_faq (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    pergunta text NOT NULL,
    categoria text,
    publico boolean DEFAULT false NOT NULL,
    votos integer DEFAULT 0 NOT NULL,
    titulo text,
    conteudo text,
    tipo_acesso text DEFAULT 'usuario'::text NOT NULL,
    pagina_path text NOT NULL,
    pagina_label text,
    medias jsonb DEFAULT '[]'::jsonb NOT NULL,
    faqs_relacionados uuid[] DEFAULT '{}'::uuid[] NOT NULL,
    ativo boolean DEFAULT true NOT NULL,
    ordem 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
);


--
-- Name: saas_faq_itens; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.saas_faq_itens (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    doc_id uuid NOT NULL,
    pergunta text NOT NULL,
    resposta text,
    ordem integer DEFAULT 0 NOT NULL,
    ativo 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
);


--
-- Name: TABLE saas_faq_itens; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.saas_faq_itens IS 'Pares pergunta/resposta vinculados a um documento de ajuda';


--
-- Name: saas_security_config; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.saas_security_config (
    id boolean DEFAULT true NOT NULL,
    honeypot_enabled boolean DEFAULT true NOT NULL,
    rate_limit_enabled boolean DEFAULT true NOT NULL,
    rate_limit_window_min integer DEFAULT 10 NOT NULL,
    rate_limit_max_attempts integer DEFAULT 5 NOT NULL,
    captcha_after_failures integer DEFAULT 3 NOT NULL,
    captcha_required_globally boolean DEFAULT false NOT NULL,
    block_duration_min integer DEFAULT 30 NOT NULL,
    captcha_required_window_min integer DEFAULT 60 NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_by uuid,
    CONSTRAINT saas_security_config_singleton CHECK ((id = true))
);


--
-- Name: TABLE saas_security_config; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.saas_security_config IS 'Singleton: configuração global de defesa contra bots em endpoints públicos.';


--
-- Name: saas_twilio_config; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.saas_twilio_config (
    id boolean DEFAULT true NOT NULL,
    account_sid text,
    whatsapp_webhook_url text,
    usd_brl_rate numeric(10,4) DEFAULT 5.5 NOT NULL,
    margin_multiplier numeric(10,4) DEFAULT 1.4 NOT NULL,
    notes text,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_by uuid,
    CONSTRAINT saas_twilio_config_mult_chk CHECK (((margin_multiplier >= (1)::numeric) AND (margin_multiplier <= (10)::numeric))),
    CONSTRAINT saas_twilio_config_rate_chk CHECK (((usd_brl_rate > (0)::numeric) AND (usd_brl_rate < (100)::numeric))),
    CONSTRAINT saas_twilio_config_sid_chk CHECK (((account_sid IS NULL) OR (account_sid ~ '^AC[a-zA-Z0-9]{32}$'::text))),
    CONSTRAINT saas_twilio_config_singleton CHECK ((id = true)),
    CONSTRAINT saas_twilio_config_url_chk CHECK (((whatsapp_webhook_url IS NULL) OR (whatsapp_webhook_url ~ '^https?://'::text)))
);


--
-- Name: TABLE saas_twilio_config; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.saas_twilio_config IS 'Config operacional Twilio editável via painel. AUTH_TOKEN continua em env var por segurança.';


--
-- Name: services; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.services (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    owner_id uuid NOT NULL,
    tenant_id uuid NOT NULL,
    name text NOT NULL,
    description text,
    price numeric(10,2) NOT NULL,
    duration_min integer,
    active boolean DEFAULT true NOT NULL,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now()
);


--
-- Name: session_reminder_logs; Type: TABLE; Schema: public; Owner: -
--

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])))
);


--
-- Name: TABLE session_reminder_logs; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.session_reminder_logs IS 'Log de lembretes disparados. UNIQUE (event_id, reminder_type) previne duplicata.';


--
-- Name: session_reminder_logs_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.session_reminder_logs_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: session_reminder_logs_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.session_reminder_logs_id_seq OWNED BY public.session_reminder_logs.id;


--
-- Name: session_reminder_settings; Type: TABLE; Schema: public; Owner: -
--

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)))
);


--
-- Name: TABLE session_reminder_settings; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.session_reminder_settings IS 'Configuracao por tenant dos lembretes automaticos de sessao via WhatsApp.';


--
-- Name: submission_rate_limits; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.submission_rate_limits (
    ip_hash text NOT NULL,
    endpoint text NOT NULL,
    attempt_count integer DEFAULT 0 NOT NULL,
    fail_count integer DEFAULT 0 NOT NULL,
    window_start timestamp with time zone DEFAULT now() NOT NULL,
    blocked_until timestamp with time zone,
    requires_captcha_until timestamp with time zone,
    last_attempt_at timestamp with time zone DEFAULT now() NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: TABLE submission_rate_limits; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.submission_rate_limits IS 'Estado de rate limit por IP+endpoint. Escrita apenas via RPC. SaaS admin lê pra dashboard.';


--
-- Name: subscription_events; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.subscription_events (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    subscription_id uuid NOT NULL,
    owner_id uuid NOT NULL,
    event_type text NOT NULL,
    old_plan_id uuid,
    new_plan_id uuid,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    created_by uuid,
    source text DEFAULT 'admin_ui'::text,
    reason text,
    metadata jsonb,
    owner_type text NOT NULL,
    owner_ref uuid NOT NULL,
    CONSTRAINT subscription_events_owner_ref_consistency_chk CHECK ((owner_id = owner_ref)),
    CONSTRAINT subscription_events_owner_type_chk CHECK (((owner_type IS NULL) OR (owner_type = ANY (ARRAY['clinic'::text, 'therapist'::text]))))
);


--
-- Name: subscription_intents_personal; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.subscription_intents_personal (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    user_id uuid NOT NULL,
    created_by_user_id uuid,
    email text NOT NULL,
    plan_id uuid NOT NULL,
    plan_key text,
    "interval" text,
    amount_cents integer,
    currency text,
    status text DEFAULT 'new'::text NOT NULL,
    source text DEFAULT 'manual'::text NOT NULL,
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    paid_at timestamp with time zone,
    subscription_id uuid,
    CONSTRAINT sint_personal_interval_check CHECK ((("interval" IS NULL) OR ("interval" = ANY (ARRAY['month'::text, 'year'::text])))),
    CONSTRAINT sint_personal_status_check CHECK ((status = ANY (ARRAY['new'::text, 'waiting_payment'::text, 'paid'::text, 'canceled'::text])))
);


--
-- Name: subscription_intents_tenant; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.subscription_intents_tenant (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    user_id uuid NOT NULL,
    created_by_user_id uuid,
    email text NOT NULL,
    plan_id uuid NOT NULL,
    plan_key text,
    "interval" text,
    amount_cents integer,
    currency text,
    status text DEFAULT 'new'::text NOT NULL,
    source text DEFAULT 'manual'::text NOT NULL,
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    paid_at timestamp with time zone,
    tenant_id uuid NOT NULL,
    subscription_id uuid,
    CONSTRAINT sint_tenant_interval_check CHECK ((("interval" IS NULL) OR ("interval" = ANY (ARRAY['month'::text, 'year'::text])))),
    CONSTRAINT sint_tenant_status_check CHECK ((status = ANY (ARRAY['new'::text, 'waiting_payment'::text, 'paid'::text, 'canceled'::text])))
);


--
-- Name: subscription_intents; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.subscription_intents AS
 SELECT t.id,
    t.user_id,
    t.created_by_user_id,
    t.email,
    t.plan_id,
    t.plan_key,
    t."interval",
    t.amount_cents,
    t.currency,
    t.status,
    t.source,
    t.notes,
    t.created_at,
    t.paid_at,
    t.tenant_id,
    t.subscription_id,
    'clinic'::text AS plan_target
   FROM public.subscription_intents_tenant t
UNION ALL
 SELECT p.id,
    p.user_id,
    p.created_by_user_id,
    p.email,
    p.plan_id,
    p.plan_key,
    p."interval",
    p.amount_cents,
    p.currency,
    p.status,
    p.source,
    p.notes,
    p.created_at,
    p.paid_at,
    NULL::uuid AS tenant_id,
    p.subscription_id,
    'therapist'::text AS plan_target
   FROM public.subscription_intents_personal p;


--
-- Name: subscription_intents_legacy; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.subscription_intents_legacy (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    user_id uuid,
    email text,
    plan_key text NOT NULL,
    "interval" text NOT NULL,
    amount_cents integer NOT NULL,
    currency text DEFAULT 'BRL'::text NOT NULL,
    status text DEFAULT 'new'::text NOT NULL,
    source text DEFAULT 'landing'::text NOT NULL,
    notes text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    paid_at timestamp with time zone,
    tenant_id uuid NOT NULL,
    created_by_user_id uuid,
    CONSTRAINT subscription_intents_interval_check CHECK (("interval" = ANY (ARRAY['month'::text, 'year'::text]))),
    CONSTRAINT subscription_intents_status_check CHECK ((status = ANY (ARRAY['new'::text, 'waiting_payment'::text, 'paid'::text, 'canceled'::text])))
);


--
-- Name: support_sessions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.support_sessions (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    admin_id uuid NOT NULL,
    token text DEFAULT encode(extensions.gen_random_bytes(32), 'hex'::text) NOT NULL,
    expires_at timestamp with time zone DEFAULT (now() + '01:00:00'::interval) NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: tenant_feature_exceptions_log; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.tenant_feature_exceptions_log (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    feature_key text NOT NULL,
    enabled boolean NOT NULL,
    reason text,
    created_by uuid,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: tenant_features; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.tenant_features (
    tenant_id uuid NOT NULL,
    feature_key text NOT NULL,
    enabled 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
);


--
-- Name: TABLE tenant_features; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.tenant_features IS 'Controle de features por tenant. RLS: member do tenant lê; tenant_admin ou saas_admin escreve. Antes da migration 20260418000005 estava com RLS off + GRANT ALL pra anon (A#30).';


--
-- Name: tenant_invites; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.tenant_invites (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    email text NOT NULL,
    role text NOT NULL,
    token uuid DEFAULT gen_random_uuid() NOT NULL,
    invited_by uuid,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    expires_at timestamp with time zone DEFAULT (now() + '7 days'::interval) NOT NULL,
    accepted_at timestamp with time zone,
    accepted_by uuid,
    revoked_at timestamp with time zone,
    revoked_by uuid,
    CONSTRAINT tenant_invites_role_check CHECK ((role = ANY (ARRAY['therapist'::text, 'secretary'::text])))
);


--
-- Name: TABLE tenant_invites; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.tenant_invites IS 'Convites pra entrar em tenant. Aceitar deve ser via RPC tenant_accept_invite (SECURITY DEFINER). Criar/revogar via UI por tenant_admin.';


--
-- Name: tenants; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.tenants (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    name text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    kind text DEFAULT 'saas'::text NOT NULL,
    papel_timbrado jsonb DEFAULT '{"footer": {"slots": {"left": null, "right": null, "center": {"type": "custom-text", "content": ""}}, "height": 40, "preset": "text-center", "divider": {"show": true, "color": "#cccccc", "style": "solid"}, "enabled": true, "showPageNumber": false}, "header": {"slots": {"left": {"size": "medium", "type": "logo"}, "right": {"type": "institution-data", "fields": ["nome", "cnpj", "endereco_linha"]}, "center": null}, "height": 80, "preset": "logo-left-text-right", "divider": {"show": true, "color": "#cccccc", "style": "solid"}, "enabled": true}, "margins": {"top": 20, "left": 25, "right": 25, "bottom": 20}}'::jsonb,
    cpf_cnpj text,
    CONSTRAINT tenants_cpf_cnpj_format CHECK (((cpf_cnpj IS NULL) OR (cpf_cnpj ~ '^[0-9]{11}$'::text) OR (cpf_cnpj ~ '^[0-9]{14}$'::text))),
    CONSTRAINT tenants_kind_check CHECK ((kind = ANY (ARRAY['therapist'::text, 'clinic_coworking'::text, 'clinic_reception'::text, 'clinic_full'::text, 'clinic'::text, 'saas'::text, 'supervisor'::text])))
);


--
-- Name: COLUMN tenants.kind; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.tenants.kind IS 'Tipo do tenant. Imut??vel ap??s cria????o. therapist=terapeuta solo. clinic_coworking/clinic_reception/clinic_full=cl??nicas. clinic e saas s??o legados.';


--
-- Name: COLUMN tenants.papel_timbrado; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.tenants.papel_timbrado IS 'Configuração do papel timbrado para geração de documentos (header, footer, margens)';


--
-- Name: COLUMN tenants.cpf_cnpj; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.tenants.cpf_cnpj IS 'CPF (11 digitos) ou CNPJ (14 digitos) do titular do tenant. Usado por gateways de pagamento (Asaas). Apenas digitos.';


--
-- Name: therapist_payout_records; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.therapist_payout_records (
    payout_id uuid NOT NULL,
    financial_record_id uuid NOT NULL
);


--
-- Name: twilio_subaccount_usage; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.twilio_subaccount_usage (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    channel_id uuid NOT NULL,
    twilio_subaccount_sid text NOT NULL,
    period_start date NOT NULL,
    period_end date NOT NULL,
    messages_sent integer DEFAULT 0 NOT NULL,
    messages_delivered integer DEFAULT 0 NOT NULL,
    messages_failed integer DEFAULT 0 NOT NULL,
    cost_usd numeric(12,6) DEFAULT 0 NOT NULL,
    cost_brl numeric(12,4) DEFAULT 0 NOT NULL,
    revenue_brl numeric(12,4) DEFAULT 0 NOT NULL,
    margin_brl numeric(12,4) GENERATED ALWAYS AS ((revenue_brl - cost_brl)) STORED,
    usd_brl_rate numeric(8,4) DEFAULT 0,
    synced_at timestamp with time zone DEFAULT now(),
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT twilio_subaccount_usage_period_check CHECK ((period_end >= period_start))
);


--
-- Name: TABLE twilio_subaccount_usage; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.twilio_subaccount_usage IS 'Consumo mensal de mensagens WhatsApp por subconta Twilio. Sincronizado via Edge Function.';


--
-- Name: user_settings; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.user_settings (
    user_id uuid NOT NULL,
    theme_mode text DEFAULT 'dark'::text NOT NULL,
    preset text DEFAULT 'Aura'::text NOT NULL,
    primary_color text DEFAULT 'noir'::text NOT NULL,
    surface_color text DEFAULT 'slate'::text NOT NULL,
    menu_mode text DEFAULT 'static'::text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    layout_variant text DEFAULT 'classic'::text NOT NULL,
    melissa_prefs jsonb DEFAULT '{}'::jsonb NOT NULL,
    CONSTRAINT user_settings_layout_variant_check CHECK ((layout_variant = ANY (ARRAY['classic'::text, 'rail'::text, 'melissa'::text]))),
    CONSTRAINT user_settings_menu_mode_check CHECK ((menu_mode = ANY (ARRAY['static'::text, 'overlay'::text]))),
    CONSTRAINT user_settings_theme_mode_check CHECK ((theme_mode = ANY (ARRAY['light'::text, 'dark'::text])))
);


--
-- Name: TABLE user_settings; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.user_settings IS 'Prefer??ncias de apar??ncia e layout por usu??rio';


--
-- Name: COLUMN user_settings.user_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.user_settings.user_id IS 'FK = auth.users.id ??? um registro por usu??rio';


--
-- Name: COLUMN user_settings.theme_mode; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.user_settings.theme_mode IS 'light | dark';


--
-- Name: COLUMN user_settings.preset; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.user_settings.preset IS 'Preset PrimeVue: Aura | Lara | Nora';


--
-- Name: COLUMN user_settings.primary_color; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.user_settings.primary_color IS 'Nome da cor prim??ria (ex: blue, emerald, noir)';


--
-- Name: COLUMN user_settings.surface_color; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.user_settings.surface_color IS 'Nome da surface (ex: slate, zinc, neutral)';


--
-- Name: COLUMN user_settings.menu_mode; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.user_settings.menu_mode IS 'static | overlay';


--
-- Name: COLUMN user_settings.layout_variant; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.user_settings.layout_variant IS 'classic (sidebar) | rail (mini rail + painel) | melissa (Win11 lockscreen, Beta)';


--
-- Name: COLUMN user_settings.melissa_prefs; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.user_settings.melissa_prefs IS 'Preferencias do layout Melissa (toque, opacidade overlay/imagem, formato hora, cards). Imagem de fundo permanece no localStorage por ser data URL pesada.';


--
-- Name: v_auth_users_public; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_auth_users_public AS
 SELECT id AS user_id,
    email,
    created_at,
    last_sign_in_at
   FROM auth.users u;


--
-- Name: v_cashflow_projection; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_cashflow_projection WITH (security_invoker='on') AS
 SELECT gs.mes,
    to_char(gs.mes, 'YYYY-MM'::text) AS mes_label,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'receita'::public.financial_record_type) AND (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])))), (0)::numeric) AS receitas_projetadas,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'despesa'::public.financial_record_type) AND (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])))), (0)::numeric) AS despesas_projetadas,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'receita'::public.financial_record_type) AND (fr.status = 'pending'::text))), (0)::numeric) AS receitas_pendentes,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'receita'::public.financial_record_type) AND (fr.status = 'overdue'::text))), (0)::numeric) AS receitas_vencidas,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'despesa'::public.financial_record_type) AND (fr.status = 'pending'::text))), (0)::numeric) AS despesas_pendentes,
    COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'despesa'::public.financial_record_type) AND (fr.status = 'overdue'::text))), (0)::numeric) AS despesas_vencidas,
    (COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'receita'::public.financial_record_type) AND (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])))), (0)::numeric) - COALESCE(sum(fr.final_amount) FILTER (WHERE ((fr.type = 'despesa'::public.financial_record_type) AND (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])))), (0)::numeric)) AS saldo_projetado,
    count(fr.id) FILTER (WHERE (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text]))) AS count_registros
   FROM (generate_series(((date_trunc('month'::text, (CURRENT_DATE)::timestamp with time zone))::date)::timestamp with time zone, (((date_trunc('month'::text, (CURRENT_DATE)::timestamp with time zone) + '5 mons'::interval))::date)::timestamp with time zone, '1 mon'::interval) gs(mes)
     LEFT JOIN public.financial_records fr ON (((fr.deleted_at IS NULL) AND (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])) AND ((date_trunc('month'::text, (fr.due_date)::timestamp with time zone))::date = gs.mes))))
  GROUP BY gs.mes
  ORDER BY gs.mes;


--
-- Name: VIEW v_cashflow_projection; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON VIEW public.v_cashflow_projection IS 'Fluxo de caixa projetado: pr??ximos 6 meses com totais de pending+overdue por due_date. Usa security_invoker=on ??? filtra automaticamente pelo auth.uid() via RLS de financial_records.';


--
-- Name: v_commitment_totals; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_commitment_totals AS
 SELECT c.tenant_id,
    c.id AS commitment_id,
    (COALESCE(sum(l.minutes), (0)::bigint))::integer AS total_minutes
   FROM (public.determined_commitments c
     LEFT JOIN public.commitment_time_logs l ON ((l.commitment_id = c.id)))
  GROUP BY c.tenant_id, c.id;


--
-- Name: v_patient_engajamento; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_patient_engajamento WITH (security_invoker='on') AS
 WITH sessoes AS (
         SELECT ae.patient_id,
            ae.tenant_id,
            count(*) FILTER (WHERE (ae.status = 'realizado'::public.status_evento_agenda)) AS total_realizadas,
            count(*) FILTER (WHERE (ae.status = ANY (ARRAY['realizado'::public.status_evento_agenda, 'cancelado'::public.status_evento_agenda, 'faltou'::public.status_evento_agenda]))) AS total_marcadas,
            count(*) FILTER (WHERE (ae.status = 'faltou'::public.status_evento_agenda)) AS total_faltas,
            max(ae.inicio_em) FILTER (WHERE (ae.status = 'realizado'::public.status_evento_agenda)) AS ultima_sessao_em,
            min(ae.inicio_em) FILTER (WHERE (ae.status = 'realizado'::public.status_evento_agenda)) AS primeira_sessao_em,
            count(*) FILTER (WHERE ((ae.status = 'realizado'::public.status_evento_agenda) AND (ae.inicio_em >= (now() - '30 days'::interval)))) AS sessoes_ultimo_mes
           FROM public.agenda_eventos ae
          WHERE (ae.patient_id IS NOT NULL)
          GROUP BY ae.patient_id, ae.tenant_id
        ), financeiro AS (
         SELECT fr.patient_id,
            fr.tenant_id,
            COALESCE(sum(fr.final_amount) FILTER (WHERE (fr.status = 'paid'::text)), (0)::numeric) AS total_pago,
            COALESCE(avg(fr.final_amount) FILTER (WHERE (fr.status = 'paid'::text)), (0)::numeric) AS ticket_medio,
            count(*) FILTER (WHERE ((fr.status = ANY (ARRAY['pending'::text, 'overdue'::text])) AND (fr.due_date < now()))) AS cobr_vencidas,
            count(*) FILTER (WHERE (fr.status = ANY (ARRAY['pending'::text, 'overdue'::text]))) AS cobr_pendentes,
            count(*) FILTER (WHERE ((fr.type = 'receita'::public.financial_record_type) AND (fr.status = 'paid'::text))) AS cobr_pagas
           FROM public.financial_records fr
          WHERE ((fr.patient_id IS NOT NULL) AND (fr.deleted_at IS NULL))
          GROUP BY fr.patient_id, fr.tenant_id
        )
 SELECT p.id AS patient_id,
    p.tenant_id,
    p.nome_completo,
    p.status,
    p.risco_elevado,
    COALESCE(s.total_realizadas, (0)::bigint) AS total_sessoes,
    COALESCE(s.sessoes_ultimo_mes, (0)::bigint) AS sessoes_ultimo_mes,
    s.primeira_sessao_em,
    s.ultima_sessao_em,
    (EXTRACT(day FROM (now() - s.ultima_sessao_em)))::integer AS dias_sem_sessao,
        CASE
            WHEN (COALESCE(s.total_marcadas, (0)::bigint) = 0) THEN NULL::numeric
            ELSE round((((s.total_realizadas)::numeric / (s.total_marcadas)::numeric) * (100)::numeric), 1)
        END AS taxa_comparecimento,
    COALESCE(f.total_pago, (0)::numeric) AS ltv_total,
    round(COALESCE(f.ticket_medio, (0)::numeric), 2) AS ticket_medio,
    COALESCE(f.cobr_vencidas, (0)::bigint) AS cobr_vencidas,
    COALESCE(f.cobr_pagas, (0)::bigint) AS cobr_pagas,
        CASE
            WHEN (COALESCE((f.cobr_pagas + f.cobr_vencidas), (0)::bigint) = 0) THEN NULL::numeric
            ELSE round((((f.cobr_pagas)::numeric / ((f.cobr_pagas + f.cobr_vencidas))::numeric) * (100)::numeric), 1)
        END AS taxa_pagamentos_dia,
    round(LEAST((100)::numeric, COALESCE(((
        CASE
            WHEN (COALESCE(s.total_marcadas, (0)::bigint) = 0) THEN (50)::numeric
            ELSE LEAST((50)::numeric, (((s.total_realizadas)::numeric / (s.total_marcadas)::numeric) * (50)::numeric))
        END +
        CASE
            WHEN (COALESCE((f.cobr_pagas + f.cobr_vencidas), (0)::bigint) = 0) THEN (30)::numeric
            ELSE LEAST((30)::numeric, (((f.cobr_pagas)::numeric / ((f.cobr_pagas + f.cobr_vencidas))::numeric) * (30)::numeric))
        END) + (
        CASE
            WHEN (s.ultima_sessao_em IS NULL) THEN 0
            WHEN (EXTRACT(day FROM (now() - s.ultima_sessao_em)) <= (14)::numeric) THEN 20
            WHEN (EXTRACT(day FROM (now() - s.ultima_sessao_em)) <= (30)::numeric) THEN 15
            WHEN (EXTRACT(day FROM (now() - s.ultima_sessao_em)) <= (60)::numeric) THEN 8
            ELSE 0
        END)::numeric), (0)::numeric)), 0) AS engajamento_score,
        CASE
            WHEN (s.primeira_sessao_em IS NULL) THEN NULL::integer
            ELSE (EXTRACT(day FROM (now() - s.primeira_sessao_em)))::integer
        END AS duracao_tratamento_dias
   FROM ((public.patients p
     LEFT JOIN sessoes s ON (((s.patient_id = p.id) AND (s.tenant_id = p.tenant_id))))
     LEFT JOIN financeiro f ON (((f.patient_id = p.id) AND (f.tenant_id = p.tenant_id))));


--
-- Name: VIEW v_patient_engajamento; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON VIEW public.v_patient_engajamento IS 'Score de engajamento e métricas consolidadas por paciente. Calculado em tempo real via RLS (security_invoker=on).';


--
-- Name: v_patient_groups_with_counts; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_patient_groups_with_counts AS
 SELECT pg.id,
    pg.nome,
    pg.cor,
    pg.owner_id,
    pg.is_system,
    pg.is_active,
    pg.created_at,
    pg.updated_at,
    (COALESCE(count(pgp.patient_id), (0)::bigint))::integer AS patients_count
   FROM (public.patient_groups pg
     LEFT JOIN public.patient_group_patient pgp ON ((pgp.patient_group_id = pg.id)))
  GROUP BY pg.id, pg.nome, pg.cor, pg.owner_id, pg.is_system, pg.is_active, pg.created_at, pg.updated_at;


--
-- Name: v_patients_risco; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_patients_risco WITH (security_invoker='on') AS
 SELECT p.id,
    p.tenant_id,
    p.nome_completo,
    p.status,
    p.risco_elevado,
    p.risco_nota,
    p.risco_sinalizado_em,
    e.dias_sem_sessao,
    e.engajamento_score,
    e.taxa_comparecimento,
        CASE
            WHEN p.risco_elevado THEN 'risco_sinalizado'::text
            WHEN ((COALESCE(e.dias_sem_sessao, 999) > 30) AND (p.status = 'Ativo'::text)) THEN 'sem_sessao_30d'::text
            WHEN (COALESCE(e.taxa_comparecimento, (100)::numeric) < (60)::numeric) THEN 'baixo_comparecimento'::text
            WHEN (COALESCE(e.cobr_vencidas, (0)::bigint) > 0) THEN 'cobranca_vencida'::text
            ELSE 'ok'::text
        END AS alerta_tipo
   FROM (public.patients p
     JOIN public.v_patient_engajamento e ON ((e.patient_id = p.id)))
  WHERE ((p.status = 'Ativo'::text) AND ((p.risco_elevado = true) OR (COALESCE(e.dias_sem_sessao, 999) > 30) OR (COALESCE(e.taxa_comparecimento, (100)::numeric) < (60)::numeric) OR (COALESCE(e.cobr_vencidas, (0)::bigint) > 0)));


--
-- Name: VIEW v_patients_risco; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON VIEW public.v_patients_risco IS 'Pacientes ativos que precisam de atenção: risco clínico, sem sessão há 30+ dias, baixo comparecimento ou cobrança vencida';


--
-- Name: v_plan_active_prices; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_plan_active_prices AS
 SELECT plan_id,
    max(
        CASE
            WHEN (("interval" = 'month'::text) AND is_active) THEN amount_cents
            ELSE NULL::integer
        END) AS monthly_cents,
    max(
        CASE
            WHEN (("interval" = 'year'::text) AND is_active) THEN amount_cents
            ELSE NULL::integer
        END) AS yearly_cents,
    max(
        CASE
            WHEN (("interval" = 'month'::text) AND is_active) THEN currency
            ELSE NULL::text
        END) AS monthly_currency,
    max(
        CASE
            WHEN (("interval" = 'year'::text) AND is_active) THEN currency
            ELSE NULL::text
        END) AS yearly_currency
   FROM public.plan_prices
  GROUP BY plan_id;


--
-- Name: v_public_pricing; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_public_pricing AS
 SELECT p.id AS plan_id,
    p.key AS plan_key,
    p.name AS plan_name,
    COALESCE(pp.public_name, ''::text) AS public_name,
    COALESCE(pp.public_description, ''::text) AS public_description,
    pp.badge,
    COALESCE(pp.is_featured, false) AS is_featured,
    COALESCE(pp.is_visible, true) AS is_visible,
    COALESCE(pp.sort_order, 0) AS sort_order,
    ap.monthly_cents,
    ap.yearly_cents,
    ap.monthly_currency,
    ap.yearly_currency,
    COALESCE(( SELECT jsonb_agg(jsonb_build_object('id', b.id, 'text', b.text, 'highlight', b.highlight, 'sort_order', b.sort_order) ORDER BY b.sort_order, b.created_at) AS jsonb_agg
           FROM public.plan_public_bullets b
          WHERE (b.plan_id = p.id)), '[]'::jsonb) AS bullets,
    p.target AS plan_target
   FROM ((public.plans p
     LEFT JOIN public.plan_public pp ON ((pp.plan_id = p.id)))
     LEFT JOIN public.v_plan_active_prices ap ON ((ap.plan_id = p.id)))
  ORDER BY COALESCE(pp.sort_order, 0), p.key;


--
-- Name: v_subscription_feature_mismatch; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_subscription_feature_mismatch AS
 WITH expected AS (
         SELECT s.user_id AS owner_id,
            f.key AS feature_key
           FROM ((public.subscriptions s
             JOIN public.plan_features pf ON (((pf.plan_id = s.plan_id) AND (pf.enabled = true))))
             JOIN public.features f ON ((f.id = pf.feature_id)))
          WHERE ((s.status = 'active'::text) AND (s.tenant_id IS NULL) AND (s.user_id IS NOT NULL))
        ), actual AS (
         SELECT e.owner_id,
            e.feature_key
           FROM public.owner_feature_entitlements e
        )
 SELECT COALESCE(expected.owner_id, actual.owner_id) AS owner_id,
    COALESCE(expected.feature_key, actual.feature_key) AS feature_key,
        CASE
            WHEN ((expected.feature_key IS NOT NULL) AND (actual.feature_key IS NULL)) THEN 'missing_entitlement'::text
            WHEN ((expected.feature_key IS NULL) AND (actual.feature_key IS NOT NULL)) THEN 'unexpected_entitlement'::text
            ELSE NULL::text
        END AS mismatch_type
   FROM (expected
     FULL JOIN actual ON (((expected.owner_id = actual.owner_id) AND (expected.feature_key = actual.feature_key))))
  WHERE ((expected.feature_key IS NULL) OR (actual.feature_key IS NULL));


--
-- Name: v_subscription_health; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_subscription_health AS
 SELECT s.id AS subscription_id,
    s.user_id AS owner_id,
    s.status,
    s.plan_id,
    p.key AS plan_key,
    s.current_period_start,
    s.current_period_end,
    s.updated_at,
        CASE
            WHEN (s.plan_id IS NULL) THEN 'missing_plan'::text
            WHEN (p.id IS NULL) THEN 'invalid_plan'::text
            WHEN ((s.status = 'active'::text) AND (s.current_period_end IS NOT NULL) AND (s.current_period_end < now())) THEN 'expired_but_active'::text
            WHEN ((s.status = 'canceled'::text) AND (s.current_period_end > now())) THEN 'canceled_but_still_in_period'::text
            ELSE 'ok'::text
        END AS health_status,
        CASE
            WHEN (s.tenant_id IS NOT NULL) THEN 'clinic'::text
            ELSE 'therapist'::text
        END AS owner_type,
    COALESCE(s.tenant_id, s.user_id) AS owner_ref
   FROM (public.subscriptions s
     LEFT JOIN public.plans p ON ((p.id = s.plan_id)));


--
-- Name: v_subscription_health_v2; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_subscription_health_v2 AS
 SELECT s.id AS subscription_id,
    s.user_id AS owner_id,
        CASE
            WHEN (s.tenant_id IS NOT NULL) THEN 'clinic'::text
            ELSE 'therapist'::text
        END AS owner_type,
    COALESCE(s.tenant_id, s.user_id) AS owner_ref,
    s.status,
    s.plan_id,
    p.key AS plan_key,
    s.current_period_start,
    s.current_period_end,
    s.updated_at,
        CASE
            WHEN (s.plan_id IS NULL) THEN 'missing_plan'::text
            WHEN (p.id IS NULL) THEN 'invalid_plan'::text
            WHEN ((s.status = 'active'::text) AND (s.current_period_end IS NOT NULL) AND (s.current_period_end < now())) THEN 'expired_but_active'::text
            WHEN ((s.status = 'canceled'::text) AND (s.current_period_end > now())) THEN 'canceled_but_still_in_period'::text
            ELSE 'ok'::text
        END AS health_status
   FROM (public.subscriptions s
     LEFT JOIN public.plans p ON ((p.id = s.plan_id)));


--
-- Name: v_tag_patient_counts; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_tag_patient_counts AS
 SELECT t.id,
    t.owner_id,
    t.nome,
    t.cor,
    t.is_padrao,
    t.created_at,
    t.updated_at,
    (COALESCE(count(ppt.patient_id), (0)::bigint))::integer AS pacientes_count,
    (COALESCE(count(ppt.patient_id), (0)::bigint))::integer AS patient_count
   FROM (public.patient_tags t
     LEFT JOIN public.patient_patient_tag ppt ON (((ppt.tag_id = t.id) AND (ppt.owner_id = t.owner_id))))
  GROUP BY t.id, t.owner_id, t.nome, t.cor, t.is_padrao, t.created_at, t.updated_at;


--
-- Name: v_tenant_active_subscription; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_tenant_active_subscription AS
 SELECT DISTINCT ON (tenant_id) tenant_id,
    plan_id,
    plan_key,
    "interval",
    status,
    current_period_start,
    current_period_end,
    created_at
   FROM public.subscriptions s
  WHERE ((tenant_id IS NOT NULL) AND (status = 'active'::text) AND ((current_period_end IS NULL) OR (current_period_end > now())))
  ORDER BY tenant_id, created_at DESC;


--
-- Name: v_tenant_entitlements; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_tenant_entitlements AS
 SELECT a.tenant_id,
    f.key AS feature_key,
    true AS allowed
   FROM ((public.v_tenant_active_subscription a
     JOIN public.plan_features pf ON (((pf.plan_id = a.plan_id) AND (pf.enabled = true))))
     JOIN public.features f ON ((f.id = pf.feature_id)));


--
-- Name: v_tenant_entitlements_full; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_tenant_entitlements_full AS
 SELECT a.tenant_id,
    f.key AS feature_key,
    (pf.enabled = true) AS allowed,
    pf.limits,
    a.plan_id,
    p.key AS plan_key
   FROM (((public.v_tenant_active_subscription a
     JOIN public.plan_features pf ON ((pf.plan_id = a.plan_id)))
     JOIN public.features f ON ((f.id = pf.feature_id)))
     JOIN public.plans p ON ((p.id = a.plan_id)));


--
-- Name: v_tenant_entitlements_json; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_tenant_entitlements_json AS
 SELECT tenant_id,
    max(plan_key) AS plan_key,
    jsonb_object_agg(feature_key, jsonb_build_object('allowed', allowed, 'limits', COALESCE(limits, '{}'::jsonb)) ORDER BY feature_key) AS entitlements
   FROM public.v_tenant_entitlements_full
  GROUP BY tenant_id;


--
-- Name: v_tenant_feature_exceptions; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_tenant_feature_exceptions AS
 SELECT tf.tenant_id,
    a.plan_key,
    tf.feature_key,
    'commercial_exception'::text AS exception_type
   FROM ((public.tenant_features tf
     JOIN public.v_tenant_active_subscription a ON ((a.tenant_id = tf.tenant_id)))
     LEFT JOIN public.v_tenant_entitlements_full v ON (((v.tenant_id = tf.tenant_id) AND (v.feature_key = tf.feature_key))))
  WHERE ((tf.enabled = true) AND (COALESCE(v.allowed, false) = false));


--
-- Name: v_tenant_feature_mismatch; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_tenant_feature_mismatch AS
 WITH plan_allowed AS (
         SELECT v.tenant_id,
            v.feature_key,
            v.allowed
           FROM public.v_tenant_entitlements_full v
        ), overrides AS (
         SELECT tf.tenant_id,
            tf.feature_key,
            tf.enabled
           FROM public.tenant_features tf
        )
 SELECT o.tenant_id,
    o.feature_key,
        CASE
            WHEN ((o.enabled = true) AND (COALESCE(p.allowed, false) = false)) THEN 'unexpected_override'::text
            ELSE NULL::text
        END AS mismatch_type
   FROM (overrides o
     LEFT JOIN plan_allowed p ON (((p.tenant_id = o.tenant_id) AND (p.feature_key = o.feature_key))))
  WHERE ((o.enabled = true) AND (COALESCE(p.allowed, false) = false));


--
-- Name: v_tenant_members_with_profiles; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_tenant_members_with_profiles AS
 SELECT tm.id AS tenant_member_id,
    tm.tenant_id,
    tm.user_id,
    tm.role,
    tm.status,
    tm.created_at,
    p.full_name,
    au.email
   FROM ((public.tenant_members tm
     LEFT JOIN public.profiles p ON ((p.id = tm.user_id)))
     LEFT JOIN auth.users au ON ((au.id = tm.user_id)));


--
-- Name: v_tenant_people; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_tenant_people AS
 SELECT 'member'::text AS type,
    m.tenant_id,
    m.user_id,
    u.email,
    m.role,
    m.status,
    NULL::uuid AS invite_token,
    NULL::timestamp with time zone AS expires_at
   FROM (public.tenant_members m
     JOIN auth.users u ON ((u.id = m.user_id)))
UNION ALL
 SELECT 'invite'::text AS type,
    i.tenant_id,
    NULL::uuid AS user_id,
    i.email,
    i.role,
    'invited'::text AS status,
    i.token AS invite_token,
    i.expires_at
   FROM public.tenant_invites i
  WHERE ((i.accepted_at IS NULL) AND (i.revoked_at IS NULL));


--
-- Name: v_tenant_staff; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_tenant_staff AS
 SELECT ('m_'::text || (tm.id)::text) AS row_id,
    tm.tenant_id,
    tm.user_id,
    tm.role,
    tm.status,
    tm.created_at,
    p.full_name,
    au.email,
    NULL::uuid AS invite_token
   FROM ((public.tenant_members tm
     LEFT JOIN public.profiles p ON ((p.id = tm.user_id)))
     LEFT JOIN auth.users au ON ((au.id = tm.user_id)))
UNION ALL
 SELECT ('i_'::text || (ti.id)::text) AS row_id,
    ti.tenant_id,
    NULL::uuid AS user_id,
    ti.role,
    'invited'::text AS status,
    ti.created_at,
    NULL::text AS full_name,
    ti.email,
    ti.token AS invite_token
   FROM public.tenant_invites ti
  WHERE ((ti.accepted_at IS NULL) AND (ti.revoked_at IS NULL) AND (ti.expires_at > now()));


--
-- Name: v_twilio_whatsapp_overview; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_twilio_whatsapp_overview AS
 SELECT nc.id AS channel_id,
    nc.tenant_id,
    nc.owner_id,
    nc.is_active,
    nc.connection_status,
    nc.display_name,
    nc.twilio_subaccount_sid,
    nc.twilio_phone_number,
    nc.twilio_phone_sid,
    nc.cost_per_message_usd,
    nc.price_per_message_brl,
    nc.provisioned_at,
    nc.created_at,
    nc.updated_at,
    COALESCE(u.messages_sent, 0) AS current_month_sent,
    COALESCE(u.messages_delivered, 0) AS current_month_delivered,
    COALESCE(u.messages_failed, 0) AS current_month_failed,
    COALESCE(u.cost_usd, (0)::numeric) AS current_month_cost_usd,
    COALESCE(u.cost_brl, (0)::numeric) AS current_month_cost_brl,
    COALESCE(u.revenue_brl, (0)::numeric) AS current_month_revenue_brl,
    COALESCE(u.margin_brl, (0)::numeric) AS current_month_margin_brl
   FROM (public.notification_channels nc
     LEFT JOIN public.twilio_subaccount_usage u ON (((u.channel_id = nc.id) AND (u.period_start = (date_trunc('month'::text, (CURRENT_DATE)::timestamp with time zone))::date))))
  WHERE ((nc.channel = 'whatsapp'::text) AND (nc.provider = 'twilio'::text) AND (nc.deleted_at IS NULL));


--
-- Name: VIEW v_twilio_whatsapp_overview; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON VIEW public.v_twilio_whatsapp_overview IS 'Visão consolidada de subcontas Twilio WhatsApp com uso do mês corrente.';


--
-- Name: v_user_active_subscription; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_user_active_subscription AS
 SELECT DISTINCT ON (user_id) user_id,
    plan_id,
    plan_key,
    "interval",
    status,
    current_period_start,
    current_period_end,
    created_at
   FROM public.subscriptions s
  WHERE ((tenant_id IS NULL) AND (user_id IS NOT NULL) AND (status = 'active'::text) AND ((current_period_end IS NULL) OR (current_period_end > now())))
  ORDER BY user_id, created_at DESC;


--
-- Name: v_user_entitlements; Type: VIEW; Schema: public; Owner: -
--

CREATE VIEW public.v_user_entitlements AS
 SELECT a.user_id,
    f.key AS feature_key,
    true AS allowed
   FROM ((public.v_user_active_subscription a
     JOIN public.plan_features pf ON (((pf.plan_id = a.plan_id) AND (pf.enabled = true))))
     JOIN public.features f ON ((f.id = pf.feature_id)));


--
-- Name: whatsapp_connection_incidents; Type: TABLE; Schema: public; Owner: -
--

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])))
);


--
-- Name: TABLE whatsapp_connection_incidents; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.whatsapp_connection_incidents IS 'Eventos de degradacao de conexao WhatsApp (evolution/twilio). Max 1 aberto por channel (UNIQUE parcial).';


--
-- Name: whatsapp_credit_packages; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.whatsapp_credit_packages (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    name text NOT NULL,
    description text,
    credits integer NOT NULL,
    price_brl numeric(10,2) NOT NULL,
    is_active boolean DEFAULT true NOT NULL,
    is_featured boolean DEFAULT false NOT NULL,
    "position" integer DEFAULT 100 NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT whatsapp_credit_packages_credits_check CHECK ((credits > 0)),
    CONSTRAINT whatsapp_credit_packages_name_check CHECK (((length(name) > 0) AND (length(name) <= 100))),
    CONSTRAINT whatsapp_credit_packages_price_brl_check CHECK ((price_brl > (0)::numeric))
);


--
-- Name: TABLE whatsapp_credit_packages; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.whatsapp_credit_packages IS 'Pacotes de creditos disponiveis pra compra. Gerenciado pelo SaaS admin.';


--
-- Name: whatsapp_credit_purchases; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.whatsapp_credit_purchases (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    package_id uuid,
    package_name text NOT NULL,
    credits integer NOT NULL,
    amount_brl numeric(10,2) NOT NULL,
    status text DEFAULT 'pending'::text NOT NULL,
    asaas_customer_id text,
    asaas_payment_id text,
    asaas_payment_link text,
    asaas_pix_qrcode text,
    asaas_pix_copy_paste text,
    paid_at timestamp with time zone,
    expires_at timestamp with time zone,
    failed_at timestamp with time zone,
    created_by uuid,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT whatsapp_credit_purchases_amount_brl_check CHECK ((amount_brl > (0)::numeric)),
    CONSTRAINT whatsapp_credit_purchases_credits_check CHECK ((credits > 0)),
    CONSTRAINT whatsapp_credit_purchases_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'paid'::text, 'failed'::text, 'expired'::text, 'refunded'::text, 'cancelled'::text])))
);


--
-- Name: TABLE whatsapp_credit_purchases; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.whatsapp_credit_purchases IS 'Ordens de compra de creditos via Asaas. Webhook atualiza status.';


--
-- Name: whatsapp_credits_balance; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.whatsapp_credits_balance (
    tenant_id uuid NOT NULL,
    balance integer DEFAULT 0 NOT NULL,
    lifetime_purchased integer DEFAULT 0 NOT NULL,
    lifetime_used integer DEFAULT 0 NOT NULL,
    low_balance_threshold integer DEFAULT 20 NOT NULL,
    low_balance_alerted_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 whatsapp_credits_balance_balance_check CHECK ((balance >= 0)),
    CONSTRAINT whatsapp_credits_balance_low_balance_threshold_check CHECK ((low_balance_threshold >= 0))
);


--
-- Name: TABLE whatsapp_credits_balance; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.whatsapp_credits_balance IS 'Saldo atual de creditos WhatsApp por tenant. 1 credito = 1 mensagem Twilio.';


--
-- Name: whatsapp_credits_transactions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.whatsapp_credits_transactions (
    id bigint NOT NULL,
    tenant_id uuid NOT NULL,
    kind text NOT NULL,
    amount integer NOT NULL,
    balance_after integer NOT NULL,
    conversation_message_id bigint,
    purchase_id uuid,
    admin_id uuid,
    note text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT whatsapp_credits_transactions_kind_check CHECK ((kind = ANY (ARRAY['purchase'::text, 'usage'::text, 'topup_manual'::text, 'refund'::text, 'adjustment'::text])))
);


--
-- Name: TABLE whatsapp_credits_transactions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.whatsapp_credits_transactions IS 'Extrato de creditos WhatsApp. Append-only — nao editar/deletar.';


--
-- Name: whatsapp_credits_transactions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.whatsapp_credits_transactions_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: whatsapp_credits_transactions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.whatsapp_credits_transactions_id_seq OWNED BY public.whatsapp_credits_transactions.id;


--
-- Name: messages; Type: TABLE; Schema: realtime; Owner: -
--

CREATE TABLE realtime.messages (
    topic text NOT NULL,
    extension text NOT NULL,
    payload jsonb,
    event text,
    private boolean DEFAULT false,
    updated_at timestamp without time zone DEFAULT now() NOT NULL,
    inserted_at timestamp without time zone DEFAULT now() NOT NULL,
    id uuid DEFAULT gen_random_uuid() NOT NULL
)
PARTITION BY RANGE (inserted_at);


--
-- Name: messages_2026_05_08; Type: TABLE; Schema: realtime; Owner: -
--

CREATE TABLE realtime.messages_2026_05_08 (
    topic text NOT NULL,
    extension text NOT NULL,
    payload jsonb,
    event text,
    private boolean DEFAULT false,
    updated_at timestamp without time zone DEFAULT now() NOT NULL,
    inserted_at timestamp without time zone DEFAULT now() NOT NULL,
    id uuid DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: messages_2026_05_09; Type: TABLE; Schema: realtime; Owner: -
--

CREATE TABLE realtime.messages_2026_05_09 (
    topic text NOT NULL,
    extension text NOT NULL,
    payload jsonb,
    event text,
    private boolean DEFAULT false,
    updated_at timestamp without time zone DEFAULT now() NOT NULL,
    inserted_at timestamp without time zone DEFAULT now() NOT NULL,
    id uuid DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: messages_2026_05_10; Type: TABLE; Schema: realtime; Owner: -
--

CREATE TABLE realtime.messages_2026_05_10 (
    topic text NOT NULL,
    extension text NOT NULL,
    payload jsonb,
    event text,
    private boolean DEFAULT false,
    updated_at timestamp without time zone DEFAULT now() NOT NULL,
    inserted_at timestamp without time zone DEFAULT now() NOT NULL,
    id uuid DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: messages_2026_05_11; Type: TABLE; Schema: realtime; Owner: -
--

CREATE TABLE realtime.messages_2026_05_11 (
    topic text NOT NULL,
    extension text NOT NULL,
    payload jsonb,
    event text,
    private boolean DEFAULT false,
    updated_at timestamp without time zone DEFAULT now() NOT NULL,
    inserted_at timestamp without time zone DEFAULT now() NOT NULL,
    id uuid DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: messages_2026_05_12; Type: TABLE; Schema: realtime; Owner: -
--

CREATE TABLE realtime.messages_2026_05_12 (
    topic text NOT NULL,
    extension text NOT NULL,
    payload jsonb,
    event text,
    private boolean DEFAULT false,
    updated_at timestamp without time zone DEFAULT now() NOT NULL,
    inserted_at timestamp without time zone DEFAULT now() NOT NULL,
    id uuid DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: messages_2026_05_13; Type: TABLE; Schema: realtime; Owner: -
--

CREATE TABLE realtime.messages_2026_05_13 (
    topic text NOT NULL,
    extension text NOT NULL,
    payload jsonb,
    event text,
    private boolean DEFAULT false,
    updated_at timestamp without time zone DEFAULT now() NOT NULL,
    inserted_at timestamp without time zone DEFAULT now() NOT NULL,
    id uuid DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: messages_2026_05_14; Type: TABLE; Schema: realtime; Owner: -
--

CREATE TABLE realtime.messages_2026_05_14 (
    topic text NOT NULL,
    extension text NOT NULL,
    payload jsonb,
    event text,
    private boolean DEFAULT false,
    updated_at timestamp without time zone DEFAULT now() NOT NULL,
    inserted_at timestamp without time zone DEFAULT now() NOT NULL,
    id uuid DEFAULT gen_random_uuid() NOT NULL
);


--
-- Name: schema_migrations; Type: TABLE; Schema: realtime; Owner: -
--

CREATE TABLE realtime.schema_migrations (
    version bigint NOT NULL,
    inserted_at timestamp(0) without time zone
);


--
-- Name: subscription; Type: TABLE; Schema: realtime; Owner: -
--

CREATE TABLE realtime.subscription (
    id bigint NOT NULL,
    subscription_id uuid NOT NULL,
    entity regclass NOT NULL,
    filters realtime.user_defined_filter[] DEFAULT '{}'::realtime.user_defined_filter[] NOT NULL,
    claims jsonb NOT NULL,
    claims_role regrole GENERATED ALWAYS AS (realtime.to_regrole((claims ->> 'role'::text))) STORED NOT NULL,
    created_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL
);


--
-- Name: subscription_id_seq; Type: SEQUENCE; Schema: realtime; Owner: -
--

ALTER TABLE realtime.subscription ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME realtime.subscription_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- Name: buckets; Type: TABLE; Schema: storage; Owner: -
--

CREATE TABLE storage.buckets (
    id text NOT NULL,
    name text NOT NULL,
    owner uuid,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now(),
    public boolean DEFAULT false,
    avif_autodetection boolean DEFAULT false,
    file_size_limit bigint,
    allowed_mime_types text[],
    owner_id text,
    type storage.buckettype DEFAULT 'STANDARD'::storage.buckettype NOT NULL
);


--
-- Name: COLUMN buckets.owner; Type: COMMENT; Schema: storage; Owner: -
--

COMMENT ON COLUMN storage.buckets.owner IS 'Field is deprecated, use owner_id instead';


--
-- Name: buckets_analytics; Type: TABLE; Schema: storage; Owner: -
--

CREATE TABLE storage.buckets_analytics (
    name text NOT NULL,
    type storage.buckettype DEFAULT 'ANALYTICS'::storage.buckettype NOT NULL,
    format text DEFAULT 'ICEBERG'::text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    deleted_at timestamp with time zone
);


--
-- Name: buckets_vectors; Type: TABLE; Schema: storage; Owner: -
--

CREATE TABLE storage.buckets_vectors (
    id text NOT NULL,
    type storage.buckettype DEFAULT 'VECTOR'::storage.buckettype NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: iceberg_namespaces; Type: TABLE; Schema: storage; Owner: -
--

CREATE TABLE storage.iceberg_namespaces (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    bucket_name text NOT NULL,
    name text NOT NULL COLLATE pg_catalog."C",
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
    catalog_id uuid NOT NULL
);


--
-- Name: iceberg_tables; Type: TABLE; Schema: storage; Owner: -
--

CREATE TABLE storage.iceberg_tables (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    namespace_id uuid NOT NULL,
    bucket_name text NOT NULL,
    name text NOT NULL COLLATE pg_catalog."C",
    location text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    remote_table_id text,
    shard_key text,
    shard_id text,
    catalog_id uuid NOT NULL
);


--
-- Name: migrations; Type: TABLE; Schema: storage; Owner: -
--

CREATE TABLE storage.migrations (
    id integer NOT NULL,
    name character varying(100) NOT NULL,
    hash character varying(40) NOT NULL,
    executed_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP
);


--
-- Name: objects; Type: TABLE; Schema: storage; Owner: -
--

CREATE TABLE storage.objects (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    bucket_id text,
    name text,
    owner uuid,
    created_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone DEFAULT now(),
    last_accessed_at timestamp with time zone DEFAULT now(),
    metadata jsonb,
    path_tokens text[] GENERATED ALWAYS AS (string_to_array(name, '/'::text)) STORED,
    version text,
    owner_id text,
    user_metadata jsonb
);


--
-- Name: COLUMN objects.owner; Type: COMMENT; Schema: storage; Owner: -
--

COMMENT ON COLUMN storage.objects.owner IS 'Field is deprecated, use owner_id instead';


--
-- Name: s3_multipart_uploads; Type: TABLE; Schema: storage; Owner: -
--

CREATE TABLE storage.s3_multipart_uploads (
    id text NOT NULL,
    in_progress_size bigint DEFAULT 0 NOT NULL,
    upload_signature text NOT NULL,
    bucket_id text NOT NULL,
    key text NOT NULL COLLATE pg_catalog."C",
    version text NOT NULL,
    owner_id text,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    user_metadata jsonb
);


--
-- Name: s3_multipart_uploads_parts; Type: TABLE; Schema: storage; Owner: -
--

CREATE TABLE storage.s3_multipart_uploads_parts (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    upload_id text NOT NULL,
    size bigint DEFAULT 0 NOT NULL,
    part_number integer NOT NULL,
    bucket_id text NOT NULL,
    key text NOT NULL COLLATE pg_catalog."C",
    etag text NOT NULL,
    owner_id text,
    version text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: vector_indexes; Type: TABLE; Schema: storage; Owner: -
--

CREATE TABLE storage.vector_indexes (
    id text DEFAULT gen_random_uuid() NOT NULL,
    name text NOT NULL COLLATE pg_catalog."C",
    bucket_id text NOT NULL,
    data_type text NOT NULL,
    dimension integer NOT NULL,
    distance_metric text NOT NULL,
    metadata_configuration jsonb,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: hooks; Type: TABLE; Schema: supabase_functions; Owner: -
--

CREATE TABLE supabase_functions.hooks (
    id bigint NOT NULL,
    hook_table_id integer NOT NULL,
    hook_name text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL,
    request_id bigint
);


--
-- Name: TABLE hooks; Type: COMMENT; Schema: supabase_functions; Owner: -
--

COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';


--
-- Name: hooks_id_seq; Type: SEQUENCE; Schema: supabase_functions; Owner: -
--

CREATE SEQUENCE supabase_functions.hooks_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: hooks_id_seq; Type: SEQUENCE OWNED BY; Schema: supabase_functions; Owner: -
--

ALTER SEQUENCE supabase_functions.hooks_id_seq OWNED BY supabase_functions.hooks.id;


--
-- Name: migrations; Type: TABLE; Schema: supabase_functions; Owner: -
--

CREATE TABLE supabase_functions.migrations (
    version text NOT NULL,
    inserted_at timestamp with time zone DEFAULT now() NOT NULL
);


--
-- Name: messages_2026_05_08; Type: TABLE ATTACH; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages ATTACH PARTITION realtime.messages_2026_05_08 FOR VALUES FROM ('2026-05-08 00:00:00') TO ('2026-05-09 00:00:00');


--
-- Name: messages_2026_05_09; Type: TABLE ATTACH; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages ATTACH PARTITION realtime.messages_2026_05_09 FOR VALUES FROM ('2026-05-09 00:00:00') TO ('2026-05-10 00:00:00');


--
-- Name: messages_2026_05_10; Type: TABLE ATTACH; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages ATTACH PARTITION realtime.messages_2026_05_10 FOR VALUES FROM ('2026-05-10 00:00:00') TO ('2026-05-11 00:00:00');


--
-- Name: messages_2026_05_11; Type: TABLE ATTACH; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages ATTACH PARTITION realtime.messages_2026_05_11 FOR VALUES FROM ('2026-05-11 00:00:00') TO ('2026-05-12 00:00:00');


--
-- Name: messages_2026_05_12; Type: TABLE ATTACH; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages ATTACH PARTITION realtime.messages_2026_05_12 FOR VALUES FROM ('2026-05-12 00:00:00') TO ('2026-05-13 00:00:00');


--
-- Name: messages_2026_05_13; Type: TABLE ATTACH; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages ATTACH PARTITION realtime.messages_2026_05_13 FOR VALUES FROM ('2026-05-13 00:00:00') TO ('2026-05-14 00:00:00');


--
-- Name: messages_2026_05_14; Type: TABLE ATTACH; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages ATTACH PARTITION realtime.messages_2026_05_14 FOR VALUES FROM ('2026-05-14 00:00:00') TO ('2026-05-15 00:00:00');


--
-- Name: refresh_tokens id; Type: DEFAULT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.refresh_tokens ALTER COLUMN id SET DEFAULT nextval('auth.refresh_tokens_id_seq'::regclass);


--
-- Name: _db_migrations id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public._db_migrations ALTER COLUMN id SET DEFAULT nextval('public._db_migrations_id_seq'::regclass);


--
-- Name: agenda_online_slots id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_online_slots ALTER COLUMN id SET DEFAULT nextval('public.agenda_online_slots_id_seq'::regclass);


--
-- Name: audit_logs id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.audit_logs ALTER COLUMN id SET DEFAULT nextval('public.audit_logs_id_seq'::regclass);


--
-- Name: conversation_autoreply_log id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_autoreply_log ALTER COLUMN id SET DEFAULT nextval('public.conversation_autoreply_log_id_seq'::regclass);


--
-- Name: conversation_messages id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_messages ALTER COLUMN id SET DEFAULT nextval('public.conversation_messages_id_seq'::regclass);


--
-- Name: dev_auditoria_items id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_auditoria_items ALTER COLUMN id SET DEFAULT nextval('public.dev_auditoria_items_id_seq'::regclass);


--
-- Name: dev_comparison_competitor_status id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_comparison_competitor_status ALTER COLUMN id SET DEFAULT nextval('public.dev_comparison_competitor_status_id_seq'::regclass);


--
-- Name: dev_comparison_matrix id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_comparison_matrix ALTER COLUMN id SET DEFAULT nextval('public.dev_comparison_matrix_id_seq'::regclass);


--
-- Name: dev_competitor_features id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_competitor_features ALTER COLUMN id SET DEFAULT nextval('public.dev_competitor_features_id_seq'::regclass);


--
-- Name: dev_competitors id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_competitors ALTER COLUMN id SET DEFAULT nextval('public.dev_competitors_id_seq'::regclass);


--
-- Name: dev_generation_log id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_generation_log ALTER COLUMN id SET DEFAULT nextval('public.dev_generation_log_id_seq'::regclass);


--
-- Name: dev_roadmap_items id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_roadmap_items ALTER COLUMN id SET DEFAULT nextval('public.dev_roadmap_items_id_seq'::regclass);


--
-- Name: dev_roadmap_phases id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_roadmap_phases ALTER COLUMN id SET DEFAULT nextval('public.dev_roadmap_phases_id_seq'::regclass);


--
-- Name: dev_test_items id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_test_items ALTER COLUMN id SET DEFAULT nextval('public.dev_test_items_id_seq'::regclass);


--
-- Name: dev_verificacoes_items id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_verificacoes_items ALTER COLUMN id SET DEFAULT nextval('public.dev_verificacoes_items_id_seq'::regclass);


--
-- Name: session_reminder_logs id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.session_reminder_logs ALTER COLUMN id SET DEFAULT nextval('public.session_reminder_logs_id_seq'::regclass);


--
-- Name: whatsapp_credits_transactions id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credits_transactions ALTER COLUMN id SET DEFAULT nextval('public.whatsapp_credits_transactions_id_seq'::regclass);


--
-- Name: hooks id; Type: DEFAULT; Schema: supabase_functions; Owner: -
--

ALTER TABLE ONLY supabase_functions.hooks ALTER COLUMN id SET DEFAULT nextval('supabase_functions.hooks_id_seq'::regclass);


--
-- Name: extensions extensions_pkey; Type: CONSTRAINT; Schema: _realtime; Owner: -
--

ALTER TABLE ONLY _realtime.extensions
    ADD CONSTRAINT extensions_pkey PRIMARY KEY (id);


--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: _realtime; Owner: -
--

ALTER TABLE ONLY _realtime.schema_migrations
    ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);


--
-- Name: tenants tenants_pkey; Type: CONSTRAINT; Schema: _realtime; Owner: -
--

ALTER TABLE ONLY _realtime.tenants
    ADD CONSTRAINT tenants_pkey PRIMARY KEY (id);


--
-- Name: mfa_amr_claims amr_id_pk; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.mfa_amr_claims
    ADD CONSTRAINT amr_id_pk PRIMARY KEY (id);


--
-- Name: audit_log_entries audit_log_entries_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.audit_log_entries
    ADD CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id);


--
-- Name: flow_state flow_state_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.flow_state
    ADD CONSTRAINT flow_state_pkey PRIMARY KEY (id);


--
-- Name: identities identities_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.identities
    ADD CONSTRAINT identities_pkey PRIMARY KEY (id);


--
-- Name: identities identities_provider_id_provider_unique; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.identities
    ADD CONSTRAINT identities_provider_id_provider_unique UNIQUE (provider_id, provider);


--
-- Name: instances instances_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.instances
    ADD CONSTRAINT instances_pkey PRIMARY KEY (id);


--
-- Name: mfa_amr_claims mfa_amr_claims_session_id_authentication_method_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.mfa_amr_claims
    ADD CONSTRAINT mfa_amr_claims_session_id_authentication_method_pkey UNIQUE (session_id, authentication_method);


--
-- Name: mfa_challenges mfa_challenges_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.mfa_challenges
    ADD CONSTRAINT mfa_challenges_pkey PRIMARY KEY (id);


--
-- Name: mfa_factors mfa_factors_last_challenged_at_key; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.mfa_factors
    ADD CONSTRAINT mfa_factors_last_challenged_at_key UNIQUE (last_challenged_at);


--
-- Name: mfa_factors mfa_factors_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.mfa_factors
    ADD CONSTRAINT mfa_factors_pkey PRIMARY KEY (id);


--
-- Name: oauth_authorizations oauth_authorizations_authorization_code_key; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_authorizations
    ADD CONSTRAINT oauth_authorizations_authorization_code_key UNIQUE (authorization_code);


--
-- Name: oauth_authorizations oauth_authorizations_authorization_id_key; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_authorizations
    ADD CONSTRAINT oauth_authorizations_authorization_id_key UNIQUE (authorization_id);


--
-- Name: oauth_authorizations oauth_authorizations_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_authorizations
    ADD CONSTRAINT oauth_authorizations_pkey PRIMARY KEY (id);


--
-- Name: oauth_client_states oauth_client_states_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_client_states
    ADD CONSTRAINT oauth_client_states_pkey PRIMARY KEY (id);


--
-- Name: oauth_clients oauth_clients_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_clients
    ADD CONSTRAINT oauth_clients_pkey PRIMARY KEY (id);


--
-- Name: oauth_consents oauth_consents_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_consents
    ADD CONSTRAINT oauth_consents_pkey PRIMARY KEY (id);


--
-- Name: oauth_consents oauth_consents_user_client_unique; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_consents
    ADD CONSTRAINT oauth_consents_user_client_unique UNIQUE (user_id, client_id);


--
-- Name: one_time_tokens one_time_tokens_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.one_time_tokens
    ADD CONSTRAINT one_time_tokens_pkey PRIMARY KEY (id);


--
-- Name: refresh_tokens refresh_tokens_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.refresh_tokens
    ADD CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id);


--
-- Name: refresh_tokens refresh_tokens_token_unique; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.refresh_tokens
    ADD CONSTRAINT refresh_tokens_token_unique UNIQUE (token);


--
-- Name: saml_providers saml_providers_entity_id_key; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.saml_providers
    ADD CONSTRAINT saml_providers_entity_id_key UNIQUE (entity_id);


--
-- Name: saml_providers saml_providers_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.saml_providers
    ADD CONSTRAINT saml_providers_pkey PRIMARY KEY (id);


--
-- Name: saml_relay_states saml_relay_states_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.saml_relay_states
    ADD CONSTRAINT saml_relay_states_pkey PRIMARY KEY (id);


--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.schema_migrations
    ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);


--
-- Name: sessions sessions_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.sessions
    ADD CONSTRAINT sessions_pkey PRIMARY KEY (id);


--
-- Name: sso_domains sso_domains_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.sso_domains
    ADD CONSTRAINT sso_domains_pkey PRIMARY KEY (id);


--
-- Name: sso_providers sso_providers_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.sso_providers
    ADD CONSTRAINT sso_providers_pkey PRIMARY KEY (id);


--
-- Name: users users_phone_key; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.users
    ADD CONSTRAINT users_phone_key UNIQUE (phone);


--
-- Name: users users_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.users
    ADD CONSTRAINT users_pkey PRIMARY KEY (id);


--
-- Name: _db_migrations _db_migrations_filename_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public._db_migrations
    ADD CONSTRAINT _db_migrations_filename_key UNIQUE (filename);


--
-- Name: _db_migrations _db_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public._db_migrations
    ADD CONSTRAINT _db_migrations_pkey PRIMARY KEY (id);


--
-- Name: addon_credits addon_credits_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_credits
    ADD CONSTRAINT addon_credits_pkey PRIMARY KEY (id);


--
-- Name: addon_products addon_products_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_products
    ADD CONSTRAINT addon_products_pkey PRIMARY KEY (id);


--
-- Name: addon_products addon_products_slug_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_products
    ADD CONSTRAINT addon_products_slug_key UNIQUE (slug);


--
-- Name: addon_transactions addon_transactions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_transactions
    ADD CONSTRAINT addon_transactions_pkey PRIMARY KEY (id);


--
-- Name: agenda_bloqueios agenda_bloqueios_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_bloqueios
    ADD CONSTRAINT agenda_bloqueios_pkey PRIMARY KEY (id);


--
-- Name: agenda_configuracoes agenda_configuracoes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_configuracoes
    ADD CONSTRAINT agenda_configuracoes_pkey PRIMARY KEY (owner_id);


--
-- Name: agenda_eventos agenda_eventos_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_eventos
    ADD CONSTRAINT agenda_eventos_pkey PRIMARY KEY (id);


--
-- Name: agenda_eventos agenda_eventos_sem_sobreposicao; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_eventos
    ADD CONSTRAINT agenda_eventos_sem_sobreposicao EXCLUDE USING gist (owner_id WITH =, tstzrange(inicio_em, fim_em, '[)'::text) WITH &&);


--
-- Name: agenda_excecoes agenda_excecoes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_excecoes
    ADD CONSTRAINT agenda_excecoes_pkey PRIMARY KEY (id);


--
-- Name: agenda_online_slots agenda_online_slots_owner_id_weekday_time_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_online_slots
    ADD CONSTRAINT agenda_online_slots_owner_id_weekday_time_key UNIQUE (owner_id, weekday, "time");


--
-- Name: agenda_online_slots agenda_online_slots_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_online_slots
    ADD CONSTRAINT agenda_online_slots_pkey PRIMARY KEY (id);


--
-- Name: agenda_regras_semanais agenda_regras_semanais_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_regras_semanais
    ADD CONSTRAINT agenda_regras_semanais_pkey PRIMARY KEY (id);


--
-- Name: agenda_regras_semanais agenda_regras_semanais_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_regras_semanais
    ADD CONSTRAINT agenda_regras_semanais_unique UNIQUE (owner_id, dia_semana, hora_inicio, hora_fim, modalidade);


--
-- Name: agenda_slots_bloqueados_semanais agenda_slots_bloqueados_seman_owner_id_dia_semana_hora_inic_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_slots_bloqueados_semanais
    ADD CONSTRAINT agenda_slots_bloqueados_seman_owner_id_dia_semana_hora_inic_key UNIQUE (owner_id, dia_semana, hora_inicio);


--
-- Name: agenda_slots_bloqueados_semanais agenda_slots_bloqueados_semanais_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_slots_bloqueados_semanais
    ADD CONSTRAINT agenda_slots_bloqueados_semanais_pkey PRIMARY KEY (id);


--
-- Name: agenda_slots_regras agenda_slots_regras_owner_id_dia_semana_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_slots_regras
    ADD CONSTRAINT agenda_slots_regras_owner_id_dia_semana_key UNIQUE (owner_id, dia_semana);


--
-- Name: agenda_slots_regras agenda_slots_regras_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_slots_regras
    ADD CONSTRAINT agenda_slots_regras_pkey PRIMARY KEY (id);


--
-- Name: agendador_configuracoes agendador_configuracoes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agendador_configuracoes
    ADD CONSTRAINT agendador_configuracoes_pkey PRIMARY KEY (owner_id);


--
-- Name: agendador_solicitacoes agendador_solicitacoes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agendador_solicitacoes
    ADD CONSTRAINT agendador_solicitacoes_pkey PRIMARY KEY (id);


--
-- Name: audit_logs audit_logs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.audit_logs
    ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);


--
-- Name: billing_contracts billing_contracts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.billing_contracts
    ADD CONSTRAINT billing_contracts_pkey PRIMARY KEY (id);


--
-- Name: commitment_services commitment_services_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.commitment_services
    ADD CONSTRAINT commitment_services_pkey PRIMARY KEY (id);


--
-- Name: commitment_time_logs commitment_time_logs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.commitment_time_logs
    ADD CONSTRAINT commitment_time_logs_pkey PRIMARY KEY (id);


--
-- Name: company_profiles company_profiles_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.company_profiles
    ADD CONSTRAINT company_profiles_pkey PRIMARY KEY (id);


--
-- Name: company_profiles company_profiles_tenant_id_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.company_profiles
    ADD CONSTRAINT company_profiles_tenant_id_key UNIQUE (tenant_id);


--
-- Name: contact_email_types contact_email_types_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.contact_email_types
    ADD CONSTRAINT contact_email_types_pkey PRIMARY KEY (id);


--
-- Name: contact_emails contact_emails_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.contact_emails
    ADD CONSTRAINT contact_emails_pkey PRIMARY KEY (id);


--
-- Name: contact_phones contact_phones_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.contact_phones
    ADD CONSTRAINT contact_phones_pkey PRIMARY KEY (id);


--
-- Name: contact_types contact_types_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.contact_types
    ADD CONSTRAINT contact_types_pkey PRIMARY KEY (id);


--
-- Name: conversation_assignments conversation_assignments_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_assignments
    ADD CONSTRAINT conversation_assignments_pkey PRIMARY KEY (tenant_id, thread_key);


--
-- Name: conversation_autoreply_log conversation_autoreply_log_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_autoreply_log
    ADD CONSTRAINT conversation_autoreply_log_pkey PRIMARY KEY (id);


--
-- Name: conversation_autoreply_settings conversation_autoreply_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_autoreply_settings
    ADD CONSTRAINT conversation_autoreply_settings_pkey PRIMARY KEY (tenant_id);


--
-- Name: conversation_bot_sessions conversation_bot_sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_bot_sessions
    ADD CONSTRAINT conversation_bot_sessions_pkey PRIMARY KEY (id);


--
-- Name: conversation_bots conversation_bots_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_bots
    ADD CONSTRAINT conversation_bots_pkey PRIMARY KEY (tenant_id);


--
-- Name: conversation_messages conversation_messages_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_messages
    ADD CONSTRAINT conversation_messages_pkey PRIMARY KEY (id);


--
-- Name: conversation_notes conversation_notes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_notes
    ADD CONSTRAINT conversation_notes_pkey PRIMARY KEY (id);


--
-- Name: conversation_optout_keywords conversation_optout_keywords_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_optout_keywords
    ADD CONSTRAINT conversation_optout_keywords_pkey PRIMARY KEY (id);


--
-- Name: conversation_optouts conversation_optouts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_optouts
    ADD CONSTRAINT conversation_optouts_pkey PRIMARY KEY (id);


--
-- Name: conversation_sla_breaches conversation_sla_breaches_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_sla_breaches
    ADD CONSTRAINT conversation_sla_breaches_pkey PRIMARY KEY (id);


--
-- Name: conversation_sla_rules conversation_sla_rules_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_sla_rules
    ADD CONSTRAINT conversation_sla_rules_pkey PRIMARY KEY (tenant_id);


--
-- Name: conversation_tags conversation_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_tags
    ADD CONSTRAINT conversation_tags_pkey PRIMARY KEY (id);


--
-- Name: conversation_thread_tags conversation_thread_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_thread_tags
    ADD CONSTRAINT conversation_thread_tags_pkey PRIMARY KEY (tenant_id, thread_key, tag_id);


--
-- Name: determined_commitment_fields determined_commitment_fields_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.determined_commitment_fields
    ADD CONSTRAINT determined_commitment_fields_pkey PRIMARY KEY (id);


--
-- Name: determined_commitments determined_commitments_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.determined_commitments
    ADD CONSTRAINT determined_commitments_pkey PRIMARY KEY (id);


--
-- Name: determined_commitments determined_commitments_tenant_native_key_uq; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.determined_commitments
    ADD CONSTRAINT determined_commitments_tenant_native_key_uq UNIQUE (tenant_id, native_key);


--
-- Name: dev_auditoria_items dev_auditoria_items_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_auditoria_items
    ADD CONSTRAINT dev_auditoria_items_pkey PRIMARY KEY (id);


--
-- Name: dev_comparison_competitor_status dev_comparison_competitor_statu_comparison_id_competitor_id_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_comparison_competitor_status
    ADD CONSTRAINT dev_comparison_competitor_statu_comparison_id_competitor_id_key UNIQUE (comparison_id, competitor_id);


--
-- Name: dev_comparison_competitor_status dev_comparison_competitor_status_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_comparison_competitor_status
    ADD CONSTRAINT dev_comparison_competitor_status_pkey PRIMARY KEY (id);


--
-- Name: dev_comparison_matrix dev_comparison_matrix_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_comparison_matrix
    ADD CONSTRAINT dev_comparison_matrix_pkey PRIMARY KEY (id);


--
-- Name: dev_competitor_features dev_competitor_features_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_competitor_features
    ADD CONSTRAINT dev_competitor_features_pkey PRIMARY KEY (id);


--
-- Name: dev_competitors dev_competitors_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_competitors
    ADD CONSTRAINT dev_competitors_pkey PRIMARY KEY (id);


--
-- Name: dev_competitors dev_competitors_slug_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_competitors
    ADD CONSTRAINT dev_competitors_slug_key UNIQUE (slug);


--
-- Name: dev_generation_log dev_generation_log_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_generation_log
    ADD CONSTRAINT dev_generation_log_pkey PRIMARY KEY (id);


--
-- Name: dev_roadmap_items dev_roadmap_items_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_roadmap_items
    ADD CONSTRAINT dev_roadmap_items_pkey PRIMARY KEY (id);


--
-- Name: dev_roadmap_phases dev_roadmap_phases_numero_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_roadmap_phases
    ADD CONSTRAINT dev_roadmap_phases_numero_key UNIQUE (numero);


--
-- Name: dev_roadmap_phases dev_roadmap_phases_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_roadmap_phases
    ADD CONSTRAINT dev_roadmap_phases_pkey PRIMARY KEY (id);


--
-- Name: dev_test_items dev_test_items_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_test_items
    ADD CONSTRAINT dev_test_items_pkey PRIMARY KEY (id);


--
-- Name: dev_user_credentials dev_user_credentials_email_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_user_credentials
    ADD CONSTRAINT dev_user_credentials_email_key UNIQUE (email);


--
-- Name: dev_user_credentials dev_user_credentials_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_user_credentials
    ADD CONSTRAINT dev_user_credentials_pkey PRIMARY KEY (id);


--
-- Name: dev_verificacoes_items dev_verificacoes_items_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_verificacoes_items
    ADD CONSTRAINT dev_verificacoes_items_pkey PRIMARY KEY (id);


--
-- Name: document_access_logs document_access_logs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_access_logs
    ADD CONSTRAINT document_access_logs_pkey PRIMARY KEY (id);


--
-- Name: document_generated document_generated_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_generated
    ADD CONSTRAINT document_generated_pkey PRIMARY KEY (id);


--
-- Name: document_share_links document_share_links_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_share_links
    ADD CONSTRAINT document_share_links_pkey PRIMARY KEY (id);


--
-- Name: document_signatures document_signatures_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_signatures
    ADD CONSTRAINT document_signatures_pkey PRIMARY KEY (id);


--
-- Name: document_templates document_templates_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_templates
    ADD CONSTRAINT document_templates_pkey PRIMARY KEY (id);


--
-- Name: documents documents_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.documents
    ADD CONSTRAINT documents_pkey PRIMARY KEY (id);


--
-- Name: document_share_links dsl_token_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_share_links
    ADD CONSTRAINT dsl_token_unique UNIQUE (token);


--
-- Name: email_layout_config email_layout_config_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.email_layout_config
    ADD CONSTRAINT email_layout_config_pkey PRIMARY KEY (id);


--
-- Name: email_layout_config email_layout_config_tenant_id_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.email_layout_config
    ADD CONSTRAINT email_layout_config_tenant_id_key UNIQUE (tenant_id);


--
-- Name: email_templates_global email_templates_global_key_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.email_templates_global
    ADD CONSTRAINT email_templates_global_key_key UNIQUE (key);


--
-- Name: email_templates_global email_templates_global_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.email_templates_global
    ADD CONSTRAINT email_templates_global_pkey PRIMARY KEY (id);


--
-- Name: email_templates_tenant email_templates_tenant_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.email_templates_tenant
    ADD CONSTRAINT email_templates_tenant_pkey PRIMARY KEY (id);


--
-- Name: email_templates_tenant email_templates_tenant_tenant_id_owner_id_template_key_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.email_templates_tenant
    ADD CONSTRAINT email_templates_tenant_tenant_id_owner_id_template_key_key UNIQUE (tenant_id, owner_id, template_key);


--
-- Name: entitlements_invalidation entitlements_invalidation_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.entitlements_invalidation
    ADD CONSTRAINT entitlements_invalidation_pkey PRIMARY KEY (owner_id);


--
-- Name: features features_key_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.features
    ADD CONSTRAINT features_key_key UNIQUE (key);


--
-- Name: features features_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.features
    ADD CONSTRAINT features_pkey PRIMARY KEY (id);


--
-- Name: feriados feriados_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.feriados
    ADD CONSTRAINT feriados_pkey PRIMARY KEY (id);


--
-- Name: feriados feriados_tenant_id_data_nome_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.feriados
    ADD CONSTRAINT feriados_tenant_id_data_nome_key UNIQUE (tenant_id, data, nome);


--
-- Name: financial_categories financial_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_categories
    ADD CONSTRAINT financial_categories_pkey PRIMARY KEY (id);


--
-- Name: financial_exceptions financial_exceptions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_exceptions
    ADD CONSTRAINT financial_exceptions_pkey PRIMARY KEY (id);


--
-- Name: financial_records financial_records_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_records
    ADD CONSTRAINT financial_records_pkey PRIMARY KEY (id);


--
-- Name: global_notices global_notices_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.global_notices
    ADD CONSTRAINT global_notices_pkey PRIMARY KEY (id);


--
-- Name: insurance_plan_services insurance_plan_services_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.insurance_plan_services
    ADD CONSTRAINT insurance_plan_services_pkey PRIMARY KEY (id);


--
-- Name: insurance_plans insurance_plans_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.insurance_plans
    ADD CONSTRAINT insurance_plans_pkey PRIMARY KEY (id);


--
-- Name: login_carousel_slides login_carousel_slides_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.login_carousel_slides
    ADD CONSTRAINT login_carousel_slides_pkey PRIMARY KEY (id);


--
-- Name: math_challenges math_challenges_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.math_challenges
    ADD CONSTRAINT math_challenges_pkey PRIMARY KEY (id);


--
-- Name: medicos medicos_crm_owner_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.medicos
    ADD CONSTRAINT medicos_crm_owner_unique UNIQUE NULLS NOT DISTINCT (owner_id, crm);


--
-- Name: medicos medicos_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.medicos
    ADD CONSTRAINT medicos_pkey PRIMARY KEY (id);


--
-- Name: module_features module_features_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.module_features
    ADD CONSTRAINT module_features_pkey PRIMARY KEY (module_id, feature_id);


--
-- Name: modules modules_key_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.modules
    ADD CONSTRAINT modules_key_key UNIQUE (key);


--
-- Name: modules modules_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.modules
    ADD CONSTRAINT modules_pkey PRIMARY KEY (id);


--
-- Name: notice_dismissals notice_dismissals_notice_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notice_dismissals
    ADD CONSTRAINT notice_dismissals_notice_id_user_id_key UNIQUE (notice_id, user_id);


--
-- Name: notice_dismissals notice_dismissals_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notice_dismissals
    ADD CONSTRAINT notice_dismissals_pkey PRIMARY KEY (id);


--
-- Name: notification_channels notification_channels_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_channels
    ADD CONSTRAINT notification_channels_pkey PRIMARY KEY (id);


--
-- Name: notification_logs notification_logs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_logs
    ADD CONSTRAINT notification_logs_pkey PRIMARY KEY (id);


--
-- Name: notification_preferences notification_preferences_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_preferences
    ADD CONSTRAINT notification_preferences_pkey PRIMARY KEY (id);


--
-- Name: notification_queue notification_queue_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_queue
    ADD CONSTRAINT notification_queue_pkey PRIMARY KEY (id);


--
-- Name: notification_schedules notification_schedules_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_schedules
    ADD CONSTRAINT notification_schedules_pkey PRIMARY KEY (id);


--
-- Name: notification_templates notification_templates_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_templates
    ADD CONSTRAINT notification_templates_pkey PRIMARY KEY (id);


--
-- Name: notifications notifications_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notifications
    ADD CONSTRAINT notifications_pkey PRIMARY KEY (id);


--
-- Name: owner_users owner_users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.owner_users
    ADD CONSTRAINT owner_users_pkey PRIMARY KEY (owner_id, user_id);


--
-- Name: patient_contacts patient_contacts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_contacts
    ADD CONSTRAINT patient_contacts_pkey PRIMARY KEY (id);


--
-- Name: patient_discounts patient_discounts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_discounts
    ADD CONSTRAINT patient_discounts_pkey PRIMARY KEY (id);


--
-- Name: patient_group_patient patient_group_patient_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_group_patient
    ADD CONSTRAINT patient_group_patient_pkey PRIMARY KEY (patient_group_id, patient_id);


--
-- Name: patient_groups patient_groups_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_groups
    ADD CONSTRAINT patient_groups_pkey PRIMARY KEY (id);


--
-- Name: patient_intake_requests patient_intake_requests_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_intake_requests
    ADD CONSTRAINT patient_intake_requests_pkey PRIMARY KEY (id);


--
-- Name: patient_invite_attempts patient_invite_attempts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_invite_attempts
    ADD CONSTRAINT patient_invite_attempts_pkey PRIMARY KEY (id);


--
-- Name: patient_invites patient_invites_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_invites
    ADD CONSTRAINT patient_invites_pkey PRIMARY KEY (id);


--
-- Name: patient_invites patient_invites_token_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_invites
    ADD CONSTRAINT patient_invites_token_key UNIQUE (token);


--
-- Name: patient_patient_tag patient_patient_tag_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_patient_tag
    ADD CONSTRAINT patient_patient_tag_pkey PRIMARY KEY (patient_id, tag_id);


--
-- Name: patient_status_history patient_status_history_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_status_history
    ADD CONSTRAINT patient_status_history_pkey PRIMARY KEY (id);


--
-- Name: patient_support_contacts patient_support_contacts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_support_contacts
    ADD CONSTRAINT patient_support_contacts_pkey PRIMARY KEY (id);


--
-- Name: patient_tags patient_tags_owner_name_uniq; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_tags
    ADD CONSTRAINT patient_tags_owner_name_uniq UNIQUE (owner_id, nome);


--
-- Name: patient_tags patient_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_tags
    ADD CONSTRAINT patient_tags_pkey PRIMARY KEY (id);


--
-- Name: patient_timeline patient_timeline_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_timeline
    ADD CONSTRAINT patient_timeline_pkey PRIMARY KEY (id);


--
-- Name: patients patients_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patients
    ADD CONSTRAINT patients_pkey PRIMARY KEY (id);


--
-- Name: payment_settings payment_settings_owner_id_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.payment_settings
    ADD CONSTRAINT payment_settings_owner_id_key UNIQUE (owner_id);


--
-- Name: payment_settings payment_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.payment_settings
    ADD CONSTRAINT payment_settings_pkey PRIMARY KEY (id);


--
-- Name: plan_features plan_features_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plan_features
    ADD CONSTRAINT plan_features_pkey PRIMARY KEY (plan_id, feature_id);


--
-- Name: plan_prices plan_prices_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plan_prices
    ADD CONSTRAINT plan_prices_pkey PRIMARY KEY (id);


--
-- Name: plan_public_bullets plan_public_bullets_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plan_public_bullets
    ADD CONSTRAINT plan_public_bullets_pkey PRIMARY KEY (id);


--
-- Name: plan_public plan_public_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plan_public
    ADD CONSTRAINT plan_public_pkey PRIMARY KEY (plan_id);


--
-- Name: plans plans_key_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plans
    ADD CONSTRAINT plans_key_key UNIQUE (key);


--
-- Name: plans plans_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plans
    ADD CONSTRAINT plans_pkey PRIMARY KEY (id);


--
-- Name: professional_pricing professional_pricing_owner_commitment_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.professional_pricing
    ADD CONSTRAINT professional_pricing_owner_commitment_key UNIQUE (owner_id, determined_commitment_id);


--
-- Name: professional_pricing professional_pricing_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.professional_pricing
    ADD CONSTRAINT professional_pricing_pkey PRIMARY KEY (id);


--
-- Name: profiles profiles_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.profiles
    ADD CONSTRAINT profiles_pkey PRIMARY KEY (id);


--
-- Name: public_submission_attempts public_submission_attempts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.public_submission_attempts
    ADD CONSTRAINT public_submission_attempts_pkey PRIMARY KEY (id);


--
-- Name: recurrence_exceptions recurrence_exceptions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.recurrence_exceptions
    ADD CONSTRAINT recurrence_exceptions_pkey PRIMARY KEY (id);


--
-- Name: recurrence_exceptions recurrence_exceptions_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.recurrence_exceptions
    ADD CONSTRAINT recurrence_exceptions_unique UNIQUE (recurrence_id, original_date);


--
-- Name: recurrence_rule_services recurrence_rule_services_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.recurrence_rule_services
    ADD CONSTRAINT recurrence_rule_services_pkey PRIMARY KEY (id);


--
-- Name: recurrence_rules recurrence_rules_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.recurrence_rules
    ADD CONSTRAINT recurrence_rules_pkey PRIMARY KEY (id);


--
-- Name: saas_admins saas_admins_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_admins
    ADD CONSTRAINT saas_admins_pkey PRIMARY KEY (user_id);


--
-- Name: saas_doc_votos saas_doc_votos_doc_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_doc_votos
    ADD CONSTRAINT saas_doc_votos_doc_id_user_id_key UNIQUE (doc_id, user_id);


--
-- Name: saas_doc_votos saas_doc_votos_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_doc_votos
    ADD CONSTRAINT saas_doc_votos_pkey PRIMARY KEY (id);


--
-- Name: saas_docs saas_docs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_docs
    ADD CONSTRAINT saas_docs_pkey PRIMARY KEY (id);


--
-- Name: saas_faq_itens saas_faq_itens_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_faq_itens
    ADD CONSTRAINT saas_faq_itens_pkey PRIMARY KEY (id);


--
-- Name: saas_faq saas_faq_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_faq
    ADD CONSTRAINT saas_faq_pkey PRIMARY KEY (id);


--
-- Name: saas_security_config saas_security_config_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_security_config
    ADD CONSTRAINT saas_security_config_pkey PRIMARY KEY (id);


--
-- Name: saas_twilio_config saas_twilio_config_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_twilio_config
    ADD CONSTRAINT saas_twilio_config_pkey PRIMARY KEY (id);


--
-- Name: services services_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.services
    ADD CONSTRAINT services_pkey PRIMARY KEY (id);


--
-- Name: session_reminder_logs session_reminder_logs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.session_reminder_logs
    ADD CONSTRAINT session_reminder_logs_pkey PRIMARY KEY (id);


--
-- Name: session_reminder_settings session_reminder_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.session_reminder_settings
    ADD CONSTRAINT session_reminder_settings_pkey PRIMARY KEY (tenant_id);


--
-- Name: submission_rate_limits submission_rate_limits_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.submission_rate_limits
    ADD CONSTRAINT submission_rate_limits_pkey PRIMARY KEY (ip_hash, endpoint);


--
-- Name: subscription_events subscription_events_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscription_events
    ADD CONSTRAINT subscription_events_pkey PRIMARY KEY (id);


--
-- Name: subscription_intents_personal subscription_intents_personal_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscription_intents_personal
    ADD CONSTRAINT subscription_intents_personal_pkey PRIMARY KEY (id);


--
-- Name: subscription_intents_legacy subscription_intents_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscription_intents_legacy
    ADD CONSTRAINT subscription_intents_pkey PRIMARY KEY (id);


--
-- Name: subscription_intents_tenant subscription_intents_tenant_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscription_intents_tenant
    ADD CONSTRAINT subscription_intents_tenant_pkey PRIMARY KEY (id);


--
-- Name: subscriptions subscriptions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscriptions
    ADD CONSTRAINT subscriptions_pkey PRIMARY KEY (id);


--
-- Name: support_sessions support_sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.support_sessions
    ADD CONSTRAINT support_sessions_pkey PRIMARY KEY (id);


--
-- Name: support_sessions support_sessions_token_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.support_sessions
    ADD CONSTRAINT support_sessions_token_unique UNIQUE (token);


--
-- Name: tenant_feature_exceptions_log tenant_feature_exceptions_log_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_feature_exceptions_log
    ADD CONSTRAINT tenant_feature_exceptions_log_pkey PRIMARY KEY (id);


--
-- Name: tenant_features tenant_features_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_features
    ADD CONSTRAINT tenant_features_pkey PRIMARY KEY (tenant_id, feature_key);


--
-- Name: tenant_invites tenant_invites_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_invites
    ADD CONSTRAINT tenant_invites_pkey PRIMARY KEY (id);


--
-- Name: tenant_members tenant_members_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_members
    ADD CONSTRAINT tenant_members_pkey PRIMARY KEY (id);


--
-- Name: tenant_members tenant_members_tenant_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_members
    ADD CONSTRAINT tenant_members_tenant_id_user_id_key UNIQUE (tenant_id, user_id);


--
-- Name: tenant_modules tenant_modules_owner_id_module_id_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_modules
    ADD CONSTRAINT tenant_modules_owner_id_module_id_key UNIQUE (owner_id, module_id);


--
-- Name: tenant_modules tenant_modules_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_modules
    ADD CONSTRAINT tenant_modules_pkey PRIMARY KEY (id);


--
-- Name: tenants tenants_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenants
    ADD CONSTRAINT tenants_pkey PRIMARY KEY (id);


--
-- Name: therapist_payout_records therapist_payout_records_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.therapist_payout_records
    ADD CONSTRAINT therapist_payout_records_pkey PRIMARY KEY (payout_id, financial_record_id);


--
-- Name: therapist_payouts therapist_payouts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.therapist_payouts
    ADD CONSTRAINT therapist_payouts_pkey PRIMARY KEY (id);


--
-- Name: twilio_subaccount_usage twilio_subaccount_usage_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.twilio_subaccount_usage
    ADD CONSTRAINT twilio_subaccount_usage_pkey PRIMARY KEY (id);


--
-- Name: addon_credits uq_addon_credits_tenant_type; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_credits
    ADD CONSTRAINT uq_addon_credits_tenant_type UNIQUE (tenant_id, addon_type);


--
-- Name: notification_channels uq_channel_per_owner; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_channels
    ADD CONSTRAINT uq_channel_per_owner UNIQUE NULLS NOT DISTINCT (owner_id, channel, deleted_at);


--
-- Name: notification_preferences uq_notif_prefs_patient_owner; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_preferences
    ADD CONSTRAINT uq_notif_prefs_patient_owner UNIQUE NULLS NOT DISTINCT (owner_id, patient_id, deleted_at);


--
-- Name: notification_queue uq_notif_queue_idempotency; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_queue
    ADD CONSTRAINT uq_notif_queue_idempotency UNIQUE (idempotency_key);


--
-- Name: notification_schedules uq_notif_schedule_owner; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_schedules
    ADD CONSTRAINT uq_notif_schedule_owner UNIQUE NULLS NOT DISTINCT (owner_id, schedule_key, deleted_at);


--
-- Name: notification_templates uq_notif_template_key; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_templates
    ADD CONSTRAINT uq_notif_template_key UNIQUE NULLS NOT DISTINCT (tenant_id, owner_id, key, deleted_at);


--
-- Name: user_settings user_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.user_settings
    ADD CONSTRAINT user_settings_pkey PRIMARY KEY (user_id);


--
-- Name: whatsapp_connection_incidents whatsapp_connection_incidents_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_connection_incidents
    ADD CONSTRAINT whatsapp_connection_incidents_pkey PRIMARY KEY (id);


--
-- Name: whatsapp_credit_packages whatsapp_credit_packages_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credit_packages
    ADD CONSTRAINT whatsapp_credit_packages_pkey PRIMARY KEY (id);


--
-- Name: whatsapp_credit_purchases whatsapp_credit_purchases_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credit_purchases
    ADD CONSTRAINT whatsapp_credit_purchases_pkey PRIMARY KEY (id);


--
-- Name: whatsapp_credits_balance whatsapp_credits_balance_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credits_balance
    ADD CONSTRAINT whatsapp_credits_balance_pkey PRIMARY KEY (tenant_id);


--
-- Name: whatsapp_credits_transactions whatsapp_credits_transactions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credits_transactions
    ADD CONSTRAINT whatsapp_credits_transactions_pkey PRIMARY KEY (id);


--
-- Name: messages messages_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages
    ADD CONSTRAINT messages_pkey PRIMARY KEY (id, inserted_at);


--
-- Name: messages_2026_05_08 messages_2026_05_08_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages_2026_05_08
    ADD CONSTRAINT messages_2026_05_08_pkey PRIMARY KEY (id, inserted_at);


--
-- Name: messages_2026_05_09 messages_2026_05_09_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages_2026_05_09
    ADD CONSTRAINT messages_2026_05_09_pkey PRIMARY KEY (id, inserted_at);


--
-- Name: messages_2026_05_10 messages_2026_05_10_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages_2026_05_10
    ADD CONSTRAINT messages_2026_05_10_pkey PRIMARY KEY (id, inserted_at);


--
-- Name: messages_2026_05_11 messages_2026_05_11_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages_2026_05_11
    ADD CONSTRAINT messages_2026_05_11_pkey PRIMARY KEY (id, inserted_at);


--
-- Name: messages_2026_05_12 messages_2026_05_12_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages_2026_05_12
    ADD CONSTRAINT messages_2026_05_12_pkey PRIMARY KEY (id, inserted_at);


--
-- Name: messages_2026_05_13 messages_2026_05_13_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages_2026_05_13
    ADD CONSTRAINT messages_2026_05_13_pkey PRIMARY KEY (id, inserted_at);


--
-- Name: messages_2026_05_14 messages_2026_05_14_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.messages_2026_05_14
    ADD CONSTRAINT messages_2026_05_14_pkey PRIMARY KEY (id, inserted_at);


--
-- Name: subscription pk_subscription; Type: CONSTRAINT; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.subscription
    ADD CONSTRAINT pk_subscription PRIMARY KEY (id);


--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--

ALTER TABLE ONLY realtime.schema_migrations
    ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);


--
-- Name: buckets_analytics buckets_analytics_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.buckets_analytics
    ADD CONSTRAINT buckets_analytics_pkey PRIMARY KEY (id);


--
-- Name: buckets buckets_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.buckets
    ADD CONSTRAINT buckets_pkey PRIMARY KEY (id);


--
-- Name: buckets_vectors buckets_vectors_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.buckets_vectors
    ADD CONSTRAINT buckets_vectors_pkey PRIMARY KEY (id);


--
-- Name: iceberg_namespaces iceberg_namespaces_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.iceberg_namespaces
    ADD CONSTRAINT iceberg_namespaces_pkey PRIMARY KEY (id);


--
-- Name: iceberg_tables iceberg_tables_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.iceberg_tables
    ADD CONSTRAINT iceberg_tables_pkey PRIMARY KEY (id);


--
-- Name: migrations migrations_name_key; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.migrations
    ADD CONSTRAINT migrations_name_key UNIQUE (name);


--
-- Name: migrations migrations_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.migrations
    ADD CONSTRAINT migrations_pkey PRIMARY KEY (id);


--
-- Name: objects objects_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.objects
    ADD CONSTRAINT objects_pkey PRIMARY KEY (id);


--
-- Name: s3_multipart_uploads_parts s3_multipart_uploads_parts_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.s3_multipart_uploads_parts
    ADD CONSTRAINT s3_multipart_uploads_parts_pkey PRIMARY KEY (id);


--
-- Name: s3_multipart_uploads s3_multipart_uploads_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.s3_multipart_uploads
    ADD CONSTRAINT s3_multipart_uploads_pkey PRIMARY KEY (id);


--
-- Name: vector_indexes vector_indexes_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.vector_indexes
    ADD CONSTRAINT vector_indexes_pkey PRIMARY KEY (id);


--
-- Name: hooks hooks_pkey; Type: CONSTRAINT; Schema: supabase_functions; Owner: -
--

ALTER TABLE ONLY supabase_functions.hooks
    ADD CONSTRAINT hooks_pkey PRIMARY KEY (id);


--
-- Name: migrations migrations_pkey; Type: CONSTRAINT; Schema: supabase_functions; Owner: -
--

ALTER TABLE ONLY supabase_functions.migrations
    ADD CONSTRAINT migrations_pkey PRIMARY KEY (version);


--
-- Name: extensions_tenant_external_id_index; Type: INDEX; Schema: _realtime; Owner: -
--

CREATE INDEX extensions_tenant_external_id_index ON _realtime.extensions USING btree (tenant_external_id);


--
-- Name: extensions_tenant_external_id_type_index; Type: INDEX; Schema: _realtime; Owner: -
--

CREATE UNIQUE INDEX extensions_tenant_external_id_type_index ON _realtime.extensions USING btree (tenant_external_id, type);


--
-- Name: tenants_external_id_index; Type: INDEX; Schema: _realtime; Owner: -
--

CREATE UNIQUE INDEX tenants_external_id_index ON _realtime.tenants USING btree (external_id);


--
-- Name: audit_logs_instance_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id);


--
-- Name: confirmation_token_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX confirmation_token_idx ON auth.users USING btree (confirmation_token) WHERE ((confirmation_token)::text !~ '^[0-9 ]*$'::text);


--
-- Name: email_change_token_current_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX email_change_token_current_idx ON auth.users USING btree (email_change_token_current) WHERE ((email_change_token_current)::text !~ '^[0-9 ]*$'::text);


--
-- Name: email_change_token_new_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX email_change_token_new_idx ON auth.users USING btree (email_change_token_new) WHERE ((email_change_token_new)::text !~ '^[0-9 ]*$'::text);


--
-- Name: factor_id_created_at_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX factor_id_created_at_idx ON auth.mfa_factors USING btree (user_id, created_at);


--
-- Name: flow_state_created_at_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX flow_state_created_at_idx ON auth.flow_state USING btree (created_at DESC);


--
-- Name: identities_email_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX identities_email_idx ON auth.identities USING btree (email text_pattern_ops);


--
-- Name: INDEX identities_email_idx; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON INDEX auth.identities_email_idx IS 'Auth: Ensures indexed queries on the email column';


--
-- Name: identities_user_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX identities_user_id_idx ON auth.identities USING btree (user_id);


--
-- Name: idx_auth_code; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX idx_auth_code ON auth.flow_state USING btree (auth_code);


--
-- Name: idx_oauth_client_states_created_at; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX idx_oauth_client_states_created_at ON auth.oauth_client_states USING btree (created_at);


--
-- Name: idx_user_id_auth_method; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX idx_user_id_auth_method ON auth.flow_state USING btree (user_id, authentication_method);


--
-- Name: mfa_challenge_created_at_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX mfa_challenge_created_at_idx ON auth.mfa_challenges USING btree (created_at DESC);


--
-- Name: mfa_factors_user_friendly_name_unique; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX mfa_factors_user_friendly_name_unique ON auth.mfa_factors USING btree (friendly_name, user_id) WHERE (TRIM(BOTH FROM friendly_name) <> ''::text);


--
-- Name: mfa_factors_user_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX mfa_factors_user_id_idx ON auth.mfa_factors USING btree (user_id);


--
-- Name: oauth_auth_pending_exp_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX oauth_auth_pending_exp_idx ON auth.oauth_authorizations USING btree (expires_at) WHERE (status = 'pending'::auth.oauth_authorization_status);


--
-- Name: oauth_clients_deleted_at_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX oauth_clients_deleted_at_idx ON auth.oauth_clients USING btree (deleted_at);


--
-- Name: oauth_consents_active_client_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX oauth_consents_active_client_idx ON auth.oauth_consents USING btree (client_id) WHERE (revoked_at IS NULL);


--
-- Name: oauth_consents_active_user_client_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX oauth_consents_active_user_client_idx ON auth.oauth_consents USING btree (user_id, client_id) WHERE (revoked_at IS NULL);


--
-- Name: oauth_consents_user_order_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX oauth_consents_user_order_idx ON auth.oauth_consents USING btree (user_id, granted_at DESC);


--
-- Name: one_time_tokens_relates_to_hash_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX one_time_tokens_relates_to_hash_idx ON auth.one_time_tokens USING hash (relates_to);


--
-- Name: one_time_tokens_token_hash_hash_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX one_time_tokens_token_hash_hash_idx ON auth.one_time_tokens USING hash (token_hash);


--
-- Name: one_time_tokens_user_id_token_type_key; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX one_time_tokens_user_id_token_type_key ON auth.one_time_tokens USING btree (user_id, token_type);


--
-- Name: reauthentication_token_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX reauthentication_token_idx ON auth.users USING btree (reauthentication_token) WHERE ((reauthentication_token)::text !~ '^[0-9 ]*$'::text);


--
-- Name: recovery_token_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX recovery_token_idx ON auth.users USING btree (recovery_token) WHERE ((recovery_token)::text !~ '^[0-9 ]*$'::text);


--
-- Name: refresh_tokens_instance_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING btree (instance_id);


--
-- Name: refresh_tokens_instance_id_user_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_tokens USING btree (instance_id, user_id);


--
-- Name: refresh_tokens_parent_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX refresh_tokens_parent_idx ON auth.refresh_tokens USING btree (parent);


--
-- Name: refresh_tokens_session_id_revoked_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX refresh_tokens_session_id_revoked_idx ON auth.refresh_tokens USING btree (session_id, revoked);


--
-- Name: refresh_tokens_updated_at_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX refresh_tokens_updated_at_idx ON auth.refresh_tokens USING btree (updated_at DESC);


--
-- Name: saml_providers_sso_provider_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX saml_providers_sso_provider_id_idx ON auth.saml_providers USING btree (sso_provider_id);


--
-- Name: saml_relay_states_created_at_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX saml_relay_states_created_at_idx ON auth.saml_relay_states USING btree (created_at DESC);


--
-- Name: saml_relay_states_for_email_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX saml_relay_states_for_email_idx ON auth.saml_relay_states USING btree (for_email);


--
-- Name: saml_relay_states_sso_provider_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX saml_relay_states_sso_provider_id_idx ON auth.saml_relay_states USING btree (sso_provider_id);


--
-- Name: sessions_not_after_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX sessions_not_after_idx ON auth.sessions USING btree (not_after DESC);


--
-- Name: sessions_oauth_client_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX sessions_oauth_client_id_idx ON auth.sessions USING btree (oauth_client_id);


--
-- Name: sessions_user_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX sessions_user_id_idx ON auth.sessions USING btree (user_id);


--
-- Name: sso_domains_domain_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX sso_domains_domain_idx ON auth.sso_domains USING btree (lower(domain));


--
-- Name: sso_domains_sso_provider_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX sso_domains_sso_provider_id_idx ON auth.sso_domains USING btree (sso_provider_id);


--
-- Name: sso_providers_resource_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX sso_providers_resource_id_idx ON auth.sso_providers USING btree (lower(resource_id));


--
-- Name: sso_providers_resource_id_pattern_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX sso_providers_resource_id_pattern_idx ON auth.sso_providers USING btree (resource_id text_pattern_ops);


--
-- Name: unique_phone_factor_per_user; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX unique_phone_factor_per_user ON auth.mfa_factors USING btree (user_id, phone);


--
-- Name: user_id_created_at_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX user_id_created_at_idx ON auth.sessions USING btree (user_id, created_at);


--
-- Name: users_email_partial_key; Type: INDEX; Schema: auth; Owner: -
--

CREATE UNIQUE INDEX users_email_partial_key ON auth.users USING btree (email) WHERE (is_sso_user = false);


--
-- Name: INDEX users_email_partial_key; Type: COMMENT; Schema: auth; Owner: -
--

COMMENT ON INDEX auth.users_email_partial_key IS 'Auth: A partial unique index that applies only when is_sso_user is false';


--
-- Name: users_instance_id_email_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (instance_id, lower((email)::text));


--
-- Name: users_instance_id_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id);


--
-- Name: users_is_anonymous_idx; Type: INDEX; Schema: auth; Owner: -
--

CREATE INDEX users_is_anonymous_idx ON auth.users USING btree (is_anonymous);


--
-- Name: agenda_bloqueios_owner_data_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_bloqueios_owner_data_idx ON public.agenda_bloqueios USING btree (owner_id, data_inicio, data_fim);


--
-- Name: agenda_bloqueios_owner_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_bloqueios_owner_id_idx ON public.agenda_bloqueios USING btree (owner_id);


--
-- Name: agenda_bloqueios_recorrente_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_bloqueios_recorrente_idx ON public.agenda_bloqueios USING btree (owner_id, dia_semana) WHERE (recorrente = true);


--
-- Name: agenda_bloqueios_tenant_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_bloqueios_tenant_id_idx ON public.agenda_bloqueios USING btree (tenant_id);


--
-- Name: agenda_configuracoes_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_configuracoes_tenant_idx ON public.agenda_configuracoes USING btree (tenant_id);


--
-- Name: agenda_configuracoes_tenant_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_configuracoes_tenant_owner_idx ON public.agenda_configuracoes USING btree (tenant_id, owner_id);


--
-- Name: agenda_eventos_billing_contract_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_eventos_billing_contract_idx ON public.agenda_eventos USING btree (billing_contract_id) WHERE (billing_contract_id IS NOT NULL);


--
-- Name: agenda_eventos_insurance_plan_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_eventos_insurance_plan_idx ON public.agenda_eventos USING btree (insurance_plan_id);


--
-- Name: agenda_eventos_owner_inicio_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_eventos_owner_inicio_idx ON public.agenda_eventos USING btree (owner_id, inicio_em);


--
-- Name: agenda_eventos_owner_terapeuta_inicio_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_eventos_owner_terapeuta_inicio_idx ON public.agenda_eventos USING btree (owner_id, terapeuta_id, inicio_em);


--
-- Name: agenda_eventos_recurrence_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_eventos_recurrence_idx ON public.agenda_eventos USING btree (recurrence_id) WHERE (recurrence_id IS NOT NULL);


--
-- Name: agenda_eventos_tenant_inicio_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_eventos_tenant_inicio_idx ON public.agenda_eventos USING btree (tenant_id, inicio_em);


--
-- Name: agenda_eventos_tenant_owner_inicio_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_eventos_tenant_owner_inicio_idx ON public.agenda_eventos USING btree (tenant_id, owner_id, inicio_em);


--
-- Name: agenda_excecoes_owner_data_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_excecoes_owner_data_idx ON public.agenda_excecoes USING btree (owner_id, data);


--
-- Name: agenda_excecoes_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_excecoes_tenant_idx ON public.agenda_excecoes USING btree (tenant_id);


--
-- Name: agenda_excecoes_tenant_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_excecoes_tenant_owner_idx ON public.agenda_excecoes USING btree (tenant_id, owner_id);


--
-- Name: agenda_online_slots_owner_weekday_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_online_slots_owner_weekday_idx ON public.agenda_online_slots USING btree (owner_id, weekday);


--
-- Name: agenda_online_slots_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_online_slots_tenant_idx ON public.agenda_online_slots USING btree (tenant_id);


--
-- Name: agenda_online_slots_tenant_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_online_slots_tenant_owner_idx ON public.agenda_online_slots USING btree (tenant_id, owner_id);


--
-- Name: agenda_regras_semanais_owner_dia_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_regras_semanais_owner_dia_idx ON public.agenda_regras_semanais USING btree (owner_id, dia_semana);


--
-- Name: agenda_regras_semanais_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_regras_semanais_tenant_idx ON public.agenda_regras_semanais USING btree (tenant_id);


--
-- Name: agenda_regras_semanais_tenant_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_regras_semanais_tenant_owner_idx ON public.agenda_regras_semanais USING btree (tenant_id, owner_id);


--
-- Name: agenda_slots_bloqueados_semanais_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_slots_bloqueados_semanais_tenant_idx ON public.agenda_slots_bloqueados_semanais USING btree (tenant_id);


--
-- Name: agenda_slots_bloqueados_semanais_tenant_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_slots_bloqueados_semanais_tenant_owner_idx ON public.agenda_slots_bloqueados_semanais USING btree (tenant_id, owner_id);


--
-- Name: agenda_slots_regras_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_slots_regras_tenant_idx ON public.agenda_slots_regras USING btree (tenant_id);


--
-- Name: agenda_slots_regras_tenant_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agenda_slots_regras_tenant_owner_idx ON public.agenda_slots_regras USING btree (tenant_id, owner_id);


--
-- Name: agendador_cfg_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agendador_cfg_tenant_idx ON public.agendador_configuracoes USING btree (tenant_id);


--
-- Name: agendador_sol_data_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agendador_sol_data_idx ON public.agendador_solicitacoes USING btree (data_solicitada, hora_solicitada);


--
-- Name: agendador_sol_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agendador_sol_owner_idx ON public.agendador_solicitacoes USING btree (owner_id, status);


--
-- Name: agendador_sol_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX agendador_sol_tenant_idx ON public.agendador_solicitacoes USING btree (tenant_id);


--
-- Name: billing_contracts_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX billing_contracts_owner_idx ON public.billing_contracts USING btree (owner_id);


--
-- Name: billing_contracts_patient_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX billing_contracts_patient_idx ON public.billing_contracts USING btree (patient_id);


--
-- Name: billing_contracts_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX billing_contracts_tenant_idx ON public.billing_contracts USING btree (tenant_id);


--
-- Name: commitment_services_commitment_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX commitment_services_commitment_idx ON public.commitment_services USING btree (commitment_id);


--
-- Name: commitment_services_service_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX commitment_services_service_idx ON public.commitment_services USING btree (service_id);


--
-- Name: commitment_time_logs_calendar_event_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX commitment_time_logs_calendar_event_idx ON public.commitment_time_logs USING btree (calendar_event_id);


--
-- Name: commitment_time_logs_commitment_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX commitment_time_logs_commitment_idx ON public.commitment_time_logs USING btree (commitment_id, created_at DESC);


--
-- Name: commitment_time_logs_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX commitment_time_logs_tenant_idx ON public.commitment_time_logs USING btree (tenant_id, created_at DESC);


--
-- Name: dal_documento_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dal_documento_idx ON public.document_access_logs USING btree (documento_id, acessado_em DESC);


--
-- Name: dal_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dal_tenant_idx ON public.document_access_logs USING btree (tenant_id, acessado_em DESC);


--
-- Name: dal_user_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dal_user_idx ON public.document_access_logs USING btree (user_id, acessado_em DESC);


--
-- Name: determined_commitment_fields_commitment_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX determined_commitment_fields_commitment_idx ON public.determined_commitment_fields USING btree (commitment_id, sort_order);


--
-- Name: determined_commitment_fields_key_uniq; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX determined_commitment_fields_key_uniq ON public.determined_commitment_fields USING btree (commitment_id, key);


--
-- Name: determined_commitment_fields_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX determined_commitment_fields_tenant_idx ON public.determined_commitment_fields USING btree (tenant_id);


--
-- Name: determined_commitments_active_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX determined_commitments_active_idx ON public.determined_commitments USING btree (tenant_id, active);


--
-- Name: determined_commitments_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX determined_commitments_tenant_idx ON public.determined_commitments USING btree (tenant_id);


--
-- Name: determined_commitments_tenant_name_uniq; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX determined_commitments_tenant_name_uniq ON public.determined_commitments USING btree (tenant_id, lower(name));


--
-- Name: dg_gerado_por_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dg_gerado_por_idx ON public.document_generated USING btree (gerado_por, gerado_em DESC);


--
-- Name: dg_patient_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dg_patient_idx ON public.document_generated USING btree (patient_id, gerado_em DESC);


--
-- Name: dg_template_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dg_template_idx ON public.document_generated USING btree (template_id);


--
-- Name: dg_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dg_tenant_idx ON public.document_generated USING btree (tenant_id, gerado_em DESC);


--
-- Name: docs_active_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX docs_active_idx ON public.documents USING btree (patient_id, uploaded_at DESC) WHERE (deleted_at IS NULL);


--
-- Name: docs_nome_trgm_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX docs_nome_trgm_idx ON public.documents USING gin (nome_original public.gin_trgm_ops);


--
-- Name: docs_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX docs_owner_idx ON public.documents USING btree (owner_id);


--
-- Name: docs_patient_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX docs_patient_idx ON public.documents USING btree (patient_id);


--
-- Name: docs_tags_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX docs_tags_idx ON public.documents USING gin (tags);


--
-- Name: docs_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX docs_tenant_idx ON public.documents USING btree (tenant_id);


--
-- Name: docs_tipo_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX docs_tipo_idx ON public.documents USING btree (patient_id, tipo_documento);


--
-- Name: docs_uploaded_at_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX docs_uploaded_at_idx ON public.documents USING btree (patient_id, uploaded_at DESC);


--
-- Name: ds_documento_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX ds_documento_idx ON public.document_signatures USING btree (documento_id, ordem);


--
-- Name: ds_status_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX ds_status_idx ON public.document_signatures USING btree (documento_id, status);


--
-- Name: ds_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX ds_tenant_idx ON public.document_signatures USING btree (tenant_id);


--
-- Name: dsl_documento_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dsl_documento_idx ON public.document_share_links USING btree (documento_id);


--
-- Name: dsl_expira_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dsl_expira_idx ON public.document_share_links USING btree (expira_em) WHERE (ativo = true);


--
-- Name: dsl_token_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dsl_token_idx ON public.document_share_links USING btree (token) WHERE (ativo = true);


--
-- Name: dt_global_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dt_global_idx ON public.document_templates USING btree (is_global) WHERE (is_global = true);


--
-- Name: dt_nome_trgm_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dt_nome_trgm_idx ON public.document_templates USING gin (nome_template public.gin_trgm_ops);


--
-- Name: dt_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dt_owner_idx ON public.document_templates USING btree (owner_id);


--
-- Name: dt_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dt_tenant_idx ON public.document_templates USING btree (tenant_id);


--
-- Name: dt_tipo_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX dt_tipo_idx ON public.document_templates USING btree (tipo);


--
-- Name: feriados_global_unique; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX feriados_global_unique ON public.feriados USING btree (data, nome) WHERE (tenant_id IS NULL);


--
-- Name: financial_exceptions_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX financial_exceptions_owner_idx ON public.financial_exceptions USING btree (owner_id);


--
-- Name: financial_exceptions_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX financial_exceptions_tenant_idx ON public.financial_exceptions USING btree (tenant_id);


--
-- Name: idx_addon_credits_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_addon_credits_tenant ON public.addon_credits USING btree (tenant_id) WHERE (is_active = true);


--
-- Name: idx_addon_credits_type; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_addon_credits_type ON public.addon_credits USING btree (addon_type) WHERE (is_active = true);


--
-- Name: idx_addon_products_active; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_addon_products_active ON public.addon_products USING btree (is_active, is_visible) WHERE (deleted_at IS NULL);


--
-- Name: idx_addon_products_type; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_addon_products_type ON public.addon_products USING btree (addon_type) WHERE (deleted_at IS NULL);


--
-- Name: idx_addon_tx_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_addon_tx_created ON public.addon_transactions USING btree (created_at DESC);


--
-- Name: idx_addon_tx_queue; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_addon_tx_queue ON public.addon_transactions USING btree (queue_id) WHERE (queue_id IS NOT NULL);


--
-- Name: idx_addon_tx_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_addon_tx_tenant ON public.addon_transactions USING btree (tenant_id, addon_type);


--
-- Name: idx_addon_tx_type; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_addon_tx_type ON public.addon_transactions USING btree (type);


--
-- Name: idx_agenda_eventos_determined_commitment_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_agenda_eventos_determined_commitment_id ON public.agenda_eventos USING btree (determined_commitment_id);


--
-- Name: idx_agenda_eventos_titulo_custom_trgm; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_agenda_eventos_titulo_custom_trgm ON public.agenda_eventos USING gin (titulo_custom public.gin_trgm_ops) WHERE (titulo_custom IS NOT NULL);


--
-- Name: idx_agenda_eventos_titulo_trgm; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_agenda_eventos_titulo_trgm ON public.agenda_eventos USING gin (titulo public.gin_trgm_ops) WHERE (titulo IS NOT NULL);


--
-- Name: idx_agenda_excecoes_owner_data; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_agenda_excecoes_owner_data ON public.agenda_excecoes USING btree (owner_id, data);


--
-- Name: idx_agenda_slots_regras_owner_dia; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_agenda_slots_regras_owner_dia ON public.agenda_slots_regras USING btree (owner_id, dia_semana);


--
-- Name: idx_audit_logs_changed_fields; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_audit_logs_changed_fields ON public.audit_logs USING gin (changed_fields);


--
-- Name: idx_audit_logs_entity; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_audit_logs_entity ON public.audit_logs USING btree (entity_type, entity_id);


--
-- Name: idx_audit_logs_tenant_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_audit_logs_tenant_created ON public.audit_logs USING btree (tenant_id, created_at DESC);


--
-- Name: idx_audit_logs_user_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_audit_logs_user_created ON public.audit_logs USING btree (user_id, created_at DESC) WHERE (user_id IS NOT NULL);


--
-- Name: idx_autoreply_log_cooldown; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_autoreply_log_cooldown ON public.conversation_autoreply_log USING btree (tenant_id, thread_key, sent_at DESC);


--
-- Name: idx_bot_sessions_tenant_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_bot_sessions_tenant_status ON public.conversation_bot_sessions USING btree (tenant_id, status, started_at DESC);


--
-- Name: idx_contact_email_types_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_contact_email_types_tenant ON public.contact_email_types USING btree (tenant_id, "position");


--
-- Name: idx_contact_emails_email; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_contact_emails_email ON public.contact_emails USING btree (tenant_id, email);


--
-- Name: idx_contact_emails_entity; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_contact_emails_entity ON public.contact_emails USING btree (tenant_id, entity_type, entity_id, "position");


--
-- Name: idx_contact_phones_entity; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_contact_phones_entity ON public.contact_phones USING btree (tenant_id, entity_type, entity_id, "position");


--
-- Name: idx_contact_phones_number; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_contact_phones_number ON public.contact_phones USING btree (tenant_id, number);


--
-- Name: idx_contact_types_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_contact_types_tenant ON public.contact_types USING btree (tenant_id, "position");


--
-- Name: idx_conv_assign_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_assign_patient ON public.conversation_assignments USING btree (patient_id) WHERE (patient_id IS NOT NULL);


--
-- Name: idx_conv_assign_tenant_user; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_assign_tenant_user ON public.conversation_assignments USING btree (tenant_id, assigned_to) WHERE (assigned_to IS NOT NULL);


--
-- Name: idx_conv_msg_delivery_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_msg_delivery_status ON public.conversation_messages USING btree (tenant_id, delivery_status) WHERE (direction = 'outbound'::text);


--
-- Name: idx_conv_msg_from_number; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_msg_from_number ON public.conversation_messages USING btree (tenant_id, from_number);


--
-- Name: idx_conv_msg_kanban; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_msg_kanban ON public.conversation_messages USING btree (tenant_id, kanban_status, priority DESC, created_at DESC);


--
-- Name: idx_conv_msg_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_msg_patient ON public.conversation_messages USING btree (patient_id, created_at DESC) WHERE (patient_id IS NOT NULL);


--
-- Name: idx_conv_msg_provider_msg_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_msg_provider_msg_id ON public.conversation_messages USING btree (provider_message_id) WHERE (provider_message_id IS NOT NULL);


--
-- Name: idx_conv_msg_tenant_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_msg_tenant_created ON public.conversation_messages USING btree (tenant_id, created_at DESC);


--
-- Name: idx_conv_notes_created_by; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_notes_created_by ON public.conversation_notes USING btree (created_by, created_at DESC) WHERE (deleted_at IS NULL);


--
-- Name: idx_conv_notes_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_notes_patient ON public.conversation_notes USING btree (patient_id, created_at DESC) WHERE ((deleted_at IS NULL) AND (patient_id IS NOT NULL));


--
-- Name: idx_conv_notes_tenant_thread; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_notes_tenant_thread ON public.conversation_notes USING btree (tenant_id, thread_key, created_at DESC) WHERE (deleted_at IS NULL);


--
-- Name: idx_conv_optout_kw_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_optout_kw_tenant ON public.conversation_optout_keywords USING btree (tenant_id) WHERE (enabled = true);


--
-- Name: idx_conv_optouts_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_optouts_patient ON public.conversation_optouts USING btree (patient_id) WHERE (patient_id IS NOT NULL);


--
-- Name: idx_conv_optouts_tenant_phone; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_optouts_tenant_phone ON public.conversation_optouts USING btree (tenant_id, phone);


--
-- Name: idx_conv_tags_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_tags_tenant ON public.conversation_tags USING btree (tenant_id, "position");


--
-- Name: idx_conv_thread_tags_tag; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_thread_tags_tag ON public.conversation_thread_tags USING btree (tag_id);


--
-- Name: idx_conv_thread_tags_tenant_thread; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_conv_thread_tags_tenant_thread ON public.conversation_thread_tags USING btree (tenant_id, thread_key);


--
-- Name: idx_dev_auditoria_items_categoria; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_auditoria_items_categoria ON public.dev_auditoria_items USING btree (categoria);


--
-- Name: idx_dev_auditoria_items_ordem; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_auditoria_items_ordem ON public.dev_auditoria_items USING btree (ordem);


--
-- Name: idx_dev_auditoria_items_severidade; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_auditoria_items_severidade ON public.dev_auditoria_items USING btree (severidade);


--
-- Name: idx_dev_auditoria_items_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_auditoria_items_status ON public.dev_auditoria_items USING btree (status);


--
-- Name: idx_dev_ccs_comp; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_ccs_comp ON public.dev_comparison_competitor_status USING btree (competitor_id);


--
-- Name: idx_dev_ccs_comparison; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_ccs_comparison ON public.dev_comparison_competitor_status USING btree (comparison_id);


--
-- Name: idx_dev_comparison_matrix_dominio; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_comparison_matrix_dominio ON public.dev_comparison_matrix USING btree (dominio);


--
-- Name: idx_dev_comparison_matrix_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_comparison_matrix_status ON public.dev_comparison_matrix USING btree (nosso_status);


--
-- Name: idx_dev_competitor_features_cat; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_competitor_features_cat ON public.dev_competitor_features USING btree (categoria);


--
-- Name: idx_dev_competitor_features_comp; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_competitor_features_comp ON public.dev_competitor_features USING btree (competitor_id);


--
-- Name: idx_dev_competitor_features_destaque; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_competitor_features_destaque ON public.dev_competitor_features USING btree (destaque);


--
-- Name: idx_dev_competitor_features_ordem; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_competitor_features_ordem ON public.dev_competitor_features USING btree (competitor_id, ordem);


--
-- Name: idx_dev_competitors_ativo; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_competitors_ativo ON public.dev_competitors USING btree (ativo);


--
-- Name: idx_dev_competitors_pais; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_competitors_pais ON public.dev_competitors USING btree (pais);


--
-- Name: idx_dev_generation_log_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_generation_log_created ON public.dev_generation_log USING btree (created_at DESC);


--
-- Name: idx_dev_generation_log_tipo; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_generation_log_tipo ON public.dev_generation_log USING btree (tipo);


--
-- Name: idx_dev_roadmap_items_ordem; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_roadmap_items_ordem ON public.dev_roadmap_items USING btree (phase_id, ordem);


--
-- Name: idx_dev_roadmap_items_phase; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_roadmap_items_phase ON public.dev_roadmap_items USING btree (phase_id);


--
-- Name: idx_dev_roadmap_items_prior; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_roadmap_items_prior ON public.dev_roadmap_items USING btree (prioridade);


--
-- Name: idx_dev_roadmap_items_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_roadmap_items_status ON public.dev_roadmap_items USING btree (status);


--
-- Name: idx_dev_roadmap_phases_ordem; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_roadmap_phases_ordem ON public.dev_roadmap_phases USING btree (ordem);


--
-- Name: idx_dev_roadmap_phases_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_roadmap_phases_status ON public.dev_roadmap_phases USING btree (status);


--
-- Name: idx_dev_test_items_area; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_test_items_area ON public.dev_test_items USING btree (area);


--
-- Name: idx_dev_test_items_ordem; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_test_items_ordem ON public.dev_test_items USING btree (area, ordem);


--
-- Name: idx_dev_test_items_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_test_items_status ON public.dev_test_items USING btree (status);


--
-- Name: idx_dev_verificacoes_area; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_verificacoes_area ON public.dev_verificacoes_items USING btree (area);


--
-- Name: idx_dev_verificacoes_ordem; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_verificacoes_ordem ON public.dev_verificacoes_items USING btree (area, ordem);


--
-- Name: idx_dev_verificacoes_severidade; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_verificacoes_severidade ON public.dev_verificacoes_items USING btree (severidade);


--
-- Name: idx_dev_verificacoes_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_dev_verificacoes_status ON public.dev_verificacoes_items USING btree (status);


--
-- Name: idx_documents_content_sha256; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_documents_content_sha256 ON public.documents USING btree (content_sha256) WHERE (content_sha256 IS NOT NULL);


--
-- Name: idx_email_templates_global_domain; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_email_templates_global_domain ON public.email_templates_global USING btree (domain) WHERE (is_active = true);


--
-- Name: idx_email_templates_global_key; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_email_templates_global_key ON public.email_templates_global USING btree (key) WHERE (is_active = true);


--
-- Name: idx_email_templates_tenant_lookup; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_email_templates_tenant_lookup ON public.email_templates_tenant USING btree (tenant_id, template_key) WHERE (enabled = true);


--
-- Name: idx_email_templates_tenant_owner; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_email_templates_tenant_owner ON public.email_templates_tenant USING btree (owner_id, template_key) WHERE ((enabled = true) AND (owner_id IS NOT NULL));


--
-- Name: idx_features_is_active; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_features_is_active ON public.features USING btree (is_active) WHERE (is_active = false);


--
-- Name: idx_financial_categories_user_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_categories_user_id ON public.financial_categories USING btree (user_id);


--
-- Name: idx_financial_records_active; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_active ON public.financial_records USING btree (owner_id, paid_at DESC) WHERE (deleted_at IS NULL);


--
-- Name: idx_financial_records_agenda_evento_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_agenda_evento_id ON public.financial_records USING btree (agenda_evento_id) WHERE (agenda_evento_id IS NOT NULL);


--
-- Name: idx_financial_records_category_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_category_id ON public.financial_records USING btree (category_id) WHERE (category_id IS NOT NULL);


--
-- Name: idx_financial_records_due_date; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_due_date ON public.financial_records USING btree (due_date);


--
-- Name: idx_financial_records_installment_group; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_installment_group ON public.financial_records USING btree (installment_group) WHERE (installment_group IS NOT NULL);


--
-- Name: idx_financial_records_owner_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_owner_id ON public.financial_records USING btree (owner_id);


--
-- Name: idx_financial_records_paid_at; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_paid_at ON public.financial_records USING btree (paid_at DESC);


--
-- Name: idx_financial_records_patient_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_patient_id ON public.financial_records USING btree (patient_id);


--
-- Name: idx_financial_records_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_status ON public.financial_records USING btree (status) WHERE (deleted_at IS NULL);


--
-- Name: idx_financial_records_tenant_active; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_tenant_active ON public.financial_records USING btree (tenant_id, paid_at DESC) WHERE ((deleted_at IS NULL) AND (tenant_id IS NOT NULL));


--
-- Name: idx_financial_records_tenant_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_tenant_id ON public.financial_records USING btree (tenant_id);


--
-- Name: idx_financial_records_type_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_financial_records_type_status ON public.financial_records USING btree (type, status);


--
-- Name: idx_global_notices_active_priority; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_global_notices_active_priority ON public.global_notices USING btree (is_active, priority DESC, starts_at, ends_at);


--
-- Name: idx_intakes_converted_patient_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_intakes_converted_patient_id ON public.patient_intake_requests USING btree (converted_patient_id);


--
-- Name: idx_intakes_owner_cpf; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_intakes_owner_cpf ON public.patient_intake_requests USING btree (owner_id, cpf);


--
-- Name: idx_intakes_owner_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_intakes_owner_created ON public.patient_intake_requests USING btree (owner_id, created_at DESC);


--
-- Name: idx_intakes_owner_status_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_intakes_owner_status_created ON public.patient_intake_requests USING btree (owner_id, status, created_at DESC);


--
-- Name: idx_intakes_progress_pending; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_intakes_progress_pending ON public.patient_intake_requests USING btree (last_progress_at) WHERE (status = 'in_progress'::text);


--
-- Name: idx_intakes_status_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_intakes_status_created ON public.patient_intake_requests USING btree (status, created_at DESC);


--
-- Name: idx_mc_expires; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_mc_expires ON public.math_challenges USING btree (expires_at);


--
-- Name: idx_notice_dismissals_user; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notice_dismissals_user ON public.notice_dismissals USING btree (user_id, notice_id);


--
-- Name: idx_notif_channels_owner_active; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_channels_owner_active ON public.notification_channels USING btree (owner_id, channel) WHERE ((is_active = true) AND (deleted_at IS NULL));


--
-- Name: idx_notif_channels_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_channels_tenant ON public.notification_channels USING btree (tenant_id) WHERE (deleted_at IS NULL);


--
-- Name: idx_notif_logs_owner_date; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_logs_owner_date ON public.notification_logs USING btree (owner_id, created_at DESC);


--
-- Name: idx_notif_logs_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_logs_patient ON public.notification_logs USING btree (patient_id, created_at DESC);


--
-- Name: idx_notif_logs_provider_msg; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_logs_provider_msg ON public.notification_logs USING btree (provider_message_id) WHERE (provider_message_id IS NOT NULL);


--
-- Name: idx_notif_logs_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_logs_status ON public.notification_logs USING btree (status, created_at DESC);


--
-- Name: idx_notif_logs_tenant_date; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_logs_tenant_date ON public.notification_logs USING btree (tenant_id, created_at DESC);


--
-- Name: idx_notif_prefs_owner; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_prefs_owner ON public.notification_preferences USING btree (owner_id) WHERE (deleted_at IS NULL);


--
-- Name: idx_notif_prefs_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_prefs_patient ON public.notification_preferences USING btree (patient_id) WHERE (deleted_at IS NULL);


--
-- Name: idx_notif_prefs_whatsapp_active; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_prefs_whatsapp_active ON public.notification_preferences USING btree (owner_id, patient_id) WHERE ((whatsapp_opt_in = true) AND (deleted_at IS NULL));


--
-- Name: idx_notif_queue_evento; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_queue_evento ON public.notification_queue USING btree (agenda_evento_id) WHERE (status = ANY (ARRAY['pendente'::text, 'processando'::text]));


--
-- Name: idx_notif_queue_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_queue_patient ON public.notification_queue USING btree (patient_id, channel) WHERE (status = 'pendente'::text);


--
-- Name: idx_notif_queue_pending; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_queue_pending ON public.notification_queue USING btree (scheduled_at) WHERE (status = 'pendente'::text);


--
-- Name: idx_notif_queue_processing; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_queue_processing ON public.notification_queue USING btree (status, updated_at) WHERE (status = 'processando'::text);


--
-- Name: idx_notif_queue_retry; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_queue_retry ON public.notification_queue USING btree (next_retry_at) WHERE ((status = 'pendente'::text) AND (attempts > 0));


--
-- Name: idx_notif_queue_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_queue_tenant ON public.notification_queue USING btree (tenant_id, created_at DESC);


--
-- Name: idx_notif_schedules_owner_active; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_schedules_owner_active ON public.notification_schedules USING btree (owner_id, event_type) WHERE ((is_active = true) AND (deleted_at IS NULL));


--
-- Name: idx_notif_templates_default; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_templates_default ON public.notification_templates USING btree (channel, event_type) WHERE ((is_default = true) AND (deleted_at IS NULL) AND (tenant_id IS NULL));


--
-- Name: idx_notif_templates_lookup; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_templates_lookup ON public.notification_templates USING btree (channel, event_type, is_active) WHERE (deleted_at IS NULL);


--
-- Name: idx_notif_templates_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notif_templates_tenant ON public.notification_templates USING btree (tenant_id, channel, event_type) WHERE ((deleted_at IS NULL) AND (is_active = true));


--
-- Name: idx_notification_channels_twilio_subaccount_sid; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_notification_channels_twilio_subaccount_sid ON public.notification_channels USING btree (twilio_subaccount_sid) WHERE (twilio_subaccount_sid IS NOT NULL);


--
-- Name: idx_patient_contacts_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_contacts_patient ON public.patient_contacts USING btree (patient_id);


--
-- Name: idx_patient_contacts_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_contacts_tenant ON public.patient_contacts USING btree (tenant_id);


--
-- Name: idx_patient_group_patient_group_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_group_patient_group_id ON public.patient_group_patient USING btree (patient_group_id);


--
-- Name: idx_patient_groups_owner; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_groups_owner ON public.patient_groups USING btree (owner_id);


--
-- Name: idx_patient_groups_owner_system_nome; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_groups_owner_system_nome ON public.patient_groups USING btree (owner_id, is_system, nome);


--
-- Name: idx_patient_intake_requests_nome_trgm; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_intake_requests_nome_trgm ON public.patient_intake_requests USING gin (nome_completo public.gin_trgm_ops) WHERE (status = 'new'::text);


--
-- Name: idx_patient_invite_attempts_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_invite_attempts_created ON public.patient_invite_attempts USING btree (created_at DESC);


--
-- Name: idx_patient_invite_attempts_ok; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_invite_attempts_ok ON public.patient_invite_attempts USING btree (ok) WHERE (ok = false);


--
-- Name: idx_patient_invite_attempts_owner; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_invite_attempts_owner ON public.patient_invite_attempts USING btree (owner_id);


--
-- Name: idx_patient_invite_attempts_token; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_invite_attempts_token ON public.patient_invite_attempts USING btree (token);


--
-- Name: idx_patient_tags_owner; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patient_tags_owner ON public.patient_tags USING btree (owner_id);


--
-- Name: idx_patients_cpf_trgm; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_cpf_trgm ON public.patients USING gin (cpf public.gin_trgm_ops) WHERE (cpf IS NOT NULL);


--
-- Name: idx_patients_created_at; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_created_at ON public.patients USING btree (created_at DESC);


--
-- Name: idx_patients_email_trgm; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_email_trgm ON public.patients USING gin (email_principal public.gin_trgm_ops) WHERE (email_principal IS NOT NULL);


--
-- Name: idx_patients_last_attended; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_last_attended ON public.patients USING btree (last_attended_at DESC);


--
-- Name: idx_patients_nome_trgm; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_nome_trgm ON public.patients USING gin (nome_completo public.gin_trgm_ops);


--
-- Name: idx_patients_origem; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_origem ON public.patients USING btree (tenant_id, origem) WHERE (origem IS NOT NULL);


--
-- Name: idx_patients_owner_email_principal; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_owner_email_principal ON public.patients USING btree (owner_id, email_principal);


--
-- Name: idx_patients_owner_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_owner_id ON public.patients USING btree (owner_id);


--
-- Name: idx_patients_owner_nome; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_owner_nome ON public.patients USING btree (owner_id, nome_completo);


--
-- Name: idx_patients_responsible_member; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_responsible_member ON public.patients USING btree (responsible_member_id);


--
-- Name: idx_patients_risco_elevado; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_risco_elevado ON public.patients USING btree (tenant_id, risco_elevado) WHERE (risco_elevado = true);


--
-- Name: idx_patients_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_status ON public.patients USING btree (status);


--
-- Name: idx_patients_status_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_status_tenant ON public.patients USING btree (tenant_id, status);


--
-- Name: idx_patients_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_tenant ON public.patients USING btree (tenant_id);


--
-- Name: idx_patients_tenant_email_norm; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_patients_tenant_email_norm ON public.patients USING btree (tenant_id, lower(TRIM(BOTH FROM email_principal)));


--
-- Name: idx_pgp_group; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_pgp_group ON public.patient_group_patient USING btree (patient_group_id);


--
-- Name: idx_pgp_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_pgp_patient ON public.patient_group_patient USING btree (patient_id);


--
-- Name: idx_ppt_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_ppt_patient ON public.patient_patient_tag USING btree (patient_id);


--
-- Name: idx_ppt_tag; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_ppt_tag ON public.patient_patient_tag USING btree (tag_id);


--
-- Name: idx_psa_endpoint_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_psa_endpoint_created ON public.public_submission_attempts USING btree (endpoint, created_at DESC);


--
-- Name: idx_psa_failed; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_psa_failed ON public.public_submission_attempts USING btree (created_at DESC) WHERE (success = false);


--
-- Name: idx_psa_ip_hash_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_psa_ip_hash_created ON public.public_submission_attempts USING btree (ip_hash, created_at DESC) WHERE (ip_hash IS NOT NULL);


--
-- Name: idx_psh_patient; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_psh_patient ON public.patient_status_history USING btree (patient_id, alterado_em DESC);


--
-- Name: idx_psh_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_psh_tenant ON public.patient_status_history USING btree (tenant_id, alterado_em DESC);


--
-- Name: idx_pt_evento_tipo; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_pt_evento_tipo ON public.patient_timeline USING btree (patient_id, evento_tipo);


--
-- Name: idx_pt_patient_ocorrido; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_pt_patient_ocorrido ON public.patient_timeline USING btree (patient_id, ocorrido_em DESC);


--
-- Name: idx_pt_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_pt_tenant ON public.patient_timeline USING btree (tenant_id, ocorrido_em DESC);


--
-- Name: idx_services_name_trgm; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_services_name_trgm ON public.services USING gin (name public.gin_trgm_ops);


--
-- Name: idx_session_reminder_tenant_sent; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_session_reminder_tenant_sent ON public.session_reminder_logs USING btree (tenant_id, sent_at DESC);


--
-- Name: idx_sla_breaches_open; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_sla_breaches_open ON public.conversation_sla_breaches USING btree (resolved_at) WHERE (resolved_at IS NULL);


--
-- Name: idx_sla_breaches_tenant_breached; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_sla_breaches_tenant_breached ON public.conversation_sla_breaches USING btree (tenant_id, breached_at DESC);


--
-- Name: idx_slots_bloq_owner_dia; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_slots_bloq_owner_dia ON public.agenda_slots_bloqueados_semanais USING btree (owner_id, dia_semana);


--
-- Name: idx_srl_blocked_until; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_srl_blocked_until ON public.submission_rate_limits USING btree (blocked_until) WHERE (blocked_until IS NOT NULL);


--
-- Name: idx_srl_endpoint; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_srl_endpoint ON public.submission_rate_limits USING btree (endpoint, last_attempt_at DESC);


--
-- Name: idx_subscription_intents_plan_interval; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_subscription_intents_plan_interval ON public.subscription_intents_legacy USING btree (plan_key, "interval");


--
-- Name: idx_subscription_intents_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_subscription_intents_status ON public.subscription_intents_legacy USING btree (status);


--
-- Name: idx_subscription_intents_user_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_subscription_intents_user_id ON public.subscription_intents_legacy USING btree (user_id);


--
-- Name: idx_tenant_features_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_tenant_features_tenant ON public.tenant_features USING btree (tenant_id);


--
-- Name: idx_tenant_invites_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_tenant_invites_tenant ON public.tenant_invites USING btree (tenant_id);


--
-- Name: idx_tenant_invites_token; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_tenant_invites_token ON public.tenant_invites USING btree (token);


--
-- Name: idx_therapist_payout_records_financial_record_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_therapist_payout_records_financial_record_id ON public.therapist_payout_records USING btree (financial_record_id);


--
-- Name: idx_therapist_payouts_owner_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_therapist_payouts_owner_id ON public.therapist_payouts USING btree (owner_id);


--
-- Name: idx_therapist_payouts_period; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_therapist_payouts_period ON public.therapist_payouts USING btree (period_start, period_end);


--
-- Name: idx_therapist_payouts_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_therapist_payouts_status ON public.therapist_payouts USING btree (status) WHERE (status = 'pending'::text);


--
-- Name: idx_therapist_payouts_tenant_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_therapist_payouts_tenant_id ON public.therapist_payouts USING btree (tenant_id);


--
-- Name: idx_twilio_usage_channel; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_twilio_usage_channel ON public.twilio_subaccount_usage USING btree (channel_id, period_start DESC);


--
-- Name: idx_twilio_usage_tenant_period; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_twilio_usage_tenant_period ON public.twilio_subaccount_usage USING btree (tenant_id, period_start DESC);


--
-- Name: idx_twilio_usage_unique_period; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX idx_twilio_usage_unique_period ON public.twilio_subaccount_usage USING btree (channel_id, period_start, period_end);


--
-- Name: idx_wa_credit_packages_active; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_wa_credit_packages_active ON public.whatsapp_credit_packages USING btree (is_active, "position", price_brl) WHERE (is_active = true);


--
-- Name: idx_wa_credit_purchases_asaas_payment; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_wa_credit_purchases_asaas_payment ON public.whatsapp_credit_purchases USING btree (asaas_payment_id) WHERE (asaas_payment_id IS NOT NULL);


--
-- Name: idx_wa_credit_purchases_status; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_wa_credit_purchases_status ON public.whatsapp_credit_purchases USING btree (status, created_at DESC);


--
-- Name: idx_wa_credit_purchases_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_wa_credit_purchases_tenant ON public.whatsapp_credit_purchases USING btree (tenant_id, created_at DESC);


--
-- Name: idx_wa_credits_tx_kind; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_wa_credits_tx_kind ON public.whatsapp_credits_transactions USING btree (tenant_id, kind, created_at DESC);


--
-- Name: idx_wa_credits_tx_tenant_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_wa_credits_tx_tenant_created ON public.whatsapp_credits_transactions USING btree (tenant_id, created_at DESC);


--
-- Name: idx_wa_incidents_open; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_wa_incidents_open ON public.whatsapp_connection_incidents USING btree (resolved_at) WHERE (resolved_at IS NULL);


--
-- Name: idx_wa_incidents_tenant_started; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_wa_incidents_tenant_started ON public.whatsapp_connection_incidents USING btree (tenant_id, started_at DESC);


--
-- Name: insurance_plans_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX insurance_plans_owner_idx ON public.insurance_plans USING btree (owner_id);


--
-- Name: insurance_plans_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX insurance_plans_tenant_idx ON public.insurance_plans USING btree (tenant_id);


--
-- Name: ix_plan_prices_plan; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX ix_plan_prices_plan ON public.plan_prices USING btree (plan_id);


--
-- Name: ix_plan_public_bullets_plan; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX ix_plan_public_bullets_plan ON public.plan_public_bullets USING btree (plan_id);


--
-- Name: ix_plan_public_sort; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX ix_plan_public_sort ON public.plan_public USING btree (sort_order);


--
-- Name: medicos_especialidade_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX medicos_especialidade_idx ON public.medicos USING btree (especialidade);


--
-- Name: medicos_nome_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX medicos_nome_idx ON public.medicos USING btree (nome);


--
-- Name: medicos_nome_trgm_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX medicos_nome_trgm_idx ON public.medicos USING gin (nome public.gin_trgm_ops);


--
-- Name: medicos_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX medicos_owner_idx ON public.medicos USING btree (owner_id);


--
-- Name: medicos_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX medicos_tenant_idx ON public.medicos USING btree (tenant_id);


--
-- Name: notifications_owner_created; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX notifications_owner_created ON public.notifications USING btree (owner_id, created_at DESC);


--
-- Name: notifications_owner_unread; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX notifications_owner_unread ON public.notifications USING btree (owner_id, read_at) WHERE (read_at IS NULL);


--
-- Name: patient_discounts_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_discounts_owner_idx ON public.patient_discounts USING btree (owner_id);


--
-- Name: patient_discounts_patient_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_discounts_patient_idx ON public.patient_discounts USING btree (patient_id);


--
-- Name: patient_discounts_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_discounts_tenant_idx ON public.patient_discounts USING btree (tenant_id);


--
-- Name: patient_group_patient_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_group_patient_tenant_idx ON public.patient_group_patient USING btree (tenant_id);


--
-- Name: patient_groups_owner_nome_uniq; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX patient_groups_owner_nome_uniq ON public.patient_groups USING btree (owner_id, nome);


--
-- Name: patient_groups_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_groups_tenant_idx ON public.patient_groups USING btree (tenant_id);


--
-- Name: patient_intake_owner_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_intake_owner_id_idx ON public.patient_intake_requests USING btree (owner_id);


--
-- Name: patient_intake_requests_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_intake_requests_tenant_idx ON public.patient_intake_requests USING btree (tenant_id);


--
-- Name: patient_intake_status_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_intake_status_idx ON public.patient_intake_requests USING btree (status);


--
-- Name: patient_intake_token_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_intake_token_idx ON public.patient_intake_requests USING btree (token);


--
-- Name: patient_invites_one_active_per_owner; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX patient_invites_one_active_per_owner ON public.patient_invites USING btree (owner_id) WHERE (active = true);


--
-- Name: patient_invites_owner_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_invites_owner_id_idx ON public.patient_invites USING btree (owner_id);


--
-- Name: patient_invites_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_invites_tenant_idx ON public.patient_invites USING btree (tenant_id);


--
-- Name: patient_invites_token_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_invites_token_idx ON public.patient_invites USING btree (token);


--
-- Name: patient_patient_tag_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_patient_tag_tenant_idx ON public.patient_patient_tag USING btree (tenant_id);


--
-- Name: patient_tags_owner_name_uq; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX patient_tags_owner_name_uq ON public.patient_tags USING btree (owner_id, lower(nome));


--
-- Name: patient_tags_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patient_tags_tenant_idx ON public.patient_tags USING btree (tenant_id);


--
-- Name: patients_convenio_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patients_convenio_id_idx ON public.patients USING btree (convenio_id);


--
-- Name: patients_etnia_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patients_etnia_idx ON public.patients USING btree (etnia);


--
-- Name: patients_pronomes_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX patients_pronomes_idx ON public.patients USING btree (pronomes);


--
-- Name: payment_settings_tenant_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX payment_settings_tenant_id_idx ON public.payment_settings USING btree (tenant_id);


--
-- Name: ppt_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX ppt_owner_idx ON public.patient_patient_tag USING btree (owner_id);


--
-- Name: ppt_patient_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX ppt_patient_idx ON public.patient_patient_tag USING btree (patient_id);


--
-- Name: ppt_tag_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX ppt_tag_idx ON public.patient_patient_tag USING btree (tag_id);


--
-- Name: professional_pricing_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX professional_pricing_tenant_idx ON public.professional_pricing USING btree (tenant_id);


--
-- Name: profiles_work_description_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX profiles_work_description_idx ON public.profiles USING btree (work_description) WHERE (work_description IS NOT NULL);


--
-- Name: psc_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX psc_owner_idx ON public.patient_support_contacts USING btree (owner_id);


--
-- Name: psc_patient_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX psc_patient_idx ON public.patient_support_contacts USING btree (patient_id);


--
-- Name: recurrence_exceptions_rule_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX recurrence_exceptions_rule_idx ON public.recurrence_exceptions USING btree (recurrence_id);


--
-- Name: recurrence_exceptions_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX recurrence_exceptions_tenant_idx ON public.recurrence_exceptions USING btree (tenant_id);


--
-- Name: recurrence_rule_services_rule_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX recurrence_rule_services_rule_idx ON public.recurrence_rule_services USING btree (rule_id);


--
-- Name: recurrence_rule_services_service_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX recurrence_rule_services_service_idx ON public.recurrence_rule_services USING btree (service_id);


--
-- Name: recurrence_rules_active_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX recurrence_rules_active_idx ON public.recurrence_rules USING btree (owner_id, status) WHERE (status = 'ativo'::text);


--
-- Name: recurrence_rules_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX recurrence_rules_owner_idx ON public.recurrence_rules USING btree (owner_id);


--
-- Name: recurrence_rules_patient_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX recurrence_rules_patient_idx ON public.recurrence_rules USING btree (patient_id);


--
-- Name: recurrence_rules_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX recurrence_rules_tenant_idx ON public.recurrence_rules USING btree (tenant_id);


--
-- Name: saas_doc_votos_doc_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_doc_votos_doc_id_idx ON public.saas_doc_votos USING btree (doc_id);


--
-- Name: saas_doc_votos_user_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_doc_votos_user_id_idx ON public.saas_doc_votos USING btree (user_id);


--
-- Name: saas_docs_categoria_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_docs_categoria_idx ON public.saas_docs USING btree (categoria);


--
-- Name: saas_docs_exibir_no_faq_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_docs_exibir_no_faq_idx ON public.saas_docs USING btree (exibir_no_faq) WHERE (exibir_no_faq = true);


--
-- Name: saas_docs_path_ativo_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_docs_path_ativo_idx ON public.saas_docs USING btree (pagina_path, ativo);


--
-- Name: saas_faq_ativo_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_faq_ativo_idx ON public.saas_faq USING btree (ativo);


--
-- Name: saas_faq_categoria_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_faq_categoria_idx ON public.saas_faq USING btree (categoria);


--
-- Name: saas_faq_fts_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_faq_fts_idx ON public.saas_faq USING gin (to_tsvector('portuguese'::regconfig, ((COALESCE(pergunta, ''::text) || ' '::text) || COALESCE(conteudo, ''::text))));


--
-- Name: saas_faq_itens_ativo_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_faq_itens_ativo_idx ON public.saas_faq_itens USING btree (ativo);


--
-- Name: saas_faq_itens_doc_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_faq_itens_doc_id_idx ON public.saas_faq_itens USING btree (doc_id);


--
-- Name: saas_faq_pagina_path_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_faq_pagina_path_idx ON public.saas_faq USING btree (pagina_path);


--
-- Name: saas_faq_publico_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_faq_publico_idx ON public.saas_faq USING btree (publico);


--
-- Name: saas_faq_votos_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX saas_faq_votos_idx ON public.saas_faq USING btree (votos DESC);


--
-- Name: services_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX services_owner_idx ON public.services USING btree (owner_id);


--
-- Name: services_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX services_tenant_idx ON public.services USING btree (tenant_id);


--
-- Name: sint_personal_created_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX sint_personal_created_idx ON public.subscription_intents_personal USING btree (created_at DESC);


--
-- Name: sint_personal_status_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX sint_personal_status_idx ON public.subscription_intents_personal USING btree (status);


--
-- Name: sint_tenant_created_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX sint_tenant_created_idx ON public.subscription_intents_tenant USING btree (created_at DESC);


--
-- Name: sint_tenant_status_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX sint_tenant_status_idx ON public.subscription_intents_tenant USING btree (status);


--
-- Name: sint_tenant_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX sint_tenant_tenant_idx ON public.subscription_intents_tenant USING btree (tenant_id);


--
-- Name: subscription_events_created_at_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscription_events_created_at_idx ON public.subscription_events USING btree (created_at DESC);


--
-- Name: subscription_events_owner_ref_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscription_events_owner_ref_idx ON public.subscription_events USING btree (owner_type, owner_ref);


--
-- Name: subscription_events_sub_created_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscription_events_sub_created_idx ON public.subscription_events USING btree (subscription_id, created_at DESC);


--
-- Name: subscription_events_subscription_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscription_events_subscription_id_idx ON public.subscription_events USING btree (subscription_id);


--
-- Name: subscriptions_one_active_per_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX subscriptions_one_active_per_tenant ON public.subscriptions USING btree (tenant_id) WHERE (status = 'active'::text);


--
-- Name: subscriptions_one_active_per_user; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX subscriptions_one_active_per_user ON public.subscriptions USING btree (user_id) WHERE (status = 'active'::text);


--
-- Name: subscriptions_one_active_per_user_personal; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX subscriptions_one_active_per_user_personal ON public.subscriptions USING btree (user_id) WHERE ((tenant_id IS NULL) AND (status = 'active'::text));


--
-- Name: subscriptions_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscriptions_owner_idx ON public.subscriptions USING btree (user_id);


--
-- Name: subscriptions_plan_key_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscriptions_plan_key_idx ON public.subscriptions USING btree (plan_key);


--
-- Name: subscriptions_status_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscriptions_status_idx ON public.subscriptions USING btree (status);


--
-- Name: subscriptions_tenant_id_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscriptions_tenant_id_idx ON public.subscriptions USING btree (tenant_id);


--
-- Name: subscriptions_tenant_period_end_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscriptions_tenant_period_end_idx ON public.subscriptions USING btree (tenant_id, current_period_end);


--
-- Name: subscriptions_tenant_status_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscriptions_tenant_status_idx ON public.subscriptions USING btree (tenant_id, status);


--
-- Name: subscriptions_user_status_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX subscriptions_user_status_idx ON public.subscriptions USING btree (user_id, status, created_at DESC);


--
-- Name: support_sessions_expires_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX support_sessions_expires_idx ON public.support_sessions USING btree (expires_at);


--
-- Name: support_sessions_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX support_sessions_tenant_idx ON public.support_sessions USING btree (tenant_id);


--
-- Name: support_sessions_token_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX support_sessions_token_idx ON public.support_sessions USING btree (token);


--
-- Name: tenant_members_tenant_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX tenant_members_tenant_idx ON public.tenant_members USING btree (tenant_id);


--
-- Name: tenant_members_user_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX tenant_members_user_idx ON public.tenant_members USING btree (user_id);


--
-- Name: tenant_modules_owner_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX tenant_modules_owner_idx ON public.tenant_modules USING btree (owner_id);


--
-- Name: unique_member_per_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX unique_member_per_tenant ON public.tenant_members USING btree (tenant_id, user_id);


--
-- Name: uq_bot_sessions_active_per_thread; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_bot_sessions_active_per_thread ON public.conversation_bot_sessions USING btree (tenant_id, thread_key) WHERE (status = 'active'::text);


--
-- Name: uq_contact_email_types_system_slug; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_contact_email_types_system_slug ON public.contact_email_types USING btree (slug) WHERE (tenant_id IS NULL);


--
-- Name: uq_contact_email_types_tenant_slug; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_contact_email_types_tenant_slug ON public.contact_email_types USING btree (tenant_id, slug) WHERE (tenant_id IS NOT NULL);


--
-- Name: uq_contact_emails_primary; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_contact_emails_primary ON public.contact_emails USING btree (entity_type, entity_id) WHERE (is_primary = true);


--
-- Name: uq_contact_phones_primary; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_contact_phones_primary ON public.contact_phones USING btree (entity_type, entity_id) WHERE (is_primary = true);


--
-- Name: uq_contact_types_system_slug; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_contact_types_system_slug ON public.contact_types USING btree (slug) WHERE (tenant_id IS NULL);


--
-- Name: uq_contact_types_tenant_slug; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_contact_types_tenant_slug ON public.contact_types USING btree (tenant_id, slug) WHERE (tenant_id IS NOT NULL);


--
-- Name: uq_conv_optouts_active; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_conv_optouts_active ON public.conversation_optouts USING btree (tenant_id, phone) WHERE (opted_back_in_at IS NULL);


--
-- Name: uq_conv_tags_system_slug; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_conv_tags_system_slug ON public.conversation_tags USING btree (slug) WHERE (tenant_id IS NULL);


--
-- Name: uq_conv_tags_tenant_slug; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_conv_tags_tenant_slug ON public.conversation_tags USING btree (tenant_id, slug) WHERE (tenant_id IS NOT NULL);


--
-- Name: uq_patient_contacts_primario; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_patient_contacts_primario ON public.patient_contacts USING btree (patient_id) WHERE ((is_primario = true) AND (ativo = true));


--
-- Name: uq_patients_tenant_user; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_patients_tenant_user ON public.patients USING btree (tenant_id, user_id) WHERE (user_id IS NOT NULL);


--
-- Name: uq_plan_price_active; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_plan_price_active ON public.plan_prices USING btree (plan_id, "interval", currency) WHERE ((is_active = true) AND (active_to IS NULL));


--
-- Name: uq_plan_prices_active; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_plan_prices_active ON public.plan_prices USING btree (plan_id, "interval") WHERE (is_active = true);


--
-- Name: uq_session_reminder_event_type; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_session_reminder_event_type ON public.session_reminder_logs USING btree (event_id, reminder_type);


--
-- Name: uq_sla_breaches_open_per_thread; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_sla_breaches_open_per_thread ON public.conversation_sla_breaches USING btree (tenant_id, thread_key) WHERE (resolved_at IS NULL);


--
-- Name: uq_subscriptions_active_by_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_subscriptions_active_by_tenant ON public.subscriptions USING btree (tenant_id) WHERE ((tenant_id IS NOT NULL) AND (status = 'active'::text));


--
-- Name: uq_subscriptions_active_personal_by_user; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_subscriptions_active_personal_by_user ON public.subscriptions USING btree (user_id) WHERE ((tenant_id IS NULL) AND (status = 'active'::text));


--
-- Name: uq_tenant_invites_pending; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_tenant_invites_pending ON public.tenant_invites USING btree (tenant_id, lower(email), role) WHERE ((accepted_at IS NULL) AND (revoked_at IS NULL));


--
-- Name: uq_tenant_members_tenant_user; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_tenant_members_tenant_user ON public.tenant_members USING btree (tenant_id, user_id);


--
-- Name: uq_wa_incidents_open_per_channel; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX uq_wa_incidents_open_per_channel ON public.whatsapp_connection_incidents USING btree (channel_id) WHERE (resolved_at IS NULL);


--
-- Name: ux_subscriptions_active_per_personal_user; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX ux_subscriptions_active_per_personal_user ON public.subscriptions USING btree (user_id) WHERE ((status = 'active'::text) AND (tenant_id IS NULL));


--
-- Name: ux_subscriptions_active_per_tenant; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX ux_subscriptions_active_per_tenant ON public.subscriptions USING btree (tenant_id) WHERE ((status = 'active'::text) AND (tenant_id IS NOT NULL));


--
-- Name: ix_realtime_subscription_entity; Type: INDEX; Schema: realtime; Owner: -
--

CREATE INDEX ix_realtime_subscription_entity ON realtime.subscription USING btree (entity);


--
-- Name: messages_inserted_at_topic_index; Type: INDEX; Schema: realtime; Owner: -
--

CREATE INDEX messages_inserted_at_topic_index ON ONLY realtime.messages USING btree (inserted_at DESC, topic) WHERE ((extension = 'broadcast'::text) AND (private IS TRUE));


--
-- Name: messages_2026_05_08_inserted_at_topic_idx; Type: INDEX; Schema: realtime; Owner: -
--

CREATE INDEX messages_2026_05_08_inserted_at_topic_idx ON realtime.messages_2026_05_08 USING btree (inserted_at DESC, topic) WHERE ((extension = 'broadcast'::text) AND (private IS TRUE));


--
-- Name: messages_2026_05_09_inserted_at_topic_idx; Type: INDEX; Schema: realtime; Owner: -
--

CREATE INDEX messages_2026_05_09_inserted_at_topic_idx ON realtime.messages_2026_05_09 USING btree (inserted_at DESC, topic) WHERE ((extension = 'broadcast'::text) AND (private IS TRUE));


--
-- Name: messages_2026_05_10_inserted_at_topic_idx; Type: INDEX; Schema: realtime; Owner: -
--

CREATE INDEX messages_2026_05_10_inserted_at_topic_idx ON realtime.messages_2026_05_10 USING btree (inserted_at DESC, topic) WHERE ((extension = 'broadcast'::text) AND (private IS TRUE));


--
-- Name: messages_2026_05_11_inserted_at_topic_idx; Type: INDEX; Schema: realtime; Owner: -
--

CREATE INDEX messages_2026_05_11_inserted_at_topic_idx ON realtime.messages_2026_05_11 USING btree (inserted_at DESC, topic) WHERE ((extension = 'broadcast'::text) AND (private IS TRUE));


--
-- Name: messages_2026_05_12_inserted_at_topic_idx; Type: INDEX; Schema: realtime; Owner: -
--

CREATE INDEX messages_2026_05_12_inserted_at_topic_idx ON realtime.messages_2026_05_12 USING btree (inserted_at DESC, topic) WHERE ((extension = 'broadcast'::text) AND (private IS TRUE));


--
-- Name: messages_2026_05_13_inserted_at_topic_idx; Type: INDEX; Schema: realtime; Owner: -
--

CREATE INDEX messages_2026_05_13_inserted_at_topic_idx ON realtime.messages_2026_05_13 USING btree (inserted_at DESC, topic) WHERE ((extension = 'broadcast'::text) AND (private IS TRUE));


--
-- Name: messages_2026_05_14_inserted_at_topic_idx; Type: INDEX; Schema: realtime; Owner: -
--

CREATE INDEX messages_2026_05_14_inserted_at_topic_idx ON realtime.messages_2026_05_14 USING btree (inserted_at DESC, topic) WHERE ((extension = 'broadcast'::text) AND (private IS TRUE));


--
-- Name: subscription_subscription_id_entity_filters_key; Type: INDEX; Schema: realtime; Owner: -
--

CREATE UNIQUE INDEX subscription_subscription_id_entity_filters_key ON realtime.subscription USING btree (subscription_id, entity, filters);


--
-- Name: bname; Type: INDEX; Schema: storage; Owner: -
--

CREATE UNIQUE INDEX bname ON storage.buckets USING btree (name);


--
-- Name: bucketid_objname; Type: INDEX; Schema: storage; Owner: -
--

CREATE UNIQUE INDEX bucketid_objname ON storage.objects USING btree (bucket_id, name);


--
-- Name: buckets_analytics_unique_name_idx; Type: INDEX; Schema: storage; Owner: -
--

CREATE UNIQUE INDEX buckets_analytics_unique_name_idx ON storage.buckets_analytics USING btree (name) WHERE (deleted_at IS NULL);


--
-- Name: idx_iceberg_namespaces_bucket_id; Type: INDEX; Schema: storage; Owner: -
--

CREATE UNIQUE INDEX idx_iceberg_namespaces_bucket_id ON storage.iceberg_namespaces USING btree (catalog_id, name);


--
-- Name: idx_iceberg_tables_location; Type: INDEX; Schema: storage; Owner: -
--

CREATE UNIQUE INDEX idx_iceberg_tables_location ON storage.iceberg_tables USING btree (location);


--
-- Name: idx_iceberg_tables_namespace_id; Type: INDEX; Schema: storage; Owner: -
--

CREATE UNIQUE INDEX idx_iceberg_tables_namespace_id ON storage.iceberg_tables USING btree (catalog_id, namespace_id, name);


--
-- Name: idx_multipart_uploads_list; Type: INDEX; Schema: storage; Owner: -
--

CREATE INDEX idx_multipart_uploads_list ON storage.s3_multipart_uploads USING btree (bucket_id, key, created_at);


--
-- Name: idx_objects_bucket_id_name; Type: INDEX; Schema: storage; Owner: -
--

CREATE INDEX idx_objects_bucket_id_name ON storage.objects USING btree (bucket_id, name COLLATE "C");


--
-- Name: idx_objects_bucket_id_name_lower; Type: INDEX; Schema: storage; Owner: -
--

CREATE INDEX idx_objects_bucket_id_name_lower ON storage.objects USING btree (bucket_id, lower(name) COLLATE "C");


--
-- Name: name_prefix_search; Type: INDEX; Schema: storage; Owner: -
--

CREATE INDEX name_prefix_search ON storage.objects USING btree (name text_pattern_ops);


--
-- Name: vector_indexes_name_bucket_id_idx; Type: INDEX; Schema: storage; Owner: -
--

CREATE UNIQUE INDEX vector_indexes_name_bucket_id_idx ON storage.vector_indexes USING btree (name, bucket_id);


--
-- Name: supabase_functions_hooks_h_table_id_h_name_idx; Type: INDEX; Schema: supabase_functions; Owner: -
--

CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);


--
-- Name: supabase_functions_hooks_request_id_idx; Type: INDEX; Schema: supabase_functions; Owner: -
--

CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);


--
-- Name: messages_2026_05_08_inserted_at_topic_idx; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_inserted_at_topic_index ATTACH PARTITION realtime.messages_2026_05_08_inserted_at_topic_idx;


--
-- Name: messages_2026_05_08_pkey; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_pkey ATTACH PARTITION realtime.messages_2026_05_08_pkey;


--
-- Name: messages_2026_05_09_inserted_at_topic_idx; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_inserted_at_topic_index ATTACH PARTITION realtime.messages_2026_05_09_inserted_at_topic_idx;


--
-- Name: messages_2026_05_09_pkey; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_pkey ATTACH PARTITION realtime.messages_2026_05_09_pkey;


--
-- Name: messages_2026_05_10_inserted_at_topic_idx; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_inserted_at_topic_index ATTACH PARTITION realtime.messages_2026_05_10_inserted_at_topic_idx;


--
-- Name: messages_2026_05_10_pkey; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_pkey ATTACH PARTITION realtime.messages_2026_05_10_pkey;


--
-- Name: messages_2026_05_11_inserted_at_topic_idx; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_inserted_at_topic_index ATTACH PARTITION realtime.messages_2026_05_11_inserted_at_topic_idx;


--
-- Name: messages_2026_05_11_pkey; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_pkey ATTACH PARTITION realtime.messages_2026_05_11_pkey;


--
-- Name: messages_2026_05_12_inserted_at_topic_idx; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_inserted_at_topic_index ATTACH PARTITION realtime.messages_2026_05_12_inserted_at_topic_idx;


--
-- Name: messages_2026_05_12_pkey; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_pkey ATTACH PARTITION realtime.messages_2026_05_12_pkey;


--
-- Name: messages_2026_05_13_inserted_at_topic_idx; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_inserted_at_topic_index ATTACH PARTITION realtime.messages_2026_05_13_inserted_at_topic_idx;


--
-- Name: messages_2026_05_13_pkey; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_pkey ATTACH PARTITION realtime.messages_2026_05_13_pkey;


--
-- Name: messages_2026_05_14_inserted_at_topic_idx; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_inserted_at_topic_index ATTACH PARTITION realtime.messages_2026_05_14_inserted_at_topic_idx;


--
-- Name: messages_2026_05_14_pkey; Type: INDEX ATTACH; Schema: realtime; Owner: -
--

ALTER INDEX realtime.messages_pkey ATTACH PARTITION realtime.messages_2026_05_14_pkey;


--
-- Name: users on_auth_user_created; Type: TRIGGER; Schema: auth; Owner: -
--

CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();


--
-- Name: users trg_seed_patient_groups; Type: TRIGGER; Schema: auth; Owner: -
--

CREATE TRIGGER trg_seed_patient_groups AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.on_new_user_seed_patient_groups();


--
-- Name: agenda_bloqueios agenda_bloqueios_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER agenda_bloqueios_updated_at BEFORE UPDATE ON public.agenda_bloqueios FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: agendador_configuracoes agendador_slug_trigger; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER agendador_slug_trigger BEFORE INSERT OR UPDATE ON public.agendador_configuracoes FOR EACH ROW EXECUTE FUNCTION public.agendador_gerar_slug();


--
-- Name: tenant_members prevent_saas_membership_trigger; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER prevent_saas_membership_trigger BEFORE INSERT ON public.tenant_members FOR EACH ROW EXECUTE FUNCTION public.prevent_saas_membership();


--
-- Name: insurance_plan_services set_insurance_plan_services_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER set_insurance_plan_services_updated_at BEFORE UPDATE ON public.insurance_plan_services FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: user_settings t_user_settings_set_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER t_user_settings_set_updated_at BEFORE UPDATE ON public.user_settings FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: agenda_configuracoes tg_agenda_configuracoes_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER tg_agenda_configuracoes_updated_at BEFORE UPDATE ON public.agenda_configuracoes FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: agenda_eventos tg_agenda_eventos_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER tg_agenda_eventos_updated_at BEFORE UPDATE ON public.agenda_eventos FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: agenda_excecoes tg_agenda_excecoes_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER tg_agenda_excecoes_updated_at BEFORE UPDATE ON public.agenda_excecoes FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: agenda_regras_semanais tg_agenda_regras_semanais_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER tg_agenda_regras_semanais_updated_at BEFORE UPDATE ON public.agenda_regras_semanais FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: recurrence_rules tg_recurrence_rules_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER tg_recurrence_rules_updated_at BEFORE UPDATE ON public.recurrence_rules FOR EACH ROW EXECUTE FUNCTION public.set_updated_at_recurrence();


--
-- Name: plan_public tr_plan_public_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER tr_plan_public_updated_at BEFORE UPDATE ON public.plan_public FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: profiles trg_account_type_immutable; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_account_type_immutable BEFORE UPDATE OF account_type ON public.profiles FOR EACH ROW EXECUTE FUNCTION public.guard_account_type_immutable();


--
-- Name: agenda_configuracoes trg_agenda_cfg_sync; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_agenda_cfg_sync BEFORE INSERT OR UPDATE ON public.agenda_configuracoes FOR EACH ROW EXECUTE FUNCTION public.agenda_cfg_sync();


--
-- Name: agenda_eventos trg_agenda_eventos_busy_mirror_del; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_agenda_eventos_busy_mirror_del AFTER DELETE ON public.agenda_eventos FOR EACH ROW WHEN (((old.mirror_of_event_id IS NULL) AND (old.tenant_id = old.owner_id))) EXECUTE FUNCTION public.sync_busy_mirror_agenda_eventos();


--
-- Name: agenda_eventos trg_agenda_eventos_busy_mirror_ins; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_agenda_eventos_busy_mirror_ins AFTER INSERT ON public.agenda_eventos FOR EACH ROW WHEN (((new.mirror_of_event_id IS NULL) AND (new.tenant_id = new.owner_id) AND (new.visibility_scope = ANY (ARRAY['busy_only'::text, 'private'::text])))) EXECUTE FUNCTION public.sync_busy_mirror_agenda_eventos();


--
-- Name: agenda_eventos trg_agenda_eventos_busy_mirror_upd; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_agenda_eventos_busy_mirror_upd AFTER UPDATE ON public.agenda_eventos FOR EACH ROW WHEN (((new.mirror_of_event_id IS NULL) AND (new.tenant_id = new.owner_id) AND ((new.visibility_scope IS DISTINCT FROM old.visibility_scope) OR (new.inicio_em IS DISTINCT FROM old.inicio_em) OR (new.fim_em IS DISTINCT FROM old.fim_em) OR (new.owner_id IS DISTINCT FROM old.owner_id) OR (new.tenant_id IS DISTINCT FROM old.tenant_id)))) EXECUTE FUNCTION public.sync_busy_mirror_agenda_eventos();


--
-- Name: agenda_regras_semanais trg_agenda_regras_semanais_no_overlap; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_agenda_regras_semanais_no_overlap BEFORE INSERT OR UPDATE ON public.agenda_regras_semanais FOR EACH ROW EXECUTE FUNCTION public.fn_agenda_regras_semanais_no_overlap();


--
-- Name: agenda_eventos trg_agenda_status_notify; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_agenda_status_notify AFTER UPDATE OF status ON public.agenda_eventos FOR EACH ROW EXECUTE FUNCTION public.fn_notify_agenda_status_change();


--
-- Name: agenda_eventos trg_audit_agenda_eventos; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_audit_agenda_eventos AFTER INSERT OR DELETE OR UPDATE ON public.agenda_eventos FOR EACH ROW EXECUTE FUNCTION public.log_audit_change();


--
-- Name: documents trg_audit_documents; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_audit_documents AFTER INSERT OR DELETE OR UPDATE ON public.documents FOR EACH ROW EXECUTE FUNCTION public.log_audit_change();


--
-- Name: financial_records trg_audit_financial_records; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_audit_financial_records AFTER INSERT OR DELETE OR UPDATE ON public.financial_records FOR EACH ROW EXECUTE FUNCTION public.log_audit_change();


--
-- Name: patients trg_audit_patients; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_audit_patients AFTER INSERT OR DELETE OR UPDATE ON public.patients FOR EACH ROW EXECUTE FUNCTION public.log_audit_change();


--
-- Name: tenant_members trg_audit_tenant_members; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_audit_tenant_members AFTER INSERT OR DELETE OR UPDATE ON public.tenant_members FOR EACH ROW EXECUTE FUNCTION public.log_audit_change();


--
-- Name: agenda_eventos trg_auto_financial_from_session; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_auto_financial_from_session AFTER UPDATE OF status ON public.agenda_eventos FOR EACH ROW EXECUTE FUNCTION public.auto_create_financial_record_from_session();


--
-- Name: conversation_bot_sessions trg_bot_sessions_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_bot_sessions_updated_at BEFORE UPDATE ON public.conversation_bot_sessions FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: notification_preferences trg_cancel_notifs_on_opt_out; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_cancel_notifs_on_opt_out AFTER UPDATE ON public.notification_preferences FOR EACH ROW EXECUTE FUNCTION public.cancel_notifications_on_opt_out();


--
-- Name: agenda_eventos trg_cancel_notifs_on_session_cancel; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_cancel_notifs_on_session_cancel AFTER UPDATE ON public.agenda_eventos FOR EACH ROW WHEN ((new.status IS DISTINCT FROM old.status)) EXECUTE FUNCTION public.cancel_notifications_on_session_cancel();


--
-- Name: company_profiles trg_company_profiles_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_company_profiles_updated_at BEFORE UPDATE ON public.company_profiles FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: contact_email_types trg_contact_email_types_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_contact_email_types_updated_at BEFORE UPDATE ON public.contact_email_types FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: contact_emails trg_contact_emails_sync_legacy; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_contact_emails_sync_legacy AFTER INSERT OR DELETE OR UPDATE ON public.contact_emails FOR EACH ROW EXECUTE FUNCTION public.sync_legacy_email_fields();


--
-- Name: contact_emails trg_contact_emails_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_contact_emails_updated_at BEFORE UPDATE ON public.contact_emails FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: contact_phones trg_contact_phones_sync_legacy; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_contact_phones_sync_legacy AFTER INSERT OR DELETE OR UPDATE ON public.contact_phones FOR EACH ROW EXECUTE FUNCTION public.sync_legacy_phone_fields();


--
-- Name: contact_phones trg_contact_phones_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_contact_phones_updated_at BEFORE UPDATE ON public.contact_phones FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: contact_types trg_contact_types_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_contact_types_updated_at BEFORE UPDATE ON public.contact_types FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: conversation_assignments trg_conv_assign_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_conv_assign_updated_at BEFORE UPDATE ON public.conversation_assignments FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: conversation_autoreply_settings trg_conv_autoreply_settings_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_conv_autoreply_settings_updated_at BEFORE UPDATE ON public.conversation_autoreply_settings FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: conversation_bots trg_conv_bots_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_conv_bots_updated_at BEFORE UPDATE ON public.conversation_bots FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: conversation_messages trg_conv_messages_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_conv_messages_updated_at BEFORE UPDATE ON public.conversation_messages FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: conversation_notes trg_conv_notes_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_conv_notes_updated_at BEFORE UPDATE ON public.conversation_notes FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: conversation_optouts trg_conv_optouts_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_conv_optouts_updated_at BEFORE UPDATE ON public.conversation_optouts FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: conversation_tags trg_conv_tags_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_conv_tags_updated_at BEFORE UPDATE ON public.conversation_tags FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: determined_commitment_fields trg_determined_commitment_fields_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_determined_commitment_fields_updated_at BEFORE UPDATE ON public.determined_commitment_fields FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: determined_commitments trg_determined_commitments_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_determined_commitments_updated_at BEFORE UPDATE ON public.determined_commitments FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: dev_auditoria_items trg_dev_auditoria_items_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_dev_auditoria_items_updated_at BEFORE UPDATE ON public.dev_auditoria_items FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();


--
-- Name: dev_comparison_competitor_status trg_dev_ccs_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_dev_ccs_updated_at BEFORE UPDATE ON public.dev_comparison_competitor_status FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();


--
-- Name: dev_comparison_matrix trg_dev_comparison_matrix_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_dev_comparison_matrix_updated_at BEFORE UPDATE ON public.dev_comparison_matrix FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();


--
-- Name: dev_competitor_features trg_dev_competitor_features_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_dev_competitor_features_updated_at BEFORE UPDATE ON public.dev_competitor_features FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();


--
-- Name: dev_competitors trg_dev_competitors_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_dev_competitors_updated_at BEFORE UPDATE ON public.dev_competitors FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();


--
-- Name: dev_roadmap_items trg_dev_roadmap_items_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_dev_roadmap_items_updated_at BEFORE UPDATE ON public.dev_roadmap_items FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();


--
-- Name: dev_roadmap_phases trg_dev_roadmap_phases_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_dev_roadmap_phases_updated_at BEFORE UPDATE ON public.dev_roadmap_phases FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();


--
-- Name: dev_test_items trg_dev_test_items_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_dev_test_items_updated_at BEFORE UPDATE ON public.dev_test_items FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();


--
-- Name: dev_verificacoes_items trg_dev_verificacoes_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_dev_verificacoes_updated_at BEFORE UPDATE ON public.dev_verificacoes_items FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();


--
-- Name: documents trg_documents_timeline_insert; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_documents_timeline_insert AFTER INSERT ON public.documents FOR EACH ROW EXECUTE FUNCTION public.fn_documents_timeline_insert();


--
-- Name: documents trg_documents_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_documents_updated_at BEFORE UPDATE ON public.documents FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: document_signatures trg_ds_timeline; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_ds_timeline AFTER UPDATE ON public.document_signatures FOR EACH ROW EXECUTE FUNCTION public.fn_document_signature_timeline();


--
-- Name: document_signatures trg_ds_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_ds_updated_at BEFORE UPDATE ON public.document_signatures FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: document_templates trg_dt_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_dt_updated_at BEFORE UPDATE ON public.document_templates FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: email_layout_config trg_email_layout_config_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_email_layout_config_updated_at BEFORE UPDATE ON public.email_layout_config FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: email_templates_global trg_email_templates_global_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_email_templates_global_updated_at BEFORE UPDATE ON public.email_templates_global FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: email_templates_tenant trg_email_templates_tenant_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_email_templates_tenant_updated_at BEFORE UPDATE ON public.email_templates_tenant FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: conversation_messages trg_fanout_inbound_to_notifications; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_fanout_inbound_to_notifications AFTER INSERT ON public.conversation_messages FOR EACH ROW EXECUTE FUNCTION public.fanout_inbound_message_to_notifications();


--
-- Name: financial_exceptions trg_financial_exceptions_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_financial_exceptions_updated_at BEFORE UPDATE ON public.financial_exceptions FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: financial_records trg_financial_records_auto_overdue; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_financial_records_auto_overdue BEFORE UPDATE ON public.financial_records FOR EACH ROW EXECUTE FUNCTION public.trg_fn_financial_records_auto_overdue();


--
-- Name: financial_records trg_financial_records_inject_tenant; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_financial_records_inject_tenant BEFORE INSERT ON public.financial_records FOR EACH ROW EXECUTE FUNCTION public.financial_records_inject_tenant();


--
-- Name: financial_records trg_financial_records_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_financial_records_updated_at BEFORE UPDATE ON public.financial_records FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: global_notices trg_global_notices_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_global_notices_updated_at BEFORE UPDATE ON public.global_notices FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: insurance_plans trg_insurance_plans_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_insurance_plans_updated_at BEFORE UPDATE ON public.insurance_plans FOR EACH ROW EXECUTE FUNCTION public.set_insurance_plans_updated_at();


--
-- Name: medicos trg_medicos_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_medicos_updated_at BEFORE UPDATE ON public.medicos FOR EACH ROW EXECUTE FUNCTION public.set_medicos_updated_at();


--
-- Name: plans trg_no_change_core_plan_key; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_no_change_core_plan_key BEFORE UPDATE ON public.plans FOR EACH ROW EXECUTE FUNCTION public.guard_no_change_core_plan_key();


--
-- Name: plans trg_no_change_plan_target; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_no_change_plan_target BEFORE UPDATE ON public.plans FOR EACH ROW EXECUTE FUNCTION public.guard_no_change_plan_target();


--
-- Name: plans trg_no_delete_core_plans; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_no_delete_core_plans BEFORE DELETE ON public.plans FOR EACH ROW EXECUTE FUNCTION public.guard_no_delete_core_plans();


--
-- Name: notification_channels trg_notification_channels_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_notification_channels_updated_at BEFORE UPDATE ON public.notification_channels FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: notification_logs trg_notification_logs_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_notification_logs_updated_at BEFORE UPDATE ON public.notification_logs FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: notification_preferences trg_notification_preferences_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_notification_preferences_updated_at BEFORE UPDATE ON public.notification_preferences FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: notification_queue trg_notification_queue_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_notification_queue_updated_at BEFORE UPDATE ON public.notification_queue FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: notification_schedules trg_notification_schedules_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_notification_schedules_updated_at BEFORE UPDATE ON public.notification_schedules FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: notification_templates trg_notification_templates_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_notification_templates_updated_at BEFORE UPDATE ON public.notification_templates FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: patient_intake_requests trg_notify_on_intake; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_notify_on_intake AFTER INSERT ON public.patient_intake_requests FOR EACH ROW EXECUTE FUNCTION public.notify_on_intake();


--
-- Name: agendador_solicitacoes trg_notify_on_scheduling; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_notify_on_scheduling AFTER INSERT ON public.agendador_solicitacoes FOR EACH ROW EXECUTE FUNCTION public.notify_on_scheduling();


--
-- Name: agenda_eventos trg_notify_on_session_status; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_notify_on_session_status AFTER UPDATE OF status ON public.agenda_eventos FOR EACH ROW EXECUTE FUNCTION public.notify_on_session_status();


--
-- Name: tenant_members trg_patient_cannot_own_tenant; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_patient_cannot_own_tenant BEFORE INSERT OR UPDATE ON public.tenant_members FOR EACH ROW EXECUTE FUNCTION public.guard_patient_cannot_own_tenant();


--
-- Name: patient_contacts trg_patient_contacts_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_patient_contacts_updated_at BEFORE UPDATE ON public.patient_contacts FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: patient_groups trg_patient_groups_set_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_patient_groups_set_updated_at BEFORE UPDATE ON public.patient_groups FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: patient_intake_requests trg_patient_intake_requests_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_patient_intake_requests_updated_at BEFORE UPDATE ON public.patient_intake_requests FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: patients trg_patient_risco_timeline; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_patient_risco_timeline AFTER UPDATE OF risco_elevado ON public.patients FOR EACH ROW EXECUTE FUNCTION public.trg_fn_patient_risco_timeline();


--
-- Name: patients trg_patient_status_history; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_patient_status_history AFTER INSERT OR UPDATE OF status ON public.patients FOR EACH ROW EXECUTE FUNCTION public.trg_fn_patient_status_history();


--
-- Name: patients trg_patient_status_timeline; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_patient_status_timeline AFTER INSERT OR UPDATE OF status ON public.patients FOR EACH ROW EXECUTE FUNCTION public.trg_fn_patient_status_timeline();


--
-- Name: patient_tags trg_patient_tags_set_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_patient_tags_set_updated_at BEFORE UPDATE ON public.patient_tags FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: patients trg_patients_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_patients_updated_at BEFORE UPDATE ON public.patients FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: patients trg_patients_validate_members; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_patients_validate_members BEFORE INSERT OR UPDATE OF tenant_id, responsible_member_id, patient_scope, therapist_member_id ON public.patients FOR EACH ROW EXECUTE FUNCTION public.patients_validate_member_consistency();


--
-- Name: payment_settings trg_payment_settings_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_payment_settings_updated_at BEFORE UPDATE ON public.payment_settings FOR EACH ROW EXECUTE FUNCTION public.update_payment_settings_updated_at();


--
-- Name: patient_groups trg_prevent_promoting_to_system; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_prevent_promoting_to_system BEFORE UPDATE ON public.patient_groups FOR EACH ROW EXECUTE FUNCTION public.prevent_promoting_to_system();


--
-- Name: patient_groups trg_prevent_system_group_changes; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_prevent_system_group_changes BEFORE DELETE OR UPDATE ON public.patient_groups FOR EACH ROW EXECUTE FUNCTION public.prevent_system_group_changes();


--
-- Name: professional_pricing trg_professional_pricing_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_professional_pricing_updated_at BEFORE UPDATE ON public.professional_pricing FOR EACH ROW EXECUTE FUNCTION public.update_professional_pricing_updated_at();


--
-- Name: profiles trg_profiles_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_profiles_updated_at BEFORE UPDATE ON public.profiles FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: patient_support_contacts trg_psc_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_psc_updated_at BEFORE UPDATE ON public.patient_support_contacts FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: services trg_services_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_services_updated_at BEFORE UPDATE ON public.services FOR EACH ROW EXECUTE FUNCTION public.set_services_updated_at();


--
-- Name: session_reminder_settings trg_session_reminder_settings_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_session_reminder_settings_updated_at BEFORE UPDATE ON public.session_reminder_settings FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: conversation_sla_breaches trg_sla_breaches_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_sla_breaches_updated_at BEFORE UPDATE ON public.conversation_sla_breaches FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: conversation_messages trg_sla_resolve_on_outbound; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_sla_resolve_on_outbound AFTER INSERT ON public.conversation_messages FOR EACH ROW EXECUTE FUNCTION public.fn_sla_resolve_on_outbound();


--
-- Name: conversation_sla_rules trg_sla_rules_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_sla_rules_updated_at BEFORE UPDATE ON public.conversation_sla_rules FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: subscription_intents trg_subscription_intents_view_insert; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_subscription_intents_view_insert INSTEAD OF INSERT ON public.subscription_intents FOR EACH ROW EXECUTE FUNCTION public.subscription_intents_view_insert();


--
-- Name: subscriptions trg_subscriptions_validate_scope; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_subscriptions_validate_scope BEFORE INSERT OR UPDATE ON public.subscriptions FOR EACH ROW EXECUTE FUNCTION public.subscriptions_validate_scope();


--
-- Name: tenant_features trg_tenant_features_guard_with_plan; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_tenant_features_guard_with_plan BEFORE INSERT OR UPDATE ON public.tenant_features FOR EACH ROW EXECUTE FUNCTION public.tenant_features_guard_with_plan();


--
-- Name: tenant_features trg_tenant_features_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_tenant_features_updated_at BEFORE UPDATE ON public.tenant_features FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: tenants trg_tenant_kind_immutable; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_tenant_kind_immutable BEFORE UPDATE OF kind ON public.tenants FOR EACH ROW EXECUTE FUNCTION public.guard_tenant_kind_immutable();


--
-- Name: therapist_payouts trg_therapist_payouts_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_therapist_payouts_updated_at BEFORE UPDATE ON public.therapist_payouts FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: user_settings trg_user_settings_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_user_settings_updated_at BEFORE UPDATE ON public.user_settings FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: whatsapp_credit_packages trg_wa_credit_packages_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_wa_credit_packages_updated_at BEFORE UPDATE ON public.whatsapp_credit_packages FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: whatsapp_credit_purchases trg_wa_credit_purchases_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_wa_credit_purchases_updated_at BEFORE UPDATE ON public.whatsapp_credit_purchases FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: whatsapp_credits_balance trg_wa_credits_balance_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_wa_credits_balance_updated_at BEFORE UPDATE ON public.whatsapp_credits_balance FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: whatsapp_connection_incidents trg_wa_incidents_updated_at; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_wa_incidents_updated_at BEFORE UPDATE ON public.whatsapp_connection_incidents FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


--
-- Name: whatsapp_credits_balance trg_whatsapp_low_balance_notify; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER trg_whatsapp_low_balance_notify BEFORE UPDATE ON public.whatsapp_credits_balance FOR EACH ROW EXECUTE FUNCTION public.fn_whatsapp_low_balance_notify();


--
-- Name: subscription tr_check_filters; Type: TRIGGER; Schema: realtime; Owner: -
--

CREATE TRIGGER tr_check_filters BEFORE INSERT OR UPDATE ON realtime.subscription FOR EACH ROW EXECUTE FUNCTION realtime.subscription_check_filters();


--
-- Name: buckets enforce_bucket_name_length_trigger; Type: TRIGGER; Schema: storage; Owner: -
--

CREATE TRIGGER enforce_bucket_name_length_trigger BEFORE INSERT OR UPDATE OF name ON storage.buckets FOR EACH ROW EXECUTE FUNCTION storage.enforce_bucket_name_length();


--
-- Name: buckets protect_buckets_delete; Type: TRIGGER; Schema: storage; Owner: -
--

CREATE TRIGGER protect_buckets_delete BEFORE DELETE ON storage.buckets FOR EACH STATEMENT EXECUTE FUNCTION storage.protect_delete();


--
-- Name: objects protect_objects_delete; Type: TRIGGER; Schema: storage; Owner: -
--

CREATE TRIGGER protect_objects_delete BEFORE DELETE ON storage.objects FOR EACH STATEMENT EXECUTE FUNCTION storage.protect_delete();


--
-- Name: objects update_objects_updated_at; Type: TRIGGER; Schema: storage; Owner: -
--

CREATE TRIGGER update_objects_updated_at BEFORE UPDATE ON storage.objects FOR EACH ROW EXECUTE FUNCTION storage.update_updated_at_column();


--
-- Name: extensions extensions_tenant_external_id_fkey; Type: FK CONSTRAINT; Schema: _realtime; Owner: -
--

ALTER TABLE ONLY _realtime.extensions
    ADD CONSTRAINT extensions_tenant_external_id_fkey FOREIGN KEY (tenant_external_id) REFERENCES _realtime.tenants(external_id) ON DELETE CASCADE;


--
-- Name: identities identities_user_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.identities
    ADD CONSTRAINT identities_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: mfa_amr_claims mfa_amr_claims_session_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.mfa_amr_claims
    ADD CONSTRAINT mfa_amr_claims_session_id_fkey FOREIGN KEY (session_id) REFERENCES auth.sessions(id) ON DELETE CASCADE;


--
-- Name: mfa_challenges mfa_challenges_auth_factor_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.mfa_challenges
    ADD CONSTRAINT mfa_challenges_auth_factor_id_fkey FOREIGN KEY (factor_id) REFERENCES auth.mfa_factors(id) ON DELETE CASCADE;


--
-- Name: mfa_factors mfa_factors_user_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.mfa_factors
    ADD CONSTRAINT mfa_factors_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: oauth_authorizations oauth_authorizations_client_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_authorizations
    ADD CONSTRAINT oauth_authorizations_client_id_fkey FOREIGN KEY (client_id) REFERENCES auth.oauth_clients(id) ON DELETE CASCADE;


--
-- Name: oauth_authorizations oauth_authorizations_user_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_authorizations
    ADD CONSTRAINT oauth_authorizations_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: oauth_consents oauth_consents_client_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_consents
    ADD CONSTRAINT oauth_consents_client_id_fkey FOREIGN KEY (client_id) REFERENCES auth.oauth_clients(id) ON DELETE CASCADE;


--
-- Name: oauth_consents oauth_consents_user_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.oauth_consents
    ADD CONSTRAINT oauth_consents_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: one_time_tokens one_time_tokens_user_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.one_time_tokens
    ADD CONSTRAINT one_time_tokens_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: refresh_tokens refresh_tokens_session_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.refresh_tokens
    ADD CONSTRAINT refresh_tokens_session_id_fkey FOREIGN KEY (session_id) REFERENCES auth.sessions(id) ON DELETE CASCADE;


--
-- Name: saml_providers saml_providers_sso_provider_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.saml_providers
    ADD CONSTRAINT saml_providers_sso_provider_id_fkey FOREIGN KEY (sso_provider_id) REFERENCES auth.sso_providers(id) ON DELETE CASCADE;


--
-- Name: saml_relay_states saml_relay_states_flow_state_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.saml_relay_states
    ADD CONSTRAINT saml_relay_states_flow_state_id_fkey FOREIGN KEY (flow_state_id) REFERENCES auth.flow_state(id) ON DELETE CASCADE;


--
-- Name: saml_relay_states saml_relay_states_sso_provider_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.saml_relay_states
    ADD CONSTRAINT saml_relay_states_sso_provider_id_fkey FOREIGN KEY (sso_provider_id) REFERENCES auth.sso_providers(id) ON DELETE CASCADE;


--
-- Name: sessions sessions_oauth_client_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.sessions
    ADD CONSTRAINT sessions_oauth_client_id_fkey FOREIGN KEY (oauth_client_id) REFERENCES auth.oauth_clients(id) ON DELETE CASCADE;


--
-- Name: sessions sessions_user_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.sessions
    ADD CONSTRAINT sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: sso_domains sso_domains_sso_provider_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--

ALTER TABLE ONLY auth.sso_domains
    ADD CONSTRAINT sso_domains_sso_provider_id_fkey FOREIGN KEY (sso_provider_id) REFERENCES auth.sso_providers(id) ON DELETE CASCADE;


--
-- Name: addon_credits addon_credits_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_credits
    ADD CONSTRAINT addon_credits_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id);


--
-- Name: addon_credits addon_credits_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_credits
    ADD CONSTRAINT addon_credits_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id);


--
-- Name: addon_transactions addon_transactions_admin_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_transactions
    ADD CONSTRAINT addon_transactions_admin_user_id_fkey FOREIGN KEY (admin_user_id) REFERENCES auth.users(id);


--
-- Name: addon_transactions addon_transactions_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_transactions
    ADD CONSTRAINT addon_transactions_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id);


--
-- Name: addon_transactions addon_transactions_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_transactions
    ADD CONSTRAINT addon_transactions_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.addon_products(id);


--
-- Name: addon_transactions addon_transactions_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.addon_transactions
    ADD CONSTRAINT addon_transactions_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id);


--
-- Name: agenda_bloqueios agenda_bloqueios_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_bloqueios
    ADD CONSTRAINT agenda_bloqueios_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: agenda_bloqueios agenda_bloqueios_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_bloqueios
    ADD CONSTRAINT agenda_bloqueios_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE SET NULL;


--
-- Name: agenda_configuracoes agenda_configuracoes_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_configuracoes
    ADD CONSTRAINT agenda_configuracoes_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: agenda_eventos agenda_eventos_billing_contract_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_eventos
    ADD CONSTRAINT agenda_eventos_billing_contract_id_fkey FOREIGN KEY (billing_contract_id) REFERENCES public.billing_contracts(id) ON DELETE SET NULL;


--
-- Name: agenda_eventos agenda_eventos_determined_commitment_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_eventos
    ADD CONSTRAINT agenda_eventos_determined_commitment_fk FOREIGN KEY (determined_commitment_id) REFERENCES public.determined_commitments(id) ON DELETE SET NULL;


--
-- Name: agenda_eventos agenda_eventos_insurance_plan_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_eventos
    ADD CONSTRAINT agenda_eventos_insurance_plan_id_fkey FOREIGN KEY (insurance_plan_id) REFERENCES public.insurance_plans(id) ON DELETE SET NULL;


--
-- Name: agenda_eventos agenda_eventos_insurance_plan_service_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_eventos
    ADD CONSTRAINT agenda_eventos_insurance_plan_service_id_fkey FOREIGN KEY (insurance_plan_service_id) REFERENCES public.insurance_plan_services(id) ON DELETE SET NULL;


--
-- Name: agenda_eventos agenda_eventos_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_eventos
    ADD CONSTRAINT agenda_eventos_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE SET NULL;


--
-- Name: agenda_eventos agenda_eventos_recurrence_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_eventos
    ADD CONSTRAINT agenda_eventos_recurrence_id_fkey FOREIGN KEY (recurrence_id) REFERENCES public.recurrence_rules(id) ON DELETE SET NULL;


--
-- Name: agenda_eventos agenda_eventos_terapeuta_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_eventos
    ADD CONSTRAINT agenda_eventos_terapeuta_fk FOREIGN KEY (terapeuta_id) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: agenda_excecoes agenda_excecoes_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_excecoes
    ADD CONSTRAINT agenda_excecoes_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: agenda_online_slots agenda_online_slots_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_online_slots
    ADD CONSTRAINT agenda_online_slots_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: agenda_online_slots agenda_online_slots_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_online_slots
    ADD CONSTRAINT agenda_online_slots_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: agenda_regras_semanais agenda_regras_semanais_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_regras_semanais
    ADD CONSTRAINT agenda_regras_semanais_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: agenda_slots_bloqueados_semanais agenda_slots_bloqueados_semanais_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_slots_bloqueados_semanais
    ADD CONSTRAINT agenda_slots_bloqueados_semanais_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: agenda_slots_regras agenda_slots_regras_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agenda_slots_regras
    ADD CONSTRAINT agenda_slots_regras_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: agendador_configuracoes agendador_configuracoes_owner_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agendador_configuracoes
    ADD CONSTRAINT agendador_configuracoes_owner_fk FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: agendador_configuracoes agendador_configuracoes_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agendador_configuracoes
    ADD CONSTRAINT agendador_configuracoes_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: agendador_solicitacoes agendador_sol_owner_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agendador_solicitacoes
    ADD CONSTRAINT agendador_sol_owner_fk FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: agendador_solicitacoes agendador_sol_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.agendador_solicitacoes
    ADD CONSTRAINT agendador_sol_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: audit_logs audit_logs_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.audit_logs
    ADD CONSTRAINT audit_logs_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: audit_logs audit_logs_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.audit_logs
    ADD CONSTRAINT audit_logs_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: billing_contracts billing_contracts_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.billing_contracts
    ADD CONSTRAINT billing_contracts_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: billing_contracts billing_contracts_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.billing_contracts
    ADD CONSTRAINT billing_contracts_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE CASCADE;


--
-- Name: commitment_services commitment_services_commitment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.commitment_services
    ADD CONSTRAINT commitment_services_commitment_id_fkey FOREIGN KEY (commitment_id) REFERENCES public.agenda_eventos(id) ON DELETE CASCADE;


--
-- Name: commitment_services commitment_services_service_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.commitment_services
    ADD CONSTRAINT commitment_services_service_id_fkey FOREIGN KEY (service_id) REFERENCES public.services(id) ON DELETE RESTRICT;


--
-- Name: commitment_time_logs commitment_time_logs_calendar_event_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.commitment_time_logs
    ADD CONSTRAINT commitment_time_logs_calendar_event_id_fkey FOREIGN KEY (calendar_event_id) REFERENCES public.agenda_eventos(id) ON DELETE SET NULL;


--
-- Name: commitment_time_logs commitment_time_logs_commitment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.commitment_time_logs
    ADD CONSTRAINT commitment_time_logs_commitment_id_fkey FOREIGN KEY (commitment_id) REFERENCES public.determined_commitments(id) ON DELETE CASCADE;


--
-- Name: commitment_time_logs commitment_time_logs_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.commitment_time_logs
    ADD CONSTRAINT commitment_time_logs_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: company_profiles company_profiles_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.company_profiles
    ADD CONSTRAINT company_profiles_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: contact_email_types contact_email_types_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.contact_email_types
    ADD CONSTRAINT contact_email_types_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: contact_emails contact_emails_contact_email_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.contact_emails
    ADD CONSTRAINT contact_emails_contact_email_type_id_fkey FOREIGN KEY (contact_email_type_id) REFERENCES public.contact_email_types(id) ON DELETE RESTRICT;


--
-- Name: contact_emails contact_emails_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.contact_emails
    ADD CONSTRAINT contact_emails_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: contact_phones contact_phones_contact_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.contact_phones
    ADD CONSTRAINT contact_phones_contact_type_id_fkey FOREIGN KEY (contact_type_id) REFERENCES public.contact_types(id) ON DELETE RESTRICT;


--
-- Name: contact_phones contact_phones_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.contact_phones
    ADD CONSTRAINT contact_phones_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: contact_types contact_types_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.contact_types
    ADD CONSTRAINT contact_types_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_assignments conversation_assignments_assigned_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_assignments
    ADD CONSTRAINT conversation_assignments_assigned_by_fkey FOREIGN KEY (assigned_by) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: conversation_assignments conversation_assignments_assigned_to_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_assignments
    ADD CONSTRAINT conversation_assignments_assigned_to_fkey FOREIGN KEY (assigned_to) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: conversation_assignments conversation_assignments_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_assignments
    ADD CONSTRAINT conversation_assignments_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE SET NULL;


--
-- Name: conversation_assignments conversation_assignments_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_assignments
    ADD CONSTRAINT conversation_assignments_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_autoreply_log conversation_autoreply_log_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_autoreply_log
    ADD CONSTRAINT conversation_autoreply_log_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_autoreply_settings conversation_autoreply_settings_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_autoreply_settings
    ADD CONSTRAINT conversation_autoreply_settings_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_bot_sessions conversation_bot_sessions_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_bot_sessions
    ADD CONSTRAINT conversation_bot_sessions_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_bots conversation_bots_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_bots
    ADD CONSTRAINT conversation_bots_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_messages conversation_messages_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_messages
    ADD CONSTRAINT conversation_messages_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE SET NULL;


--
-- Name: conversation_messages conversation_messages_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_messages
    ADD CONSTRAINT conversation_messages_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_notes conversation_notes_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_notes
    ADD CONSTRAINT conversation_notes_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: conversation_notes conversation_notes_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_notes
    ADD CONSTRAINT conversation_notes_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE SET NULL;


--
-- Name: conversation_notes conversation_notes_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_notes
    ADD CONSTRAINT conversation_notes_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_optout_keywords conversation_optout_keywords_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_optout_keywords
    ADD CONSTRAINT conversation_optout_keywords_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_optouts conversation_optouts_blocked_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_optouts
    ADD CONSTRAINT conversation_optouts_blocked_by_fkey FOREIGN KEY (blocked_by) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: conversation_optouts conversation_optouts_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_optouts
    ADD CONSTRAINT conversation_optouts_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE SET NULL;


--
-- Name: conversation_optouts conversation_optouts_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_optouts
    ADD CONSTRAINT conversation_optouts_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_sla_breaches conversation_sla_breaches_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_sla_breaches
    ADD CONSTRAINT conversation_sla_breaches_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_sla_rules conversation_sla_rules_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_sla_rules
    ADD CONSTRAINT conversation_sla_rules_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_tags conversation_tags_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_tags
    ADD CONSTRAINT conversation_tags_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: conversation_thread_tags conversation_thread_tags_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_thread_tags
    ADD CONSTRAINT conversation_thread_tags_tag_id_fkey FOREIGN KEY (tag_id) REFERENCES public.conversation_tags(id) ON DELETE CASCADE;


--
-- Name: conversation_thread_tags conversation_thread_tags_tagged_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_thread_tags
    ADD CONSTRAINT conversation_thread_tags_tagged_by_fkey FOREIGN KEY (tagged_by) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: conversation_thread_tags conversation_thread_tags_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.conversation_thread_tags
    ADD CONSTRAINT conversation_thread_tags_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: determined_commitment_fields determined_commitment_fields_commitment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.determined_commitment_fields
    ADD CONSTRAINT determined_commitment_fields_commitment_id_fkey FOREIGN KEY (commitment_id) REFERENCES public.determined_commitments(id) ON DELETE CASCADE;


--
-- Name: determined_commitment_fields determined_commitment_fields_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.determined_commitment_fields
    ADD CONSTRAINT determined_commitment_fields_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: determined_commitments determined_commitments_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.determined_commitments
    ADD CONSTRAINT determined_commitments_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: dev_comparison_competitor_status dev_comparison_competitor_status_comparison_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_comparison_competitor_status
    ADD CONSTRAINT dev_comparison_competitor_status_comparison_id_fkey FOREIGN KEY (comparison_id) REFERENCES public.dev_comparison_matrix(id) ON DELETE CASCADE;


--
-- Name: dev_comparison_competitor_status dev_comparison_competitor_status_competitor_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_comparison_competitor_status
    ADD CONSTRAINT dev_comparison_competitor_status_competitor_id_fkey FOREIGN KEY (competitor_id) REFERENCES public.dev_competitors(id) ON DELETE CASCADE;


--
-- Name: dev_competitor_features dev_competitor_features_competitor_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_competitor_features
    ADD CONSTRAINT dev_competitor_features_competitor_id_fkey FOREIGN KEY (competitor_id) REFERENCES public.dev_competitors(id) ON DELETE CASCADE;


--
-- Name: dev_roadmap_items dev_roadmap_items_phase_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_roadmap_items
    ADD CONSTRAINT dev_roadmap_items_phase_id_fkey FOREIGN KEY (phase_id) REFERENCES public.dev_roadmap_phases(id) ON DELETE CASCADE;


--
-- Name: dev_verificacoes_items dev_verificacoes_items_auditoria_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.dev_verificacoes_items
    ADD CONSTRAINT dev_verificacoes_items_auditoria_item_id_fkey FOREIGN KEY (auditoria_item_id) REFERENCES public.dev_auditoria_items(id) ON DELETE SET NULL;


--
-- Name: document_access_logs document_access_logs_documento_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_access_logs
    ADD CONSTRAINT document_access_logs_documento_id_fkey FOREIGN KEY (documento_id) REFERENCES public.documents(id) ON DELETE CASCADE;


--
-- Name: document_generated document_generated_documento_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_generated
    ADD CONSTRAINT document_generated_documento_id_fkey FOREIGN KEY (documento_id) REFERENCES public.documents(id) ON DELETE SET NULL;


--
-- Name: document_generated document_generated_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_generated
    ADD CONSTRAINT document_generated_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE CASCADE;


--
-- Name: document_generated document_generated_template_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_generated
    ADD CONSTRAINT document_generated_template_id_fkey FOREIGN KEY (template_id) REFERENCES public.document_templates(id) ON DELETE RESTRICT;


--
-- Name: document_share_links document_share_links_documento_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_share_links
    ADD CONSTRAINT document_share_links_documento_id_fkey FOREIGN KEY (documento_id) REFERENCES public.documents(id) ON DELETE CASCADE;


--
-- Name: document_signatures document_signatures_documento_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.document_signatures
    ADD CONSTRAINT document_signatures_documento_id_fkey FOREIGN KEY (documento_id) REFERENCES public.documents(id) ON DELETE CASCADE;


--
-- Name: documents documents_agenda_evento_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.documents
    ADD CONSTRAINT documents_agenda_evento_id_fkey FOREIGN KEY (agenda_evento_id) REFERENCES public.agenda_eventos(id) ON DELETE SET NULL;


--
-- Name: documents documents_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.documents
    ADD CONSTRAINT documents_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE CASCADE;


--
-- Name: email_layout_config email_layout_config_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.email_layout_config
    ADD CONSTRAINT email_layout_config_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: email_templates_tenant email_templates_tenant_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.email_templates_tenant
    ADD CONSTRAINT email_templates_tenant_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: email_templates_tenant email_templates_tenant_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.email_templates_tenant
    ADD CONSTRAINT email_templates_tenant_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: feriados feriados_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.feriados
    ADD CONSTRAINT feriados_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: feriados feriados_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.feriados
    ADD CONSTRAINT feriados_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: financial_categories financial_categories_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_categories
    ADD CONSTRAINT financial_categories_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: financial_exceptions financial_exceptions_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_exceptions
    ADD CONSTRAINT financial_exceptions_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: financial_records financial_records_agenda_evento_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_records
    ADD CONSTRAINT financial_records_agenda_evento_id_fkey FOREIGN KEY (agenda_evento_id) REFERENCES public.agenda_eventos(id) ON DELETE SET NULL;


--
-- Name: financial_records financial_records_category_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_records
    ADD CONSTRAINT financial_records_category_id_fkey FOREIGN KEY (category_id) REFERENCES public.financial_categories(id) ON DELETE SET NULL;


--
-- Name: financial_records financial_records_insurance_plan_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_records
    ADD CONSTRAINT financial_records_insurance_plan_id_fkey FOREIGN KEY (insurance_plan_id) REFERENCES public.insurance_plans(id) ON DELETE SET NULL;


--
-- Name: financial_records financial_records_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_records
    ADD CONSTRAINT financial_records_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE SET NULL;


--
-- Name: financial_records financial_records_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_records
    ADD CONSTRAINT financial_records_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE SET NULL;


--
-- Name: financial_records financial_records_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.financial_records
    ADD CONSTRAINT financial_records_user_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: global_notices global_notices_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.global_notices
    ADD CONSTRAINT global_notices_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: insurance_plan_services insurance_plan_services_plan_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.insurance_plan_services
    ADD CONSTRAINT insurance_plan_services_plan_fkey FOREIGN KEY (insurance_plan_id) REFERENCES public.insurance_plans(id) ON DELETE CASCADE;


--
-- Name: insurance_plans insurance_plans_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.insurance_plans
    ADD CONSTRAINT insurance_plans_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: module_features module_features_feature_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.module_features
    ADD CONSTRAINT module_features_feature_id_fkey FOREIGN KEY (feature_id) REFERENCES public.features(id) ON DELETE CASCADE;


--
-- Name: module_features module_features_module_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.module_features
    ADD CONSTRAINT module_features_module_id_fkey FOREIGN KEY (module_id) REFERENCES public.modules(id) ON DELETE CASCADE;


--
-- Name: notice_dismissals notice_dismissals_notice_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notice_dismissals
    ADD CONSTRAINT notice_dismissals_notice_id_fkey FOREIGN KEY (notice_id) REFERENCES public.global_notices(id) ON DELETE CASCADE;


--
-- Name: notice_dismissals notice_dismissals_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notice_dismissals
    ADD CONSTRAINT notice_dismissals_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: notification_channels notification_channels_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_channels
    ADD CONSTRAINT notification_channels_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: notification_logs notification_logs_queue_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_logs
    ADD CONSTRAINT notification_logs_queue_id_fkey FOREIGN KEY (queue_id) REFERENCES public.notification_queue(id) ON DELETE SET NULL;


--
-- Name: notification_schedules notification_schedules_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_schedules
    ADD CONSTRAINT notification_schedules_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: notification_templates notification_templates_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notification_templates
    ADD CONSTRAINT notification_templates_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: notifications notifications_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.notifications
    ADD CONSTRAINT notifications_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: patient_contacts patient_contacts_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_contacts
    ADD CONSTRAINT patient_contacts_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE CASCADE;


--
-- Name: patient_contacts patient_contacts_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_contacts
    ADD CONSTRAINT patient_contacts_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: patient_discounts patient_discounts_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_discounts
    ADD CONSTRAINT patient_discounts_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: patient_discounts patient_discounts_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_discounts
    ADD CONSTRAINT patient_discounts_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE CASCADE;


--
-- Name: patient_group_patient patient_group_patient_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_group_patient
    ADD CONSTRAINT patient_group_patient_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE CASCADE;


--
-- Name: patient_group_patient patient_group_patient_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_group_patient
    ADD CONSTRAINT patient_group_patient_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: patient_groups patient_groups_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_groups
    ADD CONSTRAINT patient_groups_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: patient_intake_requests patient_intake_requests_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_intake_requests
    ADD CONSTRAINT patient_intake_requests_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: patient_invites patient_invites_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_invites
    ADD CONSTRAINT patient_invites_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: patient_patient_tag patient_patient_tag_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_patient_tag
    ADD CONSTRAINT patient_patient_tag_tag_id_fkey FOREIGN KEY (tag_id) REFERENCES public.patient_tags(id) ON DELETE CASCADE;


--
-- Name: patient_patient_tag patient_patient_tag_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_patient_tag
    ADD CONSTRAINT patient_patient_tag_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: patient_status_history patient_status_history_alterado_por_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_status_history
    ADD CONSTRAINT patient_status_history_alterado_por_fkey FOREIGN KEY (alterado_por) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: patient_status_history patient_status_history_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_status_history
    ADD CONSTRAINT patient_status_history_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE CASCADE;


--
-- Name: patient_status_history patient_status_history_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_status_history
    ADD CONSTRAINT patient_status_history_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: patient_support_contacts patient_support_contacts_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_support_contacts
    ADD CONSTRAINT patient_support_contacts_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE CASCADE;


--
-- Name: patient_tags patient_tags_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_tags
    ADD CONSTRAINT patient_tags_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: patient_timeline patient_timeline_gerado_por_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_timeline
    ADD CONSTRAINT patient_timeline_gerado_por_fkey FOREIGN KEY (gerado_por) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: patient_timeline patient_timeline_patient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_timeline
    ADD CONSTRAINT patient_timeline_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE CASCADE;


--
-- Name: patient_timeline patient_timeline_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_timeline
    ADD CONSTRAINT patient_timeline_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: patients patients_convenio_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patients
    ADD CONSTRAINT patients_convenio_id_fkey FOREIGN KEY (convenio_id) REFERENCES public.insurance_plans(id) ON DELETE SET NULL;


--
-- Name: patients patients_responsible_member_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patients
    ADD CONSTRAINT patients_responsible_member_id_fkey FOREIGN KEY (responsible_member_id) REFERENCES public.tenant_members(id) ON DELETE RESTRICT;


--
-- Name: patients patients_risco_sinalizado_por_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patients
    ADD CONSTRAINT patients_risco_sinalizado_por_fkey FOREIGN KEY (risco_sinalizado_por) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: patients patients_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patients
    ADD CONSTRAINT patients_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: patients patients_therapist_member_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patients
    ADD CONSTRAINT patients_therapist_member_id_fkey FOREIGN KEY (therapist_member_id) REFERENCES public.tenant_members(id);


--
-- Name: patients patients_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patients
    ADD CONSTRAINT patients_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: payment_settings payment_settings_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.payment_settings
    ADD CONSTRAINT payment_settings_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: payment_settings payment_settings_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.payment_settings
    ADD CONSTRAINT payment_settings_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: plan_features plan_features_feature_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plan_features
    ADD CONSTRAINT plan_features_feature_id_fkey FOREIGN KEY (feature_id) REFERENCES public.features(id) ON DELETE CASCADE;


--
-- Name: plan_features plan_features_plan_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plan_features
    ADD CONSTRAINT plan_features_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES public.plans(id) ON DELETE CASCADE;


--
-- Name: plan_prices plan_prices_plan_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plan_prices
    ADD CONSTRAINT plan_prices_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES public.plans(id) ON DELETE CASCADE;


--
-- Name: plan_public_bullets plan_public_bullets_plan_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plan_public_bullets
    ADD CONSTRAINT plan_public_bullets_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES public.plans(id) ON DELETE CASCADE;


--
-- Name: plan_public plan_public_plan_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.plan_public
    ADD CONSTRAINT plan_public_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES public.plans(id) ON DELETE CASCADE;


--
-- Name: patient_patient_tag ppt_patient_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_patient_tag
    ADD CONSTRAINT ppt_patient_fk FOREIGN KEY (patient_id) REFERENCES public.patients(id) ON DELETE CASCADE;


--
-- Name: patient_patient_tag ppt_tag_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_patient_tag
    ADD CONSTRAINT ppt_tag_fk FOREIGN KEY (tag_id) REFERENCES public.patient_tags(id) ON DELETE CASCADE;


--
-- Name: professional_pricing professional_pricing_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.professional_pricing
    ADD CONSTRAINT professional_pricing_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: profiles profiles_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.profiles
    ADD CONSTRAINT profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: profiles profiles_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.profiles
    ADD CONSTRAINT profiles_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id);


--
-- Name: recurrence_exceptions recurrence_exceptions_agenda_evento_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.recurrence_exceptions
    ADD CONSTRAINT recurrence_exceptions_agenda_evento_id_fkey FOREIGN KEY (agenda_evento_id) REFERENCES public.agenda_eventos(id) ON DELETE SET NULL;


--
-- Name: recurrence_exceptions recurrence_exceptions_recurrence_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.recurrence_exceptions
    ADD CONSTRAINT recurrence_exceptions_recurrence_id_fkey FOREIGN KEY (recurrence_id) REFERENCES public.recurrence_rules(id) ON DELETE CASCADE;


--
-- Name: recurrence_rule_services recurrence_rule_services_rule_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.recurrence_rule_services
    ADD CONSTRAINT recurrence_rule_services_rule_id_fkey FOREIGN KEY (rule_id) REFERENCES public.recurrence_rules(id) ON DELETE CASCADE;


--
-- Name: recurrence_rule_services recurrence_rule_services_service_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.recurrence_rule_services
    ADD CONSTRAINT recurrence_rule_services_service_id_fkey FOREIGN KEY (service_id) REFERENCES public.services(id) ON DELETE RESTRICT;


--
-- Name: recurrence_rules recurrence_rules_insurance_plan_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.recurrence_rules
    ADD CONSTRAINT recurrence_rules_insurance_plan_id_fkey FOREIGN KEY (insurance_plan_id) REFERENCES public.insurance_plans(id) ON DELETE SET NULL;


--
-- Name: recurrence_rules recurrence_rules_insurance_plan_service_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.recurrence_rules
    ADD CONSTRAINT recurrence_rules_insurance_plan_service_id_fkey FOREIGN KEY (insurance_plan_service_id) REFERENCES public.insurance_plan_services(id) ON DELETE SET NULL;


--
-- Name: saas_admins saas_admins_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_admins
    ADD CONSTRAINT saas_admins_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: saas_doc_votos saas_doc_votos_doc_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_doc_votos
    ADD CONSTRAINT saas_doc_votos_doc_id_fkey FOREIGN KEY (doc_id) REFERENCES public.saas_docs(id) ON DELETE CASCADE;


--
-- Name: saas_doc_votos saas_doc_votos_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_doc_votos
    ADD CONSTRAINT saas_doc_votos_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: saas_faq_itens saas_faq_itens_doc_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.saas_faq_itens
    ADD CONSTRAINT saas_faq_itens_doc_id_fkey FOREIGN KEY (doc_id) REFERENCES public.saas_docs(id) ON DELETE CASCADE;


--
-- Name: services services_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.services
    ADD CONSTRAINT services_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: session_reminder_logs session_reminder_logs_conversation_message_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.session_reminder_logs
    ADD CONSTRAINT session_reminder_logs_conversation_message_id_fkey FOREIGN KEY (conversation_message_id) REFERENCES public.conversation_messages(id) ON DELETE SET NULL;


--
-- Name: session_reminder_logs session_reminder_logs_event_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.session_reminder_logs
    ADD CONSTRAINT session_reminder_logs_event_id_fkey FOREIGN KEY (event_id) REFERENCES public.agenda_eventos(id) ON DELETE CASCADE;


--
-- Name: session_reminder_logs session_reminder_logs_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.session_reminder_logs
    ADD CONSTRAINT session_reminder_logs_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: session_reminder_settings session_reminder_settings_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.session_reminder_settings
    ADD CONSTRAINT session_reminder_settings_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: subscription_intents_personal sint_personal_subscription_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscription_intents_personal
    ADD CONSTRAINT sint_personal_subscription_id_fkey FOREIGN KEY (subscription_id) REFERENCES public.subscriptions(id) ON DELETE SET NULL;


--
-- Name: subscription_intents_tenant sint_tenant_subscription_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscription_intents_tenant
    ADD CONSTRAINT sint_tenant_subscription_id_fkey FOREIGN KEY (subscription_id) REFERENCES public.subscriptions(id) ON DELETE SET NULL;


--
-- Name: subscription_events subscription_events_subscription_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscription_events
    ADD CONSTRAINT subscription_events_subscription_id_fkey FOREIGN KEY (subscription_id) REFERENCES public.subscriptions(id) ON DELETE CASCADE;


--
-- Name: subscription_intents_personal subscription_intents_personal_plan_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscription_intents_personal
    ADD CONSTRAINT subscription_intents_personal_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES public.plans(id) ON DELETE RESTRICT;


--
-- Name: subscription_intents_tenant subscription_intents_tenant_plan_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscription_intents_tenant
    ADD CONSTRAINT subscription_intents_tenant_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES public.plans(id) ON DELETE RESTRICT;


--
-- Name: subscription_intents_legacy subscription_intents_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscription_intents_legacy
    ADD CONSTRAINT subscription_intents_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: subscriptions subscriptions_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscriptions
    ADD CONSTRAINT subscriptions_owner_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: subscriptions subscriptions_plan_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.subscriptions
    ADD CONSTRAINT subscriptions_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES public.plans(id) ON DELETE RESTRICT;


--
-- Name: support_sessions support_sessions_admin_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.support_sessions
    ADD CONSTRAINT support_sessions_admin_fk FOREIGN KEY (admin_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: support_sessions support_sessions_tenant_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.support_sessions
    ADD CONSTRAINT support_sessions_tenant_fk FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: tenant_features tenant_features_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_features
    ADD CONSTRAINT tenant_features_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: tenant_invites tenant_invites_accepted_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_invites
    ADD CONSTRAINT tenant_invites_accepted_by_fkey FOREIGN KEY (accepted_by) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: tenant_invites tenant_invites_invited_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_invites
    ADD CONSTRAINT tenant_invites_invited_by_fkey FOREIGN KEY (invited_by) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: tenant_invites tenant_invites_revoked_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_invites
    ADD CONSTRAINT tenant_invites_revoked_by_fkey FOREIGN KEY (revoked_by) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: tenant_invites tenant_invites_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_invites
    ADD CONSTRAINT tenant_invites_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: tenant_members tenant_members_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_members
    ADD CONSTRAINT tenant_members_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: tenant_members tenant_members_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_members
    ADD CONSTRAINT tenant_members_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: tenant_modules tenant_modules_module_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_modules
    ADD CONSTRAINT tenant_modules_module_id_fkey FOREIGN KEY (module_id) REFERENCES public.modules(id) ON DELETE CASCADE;


--
-- Name: tenant_modules tenant_modules_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.tenant_modules
    ADD CONSTRAINT tenant_modules_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: therapist_payout_records therapist_payout_records_financial_record_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.therapist_payout_records
    ADD CONSTRAINT therapist_payout_records_financial_record_id_fkey FOREIGN KEY (financial_record_id) REFERENCES public.financial_records(id) ON DELETE RESTRICT;


--
-- Name: therapist_payout_records therapist_payout_records_payout_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.therapist_payout_records
    ADD CONSTRAINT therapist_payout_records_payout_id_fkey FOREIGN KEY (payout_id) REFERENCES public.therapist_payouts(id) ON DELETE CASCADE;


--
-- Name: therapist_payouts therapist_payouts_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.therapist_payouts
    ADD CONSTRAINT therapist_payouts_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: therapist_payouts therapist_payouts_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.therapist_payouts
    ADD CONSTRAINT therapist_payouts_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: twilio_subaccount_usage twilio_subaccount_usage_channel_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.twilio_subaccount_usage
    ADD CONSTRAINT twilio_subaccount_usage_channel_fk FOREIGN KEY (channel_id) REFERENCES public.notification_channels(id) ON DELETE CASCADE;


--
-- Name: user_settings user_settings_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.user_settings
    ADD CONSTRAINT user_settings_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;


--
-- Name: whatsapp_connection_incidents whatsapp_connection_incidents_channel_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_connection_incidents
    ADD CONSTRAINT whatsapp_connection_incidents_channel_id_fkey FOREIGN KEY (channel_id) REFERENCES public.notification_channels(id) ON DELETE CASCADE;


--
-- Name: whatsapp_connection_incidents whatsapp_connection_incidents_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_connection_incidents
    ADD CONSTRAINT whatsapp_connection_incidents_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: whatsapp_credit_purchases whatsapp_credit_purchases_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credit_purchases
    ADD CONSTRAINT whatsapp_credit_purchases_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: whatsapp_credit_purchases whatsapp_credit_purchases_package_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credit_purchases
    ADD CONSTRAINT whatsapp_credit_purchases_package_id_fkey FOREIGN KEY (package_id) REFERENCES public.whatsapp_credit_packages(id) ON DELETE SET NULL;


--
-- Name: whatsapp_credit_purchases whatsapp_credit_purchases_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credit_purchases
    ADD CONSTRAINT whatsapp_credit_purchases_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: whatsapp_credits_balance whatsapp_credits_balance_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credits_balance
    ADD CONSTRAINT whatsapp_credits_balance_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: whatsapp_credits_transactions whatsapp_credits_transactions_admin_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credits_transactions
    ADD CONSTRAINT whatsapp_credits_transactions_admin_id_fkey FOREIGN KEY (admin_id) REFERENCES auth.users(id) ON DELETE SET NULL;


--
-- Name: whatsapp_credits_transactions whatsapp_credits_transactions_conversation_message_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credits_transactions
    ADD CONSTRAINT whatsapp_credits_transactions_conversation_message_id_fkey FOREIGN KEY (conversation_message_id) REFERENCES public.conversation_messages(id) ON DELETE SET NULL;


--
-- Name: whatsapp_credits_transactions whatsapp_credits_transactions_purchase_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credits_transactions
    ADD CONSTRAINT whatsapp_credits_transactions_purchase_id_fkey FOREIGN KEY (purchase_id) REFERENCES public.whatsapp_credit_purchases(id) ON DELETE SET NULL;


--
-- Name: whatsapp_credits_transactions whatsapp_credits_transactions_tenant_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.whatsapp_credits_transactions
    ADD CONSTRAINT whatsapp_credits_transactions_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;


--
-- Name: iceberg_namespaces iceberg_namespaces_catalog_id_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.iceberg_namespaces
    ADD CONSTRAINT iceberg_namespaces_catalog_id_fkey FOREIGN KEY (catalog_id) REFERENCES storage.buckets_analytics(id) ON DELETE CASCADE;


--
-- Name: iceberg_tables iceberg_tables_catalog_id_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.iceberg_tables
    ADD CONSTRAINT iceberg_tables_catalog_id_fkey FOREIGN KEY (catalog_id) REFERENCES storage.buckets_analytics(id) ON DELETE CASCADE;


--
-- Name: iceberg_tables iceberg_tables_namespace_id_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.iceberg_tables
    ADD CONSTRAINT iceberg_tables_namespace_id_fkey FOREIGN KEY (namespace_id) REFERENCES storage.iceberg_namespaces(id) ON DELETE CASCADE;


--
-- Name: objects objects_bucketId_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.objects
    ADD CONSTRAINT "objects_bucketId_fkey" FOREIGN KEY (bucket_id) REFERENCES storage.buckets(id);


--
-- Name: s3_multipart_uploads s3_multipart_uploads_bucket_id_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.s3_multipart_uploads
    ADD CONSTRAINT s3_multipart_uploads_bucket_id_fkey FOREIGN KEY (bucket_id) REFERENCES storage.buckets(id);


--
-- Name: s3_multipart_uploads_parts s3_multipart_uploads_parts_bucket_id_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.s3_multipart_uploads_parts
    ADD CONSTRAINT s3_multipart_uploads_parts_bucket_id_fkey FOREIGN KEY (bucket_id) REFERENCES storage.buckets(id);


--
-- Name: s3_multipart_uploads_parts s3_multipart_uploads_parts_upload_id_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.s3_multipart_uploads_parts
    ADD CONSTRAINT s3_multipart_uploads_parts_upload_id_fkey FOREIGN KEY (upload_id) REFERENCES storage.s3_multipart_uploads(id) ON DELETE CASCADE;


--
-- Name: vector_indexes vector_indexes_bucket_id_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--

ALTER TABLE ONLY storage.vector_indexes
    ADD CONSTRAINT vector_indexes_bucket_id_fkey FOREIGN KEY (bucket_id) REFERENCES storage.buckets_vectors(id);


--
-- Name: audit_log_entries; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.audit_log_entries ENABLE ROW LEVEL SECURITY;

--
-- Name: flow_state; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.flow_state ENABLE ROW LEVEL SECURITY;

--
-- Name: identities; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.identities ENABLE ROW LEVEL SECURITY;

--
-- Name: instances; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.instances ENABLE ROW LEVEL SECURITY;

--
-- Name: mfa_amr_claims; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.mfa_amr_claims ENABLE ROW LEVEL SECURITY;

--
-- Name: mfa_challenges; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.mfa_challenges ENABLE ROW LEVEL SECURITY;

--
-- Name: mfa_factors; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.mfa_factors ENABLE ROW LEVEL SECURITY;

--
-- Name: one_time_tokens; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.one_time_tokens ENABLE ROW LEVEL SECURITY;

--
-- Name: refresh_tokens; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.refresh_tokens ENABLE ROW LEVEL SECURITY;

--
-- Name: saml_providers; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.saml_providers ENABLE ROW LEVEL SECURITY;

--
-- Name: saml_relay_states; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.saml_relay_states ENABLE ROW LEVEL SECURITY;

--
-- Name: schema_migrations; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.schema_migrations ENABLE ROW LEVEL SECURITY;

--
-- Name: sessions; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.sessions ENABLE ROW LEVEL SECURITY;

--
-- Name: sso_domains; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.sso_domains ENABLE ROW LEVEL SECURITY;

--
-- Name: sso_providers; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.sso_providers ENABLE ROW LEVEL SECURITY;

--
-- Name: users; Type: ROW SECURITY; Schema: auth; Owner: -
--

ALTER TABLE auth.users ENABLE ROW LEVEL SECURITY;

--
-- Name: addon_credits; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.addon_credits ENABLE ROW LEVEL SECURITY;

--
-- Name: addon_credits addon_credits_admin_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY addon_credits_admin_select ON public.addon_credits FOR SELECT TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid()))));


--
-- Name: addon_credits addon_credits_admin_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY addon_credits_admin_write ON public.addon_credits TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid())))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid()))));


--
-- Name: addon_credits addon_credits_select_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY addon_credits_select_own ON public.addon_credits FOR SELECT TO authenticated USING ((public.is_tenant_member(tenant_id) OR (owner_id = auth.uid())));


--
-- Name: addon_products; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.addon_products ENABLE ROW LEVEL SECURITY;

--
-- Name: addon_products addon_products_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY addon_products_admin_all ON public.addon_products TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid())))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid()))));


--
-- Name: addon_products addon_products_select_authenticated; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY addon_products_select_authenticated ON public.addon_products FOR SELECT TO authenticated USING (((deleted_at IS NULL) AND (is_active = true) AND (is_visible = true)));


--
-- Name: addon_transactions; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.addon_transactions ENABLE ROW LEVEL SECURITY;

--
-- Name: addon_transactions addon_transactions_admin_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY addon_transactions_admin_insert ON public.addon_transactions FOR INSERT TO authenticated WITH CHECK ((EXISTS ( SELECT 1
   FROM public.saas_admins sa
  WHERE (sa.user_id = auth.uid()))));


--
-- Name: addon_transactions addon_transactions_admin_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY addon_transactions_admin_select ON public.addon_transactions FOR SELECT TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid()))));


--
-- Name: addon_transactions addon_transactions_select_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY addon_transactions_select_own ON public.addon_transactions FOR SELECT TO authenticated USING ((public.is_tenant_member(tenant_id) OR (owner_id = auth.uid())));


--
-- Name: agenda_bloqueios; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.agenda_bloqueios ENABLE ROW LEVEL SECURITY;

--
-- Name: agenda_configuracoes; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.agenda_configuracoes ENABLE ROW LEVEL SECURITY;

--
-- Name: agenda_configuracoes agenda_configuracoes_clinic_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_configuracoes_clinic_read ON public.agenda_configuracoes FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.view'::text)));


--
-- Name: agenda_configuracoes agenda_configuracoes_clinic_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_configuracoes_clinic_write ON public.agenda_configuracoes USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text)));


--
-- Name: agenda_configuracoes agenda_configuracoes_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_configuracoes_owner ON public.agenda_configuracoes USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: agenda_eventos; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.agenda_eventos ENABLE ROW LEVEL SECURITY;

--
-- Name: agenda_eventos agenda_eventos_delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_eventos_delete ON public.agenda_eventos FOR DELETE USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.delete'::text)));


--
-- Name: agenda_eventos agenda_eventos_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_eventos_insert ON public.agenda_eventos FOR INSERT WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.create'::text)));


--
-- Name: agenda_eventos agenda_eventos_owner_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_eventos_owner_all ON public.agenda_eventos TO authenticated USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: agenda_eventos agenda_eventos_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_eventos_select ON public.agenda_eventos FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.view'::text)));


--
-- Name: agenda_eventos agenda_eventos_update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_eventos_update ON public.agenda_eventos FOR UPDATE USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text)));


--
-- Name: agenda_excecoes; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.agenda_excecoes ENABLE ROW LEVEL SECURITY;

--
-- Name: agenda_excecoes agenda_excecoes_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_excecoes_owner ON public.agenda_excecoes USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: agenda_excecoes agenda_excecoes_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_excecoes_select ON public.agenda_excecoes FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.view'::text)));


--
-- Name: agenda_excecoes agenda_excecoes_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_excecoes_write ON public.agenda_excecoes USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text)));


--
-- Name: agenda_online_slots; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.agenda_online_slots ENABLE ROW LEVEL SECURITY;

--
-- Name: agenda_online_slots agenda_online_slots_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_online_slots_owner ON public.agenda_online_slots USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: agenda_online_slots agenda_online_slots_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_online_slots_select ON public.agenda_online_slots FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.view'::text)));


--
-- Name: agenda_online_slots agenda_online_slots_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_online_slots_write ON public.agenda_online_slots USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text)));


--
-- Name: agenda_regras_semanais; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.agenda_regras_semanais ENABLE ROW LEVEL SECURITY;

--
-- Name: agenda_regras_semanais agenda_regras_semanais_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_regras_semanais_owner ON public.agenda_regras_semanais USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: agenda_regras_semanais agenda_regras_semanais_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_regras_semanais_select ON public.agenda_regras_semanais FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.view'::text)));


--
-- Name: agenda_regras_semanais agenda_regras_semanais_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_regras_semanais_write ON public.agenda_regras_semanais USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text)));


--
-- Name: agenda_slots_bloqueados_semanais; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.agenda_slots_bloqueados_semanais ENABLE ROW LEVEL SECURITY;

--
-- Name: agenda_slots_bloqueados_semanais agenda_slots_bloqueados_semanais_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_slots_bloqueados_semanais_select ON public.agenda_slots_bloqueados_semanais FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.view'::text)));


--
-- Name: agenda_slots_bloqueados_semanais agenda_slots_bloqueados_semanais_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_slots_bloqueados_semanais_write ON public.agenda_slots_bloqueados_semanais USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text)));


--
-- Name: agenda_slots_regras; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.agenda_slots_regras ENABLE ROW LEVEL SECURITY;

--
-- Name: agenda_slots_regras agenda_slots_regras_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_slots_regras_select ON public.agenda_slots_regras FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.view'::text)));


--
-- Name: agenda_slots_regras agenda_slots_regras_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agenda_slots_regras_write ON public.agenda_slots_regras USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text)));


--
-- Name: agendador_configuracoes agendador_cfg_public_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agendador_cfg_public_read ON public.agendador_configuracoes FOR SELECT TO anon USING (((ativo = true) AND (link_slug IS NOT NULL)));


--
-- Name: agendador_configuracoes agendador_cfg_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agendador_cfg_select ON public.agendador_configuracoes FOR SELECT USING ((auth.uid() = owner_id));


--
-- Name: agendador_configuracoes agendador_cfg_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agendador_cfg_write ON public.agendador_configuracoes USING ((auth.uid() = owner_id)) WITH CHECK ((auth.uid() = owner_id));


--
-- Name: agendador_configuracoes; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.agendador_configuracoes ENABLE ROW LEVEL SECURITY;

--
-- Name: agendador_solicitacoes agendador_sol_owner_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agendador_sol_owner_select ON public.agendador_solicitacoes FOR SELECT USING ((auth.uid() = owner_id));


--
-- Name: agendador_solicitacoes agendador_sol_owner_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agendador_sol_owner_write ON public.agendador_solicitacoes USING ((auth.uid() = owner_id)) WITH CHECK ((auth.uid() = owner_id));


--
-- Name: agendador_solicitacoes agendador_sol_patient_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agendador_sol_patient_read ON public.agendador_solicitacoes FOR SELECT TO authenticated USING (((auth.uid() = user_id) OR (auth.uid() = owner_id)));


--
-- Name: agendador_solicitacoes agendador_sol_public_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY agendador_sol_public_insert ON public.agendador_solicitacoes FOR INSERT TO anon WITH CHECK (true);


--
-- Name: agendador_solicitacoes; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.agendador_solicitacoes ENABLE ROW LEVEL SECURITY;

--
-- Name: audit_logs; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.audit_logs ENABLE ROW LEVEL SECURITY;

--
-- Name: audit_logs audit_logs: no direct delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "audit_logs: no direct delete" ON public.audit_logs FOR DELETE TO authenticated USING (false);


--
-- Name: audit_logs audit_logs: no direct insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "audit_logs: no direct insert" ON public.audit_logs FOR INSERT TO authenticated WITH CHECK (false);


--
-- Name: audit_logs audit_logs: no direct update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "audit_logs: no direct update" ON public.audit_logs FOR UPDATE TO authenticated USING (false) WITH CHECK (false);


--
-- Name: audit_logs audit_logs: select tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "audit_logs: select tenant" ON public.audit_logs FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_autoreply_log autoreply_log: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "autoreply_log: select" ON public.conversation_autoreply_log FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_autoreply_log.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_autoreply_settings autoreply_settings: insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "autoreply_settings: insert" ON public.conversation_autoreply_settings FOR INSERT TO authenticated WITH CHECK ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_autoreply_settings.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_autoreply_settings autoreply_settings: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "autoreply_settings: select" ON public.conversation_autoreply_settings FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_autoreply_settings.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_autoreply_settings autoreply_settings: update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "autoreply_settings: update" ON public.conversation_autoreply_settings FOR UPDATE TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_autoreply_settings.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: billing_contracts; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.billing_contracts ENABLE ROW LEVEL SECURITY;

--
-- Name: billing_contracts billing_contracts: delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "billing_contracts: delete" ON public.billing_contracts FOR DELETE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: billing_contracts billing_contracts: insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "billing_contracts: insert" ON public.billing_contracts FOR INSERT TO authenticated WITH CHECK (((owner_id = auth.uid()) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: billing_contracts billing_contracts: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "billing_contracts: select" ON public.billing_contracts FOR SELECT TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: billing_contracts billing_contracts: update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "billing_contracts: update" ON public.billing_contracts FOR UPDATE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin())) WITH CHECK (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: agenda_bloqueios bloqueios_delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY bloqueios_delete ON public.agenda_bloqueios FOR DELETE TO authenticated USING ((owner_id = auth.uid()));


--
-- Name: agenda_bloqueios bloqueios_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY bloqueios_insert ON public.agenda_bloqueios FOR INSERT TO authenticated WITH CHECK ((owner_id = auth.uid()));


--
-- Name: agenda_bloqueios bloqueios_select_clinic; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY bloqueios_select_clinic ON public.agenda_bloqueios FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT tenant_members.tenant_id
   FROM public.tenant_members
  WHERE ((tenant_members.user_id = auth.uid()) AND (tenant_members.role = ANY (ARRAY['admin'::text, 'clinic_admin'::text, 'tenant_admin'::text, 'secretary'::text]))))));


--
-- Name: agenda_bloqueios bloqueios_select_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY bloqueios_select_own ON public.agenda_bloqueios FOR SELECT TO authenticated USING ((owner_id = auth.uid()));


--
-- Name: agenda_bloqueios bloqueios_update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY bloqueios_update ON public.agenda_bloqueios FOR UPDATE TO authenticated USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: conversation_bot_sessions bot_sessions: select membros; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "bot_sessions: select membros" ON public.conversation_bot_sessions FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = conversation_bot_sessions.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_bot_sessions bot_sessions: write service_role; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "bot_sessions: write service_role" ON public.conversation_bot_sessions TO service_role USING (true) WITH CHECK (true);


--
-- Name: saas_docs clinic_admin_read_all_docs; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY clinic_admin_read_all_docs ON public.saas_docs FOR SELECT TO authenticated USING (((ativo = true) AND (EXISTS ( SELECT 1
   FROM public.profiles
  WHERE ((profiles.id = auth.uid()) AND (profiles.role = ANY (ARRAY['clinic_admin'::text, 'tenant_admin'::text])))))));


--
-- Name: commitment_services; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.commitment_services ENABLE ROW LEVEL SECURITY;

--
-- Name: commitment_services commitment_services: tenant_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "commitment_services: tenant_member" ON public.commitment_services TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.services s
  WHERE ((s.id = commitment_services.service_id) AND ((s.owner_id = auth.uid()) OR public.is_saas_admin() OR (s.tenant_id IN ( SELECT tm.tenant_id
           FROM public.tenant_members tm
          WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.services s
  WHERE ((s.id = commitment_services.service_id) AND ((s.owner_id = auth.uid()) OR public.is_saas_admin())))));


--
-- Name: commitment_time_logs; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.commitment_time_logs ENABLE ROW LEVEL SECURITY;

--
-- Name: company_profiles; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.company_profiles ENABLE ROW LEVEL SECURITY;

--
-- Name: company_profiles company_profiles_delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY company_profiles_delete ON public.company_profiles FOR DELETE USING ((EXISTS ( SELECT 1
   FROM public.tenant_members
  WHERE ((tenant_members.tenant_id = company_profiles.tenant_id) AND (tenant_members.user_id = auth.uid())))));


--
-- Name: company_profiles company_profiles_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY company_profiles_insert ON public.company_profiles FOR INSERT WITH CHECK ((EXISTS ( SELECT 1
   FROM public.tenant_members
  WHERE ((tenant_members.tenant_id = company_profiles.tenant_id) AND (tenant_members.user_id = auth.uid())))));


--
-- Name: company_profiles company_profiles_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY company_profiles_select ON public.company_profiles FOR SELECT USING ((EXISTS ( SELECT 1
   FROM public.tenant_members
  WHERE ((tenant_members.tenant_id = company_profiles.tenant_id) AND (tenant_members.user_id = auth.uid())))));


--
-- Name: company_profiles company_profiles_update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY company_profiles_update ON public.company_profiles FOR UPDATE USING ((EXISTS ( SELECT 1
   FROM public.tenant_members
  WHERE ((tenant_members.tenant_id = company_profiles.tenant_id) AND (tenant_members.user_id = auth.uid()))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.tenant_members
  WHERE ((tenant_members.tenant_id = company_profiles.tenant_id) AND (tenant_members.user_id = auth.uid())))));


--
-- Name: contact_email_types; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.contact_email_types ENABLE ROW LEVEL SECURITY;

--
-- Name: contact_email_types contact_email_types: manage custom; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "contact_email_types: manage custom" ON public.contact_email_types TO authenticated USING (((is_system = false) AND (tenant_id IS NOT NULL) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = contact_email_types.tenant_id) AND (tm.status = 'active'::text))))))) WITH CHECK (((is_system = false) AND (tenant_id IS NOT NULL) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = contact_email_types.tenant_id) AND (tm.status = 'active'::text)))))));


--
-- Name: contact_email_types contact_email_types: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "contact_email_types: select" ON public.contact_email_types FOR SELECT TO authenticated USING (((tenant_id IS NULL) OR public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = contact_email_types.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: contact_emails; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.contact_emails ENABLE ROW LEVEL SECURITY;

--
-- Name: contact_emails contact_emails: all tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "contact_emails: all tenant" ON public.contact_emails TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = contact_emails.tenant_id) AND (tm.status = 'active'::text)))))) WITH CHECK ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = contact_emails.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: contact_phones; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.contact_phones ENABLE ROW LEVEL SECURITY;

--
-- Name: contact_phones contact_phones: all tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "contact_phones: all tenant" ON public.contact_phones TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = contact_phones.tenant_id) AND (tm.status = 'active'::text)))))) WITH CHECK ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = contact_phones.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: contact_types; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.contact_types ENABLE ROW LEVEL SECURITY;

--
-- Name: contact_types contact_types: manage custom; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "contact_types: manage custom" ON public.contact_types TO authenticated USING (((is_system = false) AND (tenant_id IS NOT NULL) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = contact_types.tenant_id) AND (tm.status = 'active'::text))))))) WITH CHECK (((is_system = false) AND (tenant_id IS NOT NULL) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = contact_types.tenant_id) AND (tm.status = 'active'::text)))))));


--
-- Name: contact_types contact_types: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "contact_types: select" ON public.contact_types FOR SELECT TO authenticated USING (((tenant_id IS NULL) OR public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = contact_types.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_assignments conv_assign: insert tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_assign: insert tenant" ON public.conversation_assignments FOR INSERT TO authenticated WITH CHECK (((assigned_by = auth.uid()) AND (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_assignments.tenant_id) AND (tm.status = 'active'::text)))) AND ((assigned_to IS NULL) OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm2
  WHERE ((tm2.user_id = conversation_assignments.assigned_to) AND (tm2.tenant_id = conversation_assignments.tenant_id) AND (tm2.status = 'active'::text)))))));


--
-- Name: conversation_assignments conv_assign: select tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_assign: select tenant" ON public.conversation_assignments FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_assignments.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_assignments conv_assign: update tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_assign: update tenant" ON public.conversation_assignments FOR UPDATE TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_assignments.tenant_id) AND (tm.status = 'active'::text))))) WITH CHECK (((assigned_by = auth.uid()) AND ((assigned_to IS NULL) OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm2
  WHERE ((tm2.user_id = conversation_assignments.assigned_to) AND (tm2.tenant_id = conversation_assignments.tenant_id) AND (tm2.status = 'active'::text)))))));


--
-- Name: conversation_bots conv_bots: select membros; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_bots: select membros" ON public.conversation_bots FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = conversation_bots.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_bots conv_bots: write admins; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_bots: write admins" ON public.conversation_bots TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = conversation_bots.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.role = ANY (ARRAY['clinic_admin'::text, 'tenant_admin'::text])) AND (tm.status = 'active'::text)))))) WITH CHECK ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = conversation_bots.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.role = ANY (ARRAY['clinic_admin'::text, 'tenant_admin'::text])) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_messages conv_msg: no direct delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_msg: no direct delete" ON public.conversation_messages FOR DELETE TO authenticated USING (false);


--
-- Name: conversation_messages conv_msg: no direct insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_msg: no direct insert" ON public.conversation_messages FOR INSERT TO authenticated WITH CHECK (false);


--
-- Name: conversation_messages conv_msg: select tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_msg: select tenant" ON public.conversation_messages FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_messages conv_msg: update kanban; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_msg: update kanban" ON public.conversation_messages FOR UPDATE TO authenticated USING ((tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))) WITH CHECK ((tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: conversation_notes conv_notes: insert tenant members; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_notes: insert tenant members" ON public.conversation_notes FOR INSERT TO authenticated WITH CHECK (((created_by = auth.uid()) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_notes.tenant_id) AND (tm.status = 'active'::text)))))));


--
-- Name: conversation_notes conv_notes: select tenant members; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_notes: select tenant members" ON public.conversation_notes FOR SELECT TO authenticated USING (((deleted_at IS NULL) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_notes.tenant_id) AND (tm.status = 'active'::text)))))));


--
-- Name: conversation_notes conv_notes: update creator or saas; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_notes: update creator or saas" ON public.conversation_notes FOR UPDATE TO authenticated USING (((deleted_at IS NULL) AND ((created_by = auth.uid()) OR public.is_saas_admin()))) WITH CHECK ((created_by = ( SELECT conversation_notes_1.created_by
   FROM public.conversation_notes conversation_notes_1
  WHERE (conversation_notes_1.id = conversation_notes_1.id))));


--
-- Name: conversation_tags conv_tags: delete custom; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_tags: delete custom" ON public.conversation_tags FOR DELETE TO authenticated USING (((is_system = false) AND (tenant_id IS NOT NULL) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_tags.tenant_id) AND (tm.status = 'active'::text)))))));


--
-- Name: conversation_tags conv_tags: insert custom; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_tags: insert custom" ON public.conversation_tags FOR INSERT TO authenticated WITH CHECK (((tenant_id IS NOT NULL) AND (is_system = false) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_tags.tenant_id) AND (tm.status = 'active'::text)))))));


--
-- Name: conversation_tags conv_tags: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_tags: select" ON public.conversation_tags FOR SELECT TO authenticated USING (((tenant_id IS NULL) OR public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_tags.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_tags conv_tags: update custom; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_tags: update custom" ON public.conversation_tags FOR UPDATE TO authenticated USING (((is_system = false) AND (tenant_id IS NOT NULL) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_tags.tenant_id) AND (tm.status = 'active'::text))))))) WITH CHECK ((is_system = false));


--
-- Name: conversation_thread_tags conv_thread_tags: delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_thread_tags: delete" ON public.conversation_thread_tags FOR DELETE TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_thread_tags.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_thread_tags conv_thread_tags: insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_thread_tags: insert" ON public.conversation_thread_tags FOR INSERT TO authenticated WITH CHECK (((tagged_by = auth.uid()) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_thread_tags.tenant_id) AND (tm.status = 'active'::text)))))));


--
-- Name: conversation_thread_tags conv_thread_tags: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "conv_thread_tags: select" ON public.conversation_thread_tags FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_thread_tags.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_assignments; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_assignments ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_autoreply_log; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_autoreply_log ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_autoreply_settings; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_autoreply_settings ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_bot_sessions; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_bot_sessions ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_bots; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_bots ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_messages; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_messages ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_notes; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_notes ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_optout_keywords; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_optout_keywords ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_optouts; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_optouts ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_sla_breaches; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_sla_breaches ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_sla_rules; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_sla_rules ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_tags; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_tags ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_thread_tags; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.conversation_thread_tags ENABLE ROW LEVEL SECURITY;

--
-- Name: commitment_time_logs ctl_delete_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY ctl_delete_for_active_member ON public.commitment_time_logs FOR DELETE TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = commitment_time_logs.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: commitment_time_logs ctl_insert_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY ctl_insert_for_active_member ON public.commitment_time_logs FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: commitment_time_logs ctl_select_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY ctl_select_for_active_member ON public.commitment_time_logs FOR SELECT TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = commitment_time_logs.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: commitment_time_logs ctl_update_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY ctl_update_for_active_member ON public.commitment_time_logs FOR UPDATE TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = commitment_time_logs.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = commitment_time_logs.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: document_access_logs dal: tenant members can insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dal: tenant members can insert" ON public.document_access_logs FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: document_access_logs dal: tenant members can select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dal: tenant members can select" ON public.document_access_logs FOR SELECT USING ((tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: determined_commitments dc_delete_custom_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dc_delete_custom_for_active_member ON public.determined_commitments FOR DELETE TO authenticated USING (((is_native = false) AND (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = determined_commitments.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: determined_commitments dc_insert_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dc_insert_for_active_member ON public.determined_commitments FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: determined_commitments dc_select_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dc_select_for_active_member ON public.determined_commitments FOR SELECT TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = determined_commitments.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: determined_commitments dc_update_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dc_update_for_active_member ON public.determined_commitments FOR UPDATE TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = determined_commitments.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = determined_commitments.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: determined_commitment_fields dcf_delete_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dcf_delete_for_active_member ON public.determined_commitment_fields FOR DELETE TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = determined_commitment_fields.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: determined_commitment_fields dcf_insert_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dcf_insert_for_active_member ON public.determined_commitment_fields FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: determined_commitment_fields dcf_select_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dcf_select_for_active_member ON public.determined_commitment_fields FOR SELECT TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = determined_commitment_fields.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: determined_commitment_fields dcf_update_for_active_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dcf_update_for_active_member ON public.determined_commitment_fields FOR UPDATE TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = determined_commitment_fields.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = determined_commitment_fields.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: agenda_bloqueios delete own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "delete own" ON public.agenda_bloqueios FOR DELETE USING ((owner_id = auth.uid()));


--
-- Name: determined_commitment_fields; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.determined_commitment_fields ENABLE ROW LEVEL SECURITY;

--
-- Name: determined_commitments; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.determined_commitments ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_auditoria_items; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_auditoria_items ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_auditoria_items dev_auditoria_items_saas_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_auditoria_items_saas_admin_all ON public.dev_auditoria_items TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: dev_comparison_competitor_status; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_comparison_competitor_status ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_comparison_competitor_status dev_comparison_competitor_status_saas_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_comparison_competitor_status_saas_admin_all ON public.dev_comparison_competitor_status TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: dev_comparison_matrix; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_comparison_matrix ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_comparison_matrix dev_comparison_matrix_saas_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_comparison_matrix_saas_admin_all ON public.dev_comparison_matrix TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: dev_competitor_features; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_competitor_features ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_competitor_features dev_competitor_features_saas_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_competitor_features_saas_admin_all ON public.dev_competitor_features TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: dev_competitors; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_competitors ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_competitors dev_competitors_saas_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_competitors_saas_admin_all ON public.dev_competitors TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: dev_user_credentials dev_creds_select_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_creds_select_saas_admin ON public.dev_user_credentials FOR SELECT TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.profiles p
  WHERE ((p.id = auth.uid()) AND (p.role = 'saas_admin'::text)))));


--
-- Name: dev_user_credentials dev_creds_write_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_creds_write_saas_admin ON public.dev_user_credentials TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.profiles p
  WHERE ((p.id = auth.uid()) AND (p.role = 'saas_admin'::text))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.profiles p
  WHERE ((p.id = auth.uid()) AND (p.role = 'saas_admin'::text)))));


--
-- Name: dev_generation_log; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_generation_log ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_generation_log dev_generation_log_saas_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_generation_log_saas_admin_all ON public.dev_generation_log TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: dev_roadmap_items; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_roadmap_items ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_roadmap_items dev_roadmap_items_saas_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_roadmap_items_saas_admin_all ON public.dev_roadmap_items TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: dev_roadmap_phases; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_roadmap_phases ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_roadmap_phases dev_roadmap_phases_saas_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_roadmap_phases_saas_admin_all ON public.dev_roadmap_phases TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: dev_test_items; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_test_items ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_test_items dev_test_items_saas_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_test_items_saas_admin_all ON public.dev_test_items TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: dev_user_credentials; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_user_credentials ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_verificacoes_items; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.dev_verificacoes_items ENABLE ROW LEVEL SECURITY;

--
-- Name: dev_verificacoes_items dev_verificacoes_items_saas_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY dev_verificacoes_items_saas_admin_all ON public.dev_verificacoes_items TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: document_generated dg: generator full access; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dg: generator full access" ON public.document_generated USING ((gerado_por = auth.uid())) WITH CHECK ((gerado_por = auth.uid()));


--
-- Name: document_generated dg: tenant members can select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dg: tenant members can select" ON public.document_generated FOR SELECT USING ((tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))));


--
-- Name: document_access_logs; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.document_access_logs ENABLE ROW LEVEL SECURITY;

--
-- Name: document_generated; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.document_generated ENABLE ROW LEVEL SECURITY;

--
-- Name: document_share_links; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.document_share_links ENABLE ROW LEVEL SECURITY;

--
-- Name: document_signatures; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.document_signatures ENABLE ROW LEVEL SECURITY;

--
-- Name: document_templates; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.document_templates ENABLE ROW LEVEL SECURITY;

--
-- Name: documents; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY;

--
-- Name: documents documents: delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "documents: delete" ON public.documents FOR DELETE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: documents documents: insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "documents: insert" ON public.documents FOR INSERT TO authenticated WITH CHECK (((owner_id = auth.uid()) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: documents documents: portal patient read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "documents: portal patient read" ON public.documents FOR SELECT TO authenticated USING (((compartilhado_portal = true) AND (patient_id IN ( SELECT p.id
   FROM public.patients p
  WHERE (p.user_id = auth.uid()))) AND ((expira_compartilhamento IS NULL) OR (expira_compartilhamento > now()))));


--
-- Name: POLICY "documents: portal patient read" ON documents; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY "documents: portal patient read" ON public.documents IS 'V#50: paciente lê documento quando compartilhado_portal=true E patient_id pertence ao auth.uid + não expirou.';


--
-- Name: documents documents: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "documents: select" ON public.documents FOR SELECT TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: documents documents: update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "documents: update" ON public.documents FOR UPDATE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin())) WITH CHECK (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: document_signatures ds: delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "ds: delete" ON public.document_signatures FOR DELETE TO authenticated USING (((signatario_id = auth.uid()) OR public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text])))))));


--
-- Name: document_signatures ds: insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "ds: insert" ON public.document_signatures FOR INSERT TO authenticated WITH CHECK (((tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))) AND ((signatario_id IS NULL) OR (signatario_id = auth.uid()))));


--
-- Name: document_signatures ds: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "ds: select" ON public.document_signatures FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: document_signatures ds: update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "ds: update" ON public.document_signatures FOR UPDATE TO authenticated USING (((signatario_id = auth.uid()) OR public.is_saas_admin())) WITH CHECK (((signatario_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: document_share_links dsl: creator full access; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dsl: creator full access" ON public.document_share_links TO authenticated USING (((criado_por = auth.uid()) OR public.is_saas_admin())) WITH CHECK ((criado_por = auth.uid()));


--
-- Name: document_templates dt: global templates readable by all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dt: global templates readable by all" ON public.document_templates FOR SELECT USING ((is_global = true));


--
-- Name: document_templates dt: owner can delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dt: owner can delete" ON public.document_templates FOR DELETE USING (((owner_id = auth.uid()) AND (is_global = false)));


--
-- Name: document_templates dt: owner can insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dt: owner can insert" ON public.document_templates FOR INSERT TO authenticated WITH CHECK (((is_global = false) AND (owner_id = auth.uid()) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: document_templates dt: owner can update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dt: owner can update" ON public.document_templates FOR UPDATE USING (((owner_id = auth.uid()) AND (is_global = false))) WITH CHECK (((owner_id = auth.uid()) AND (is_global = false)));


--
-- Name: document_templates dt: saas admin can delete global; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dt: saas admin can delete global" ON public.document_templates FOR DELETE USING (((is_global = true) AND public.is_saas_admin()));


--
-- Name: document_templates dt: saas admin can insert global; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dt: saas admin can insert global" ON public.document_templates FOR INSERT TO authenticated WITH CHECK (((is_global = true) AND public.is_saas_admin()));


--
-- Name: document_templates dt: saas admin can update global; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dt: saas admin can update global" ON public.document_templates FOR UPDATE USING (((is_global = true) AND public.is_saas_admin())) WITH CHECK (((is_global = true) AND public.is_saas_admin()));


--
-- Name: document_templates dt: tenant members can select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "dt: tenant members can select" ON public.document_templates FOR SELECT USING (((is_global = false) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: email_layout_config; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.email_layout_config ENABLE ROW LEVEL SECURITY;

--
-- Name: email_layout_config email_layout_config: tenant_admin all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "email_layout_config: tenant_admin all" ON public.email_layout_config TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text]))))))) WITH CHECK ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text])))))));


--
-- Name: email_templates_global; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.email_templates_global ENABLE ROW LEVEL SECURITY;

--
-- Name: email_templates_tenant; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.email_templates_tenant ENABLE ROW LEVEL SECURITY;

--
-- Name: email_templates_tenant email_templates_tenant: tenant_admin all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "email_templates_tenant: tenant_admin all" ON public.email_templates_tenant TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text]))))))) WITH CHECK ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text])))))));


--
-- Name: entitlements_invalidation ent_inv_select_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY ent_inv_select_own ON public.entitlements_invalidation FOR SELECT USING (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: entitlements_invalidation ent_inv_update_saas; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY ent_inv_update_saas ON public.entitlements_invalidation FOR UPDATE USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: entitlements_invalidation ent_inv_write_saas; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY ent_inv_write_saas ON public.entitlements_invalidation FOR INSERT WITH CHECK (public.is_saas_admin());


--
-- Name: entitlements_invalidation; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.entitlements_invalidation ENABLE ROW LEVEL SECURITY;

--
-- Name: saas_faq faq_auth_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY faq_auth_read ON public.saas_faq FOR SELECT TO authenticated USING ((ativo = true));


--
-- Name: saas_faq_itens faq_itens_auth_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY faq_itens_auth_read ON public.saas_faq_itens FOR SELECT TO authenticated USING (((ativo = true) AND (EXISTS ( SELECT 1
   FROM public.saas_docs d
  WHERE ((d.id = saas_faq_itens.doc_id) AND (d.ativo = true))))));


--
-- Name: saas_faq_itens faq_itens_saas_admin_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY faq_itens_saas_admin_write ON public.saas_faq_itens TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: saas_faq faq_public_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY faq_public_read ON public.saas_faq FOR SELECT USING (((publico = true) AND (ativo = true)));


--
-- Name: saas_faq faq_saas_admin_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY faq_saas_admin_write ON public.saas_faq TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: features; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.features ENABLE ROW LEVEL SECURITY;

--
-- Name: features features_read_authenticated; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY features_read_authenticated ON public.features FOR SELECT TO authenticated USING (true);


--
-- Name: POLICY features_read_authenticated ON features; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY features_read_authenticated ON public.features IS 'Qualquer logado lê o catálogo de features.';


--
-- Name: features features_write_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY features_write_saas_admin ON public.features TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: POLICY features_write_saas_admin ON features; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY features_write_saas_admin ON public.features IS 'Somente saas_admin escreve. DELETE = soft delete via is_active=false (V#40).';


--
-- Name: feriados; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.feriados ENABLE ROW LEVEL SECURITY;

--
-- Name: feriados feriados_delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY feriados_delete ON public.feriados FOR DELETE TO authenticated USING (((owner_id = auth.uid()) OR ((tenant_id IS NOT NULL) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text]))))))));


--
-- Name: feriados feriados_global_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY feriados_global_select ON public.feriados FOR SELECT USING ((tenant_id IS NULL));


--
-- Name: feriados feriados_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY feriados_insert ON public.feriados FOR INSERT TO authenticated WITH CHECK (((tenant_id IS NOT NULL) AND (owner_id = auth.uid()) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: feriados feriados_saas_delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY feriados_saas_delete ON public.feriados FOR DELETE USING ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid()))));


--
-- Name: feriados feriados_saas_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY feriados_saas_insert ON public.feriados FOR INSERT TO authenticated WITH CHECK (((tenant_id IS NULL) AND (EXISTS ( SELECT 1
   FROM public.saas_admins sa
  WHERE (sa.user_id = auth.uid())))));


--
-- Name: feriados feriados_saas_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY feriados_saas_select ON public.feriados FOR SELECT USING ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid()))));


--
-- Name: feriados feriados_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY feriados_select ON public.feriados FOR SELECT USING ((tenant_id IN ( SELECT tenant_members.tenant_id
   FROM public.tenant_members
  WHERE (tenant_members.user_id = auth.uid()))));


--
-- Name: financial_categories; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.financial_categories ENABLE ROW LEVEL SECURITY;

--
-- Name: financial_categories financial_categories_self; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY financial_categories_self ON public.financial_categories USING ((auth.uid() = user_id)) WITH CHECK ((auth.uid() = user_id));


--
-- Name: financial_exceptions; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.financial_exceptions ENABLE ROW LEVEL SECURITY;

--
-- Name: financial_exceptions financial_exceptions: owner full access; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "financial_exceptions: owner full access" ON public.financial_exceptions USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: financial_exceptions financial_exceptions: tenant members read clinic rules; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "financial_exceptions: tenant members read clinic rules" ON public.financial_exceptions FOR SELECT USING (((owner_id IS NULL) AND (EXISTS ( SELECT 1
   FROM public.owner_users ou
  WHERE ((ou.owner_id = financial_exceptions.tenant_id) AND (ou.user_id = auth.uid()))))));


--
-- Name: financial_records; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.financial_records ENABLE ROW LEVEL SECURITY;

--
-- Name: financial_records financial_records_self; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY financial_records_self ON public.financial_records USING ((auth.uid() = owner_id)) WITH CHECK ((auth.uid() = owner_id));


--
-- Name: financial_records financial_records_tenant_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY financial_records_tenant_admin ON public.financial_records FOR SELECT USING (((tenant_id IS NOT NULL) AND public.is_tenant_admin(tenant_id)));


--
-- Name: financial_records financial_records_tenant_member_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY financial_records_tenant_member_read ON public.financial_records FOR SELECT USING (((tenant_id IS NOT NULL) AND (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = financial_records.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: email_templates_global global templates readable by authenticated; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "global templates readable by authenticated" ON public.email_templates_global FOR SELECT USING ((auth.role() = 'authenticated'::text));


--
-- Name: global_notices; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.global_notices ENABLE ROW LEVEL SECURITY;

--
-- Name: global_notices global_notices_saas_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY global_notices_saas_all ON public.global_notices TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid()))));


--
-- Name: global_notices global_notices_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY global_notices_select ON public.global_notices FOR SELECT TO authenticated USING ((is_active = true));


--
-- Name: agenda_bloqueios insert own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "insert own" ON public.agenda_bloqueios FOR INSERT WITH CHECK ((owner_id = auth.uid()));


--
-- Name: insurance_plan_services; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.insurance_plan_services ENABLE ROW LEVEL SECURITY;

--
-- Name: insurance_plan_services insurance_plan_services: tenant_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "insurance_plan_services: tenant_member" ON public.insurance_plan_services TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.insurance_plans ip
  WHERE ((ip.id = insurance_plan_services.insurance_plan_id) AND ((ip.owner_id = auth.uid()) OR public.is_saas_admin() OR (ip.tenant_id IN ( SELECT tm.tenant_id
           FROM public.tenant_members tm
          WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.insurance_plans ip
  WHERE ((ip.id = insurance_plan_services.insurance_plan_id) AND ((ip.owner_id = auth.uid()) OR public.is_saas_admin())))));


--
-- Name: insurance_plans; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.insurance_plans ENABLE ROW LEVEL SECURITY;

--
-- Name: insurance_plans insurance_plans: delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "insurance_plans: delete" ON public.insurance_plans FOR DELETE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: insurance_plans insurance_plans: insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "insurance_plans: insert" ON public.insurance_plans FOR INSERT TO authenticated WITH CHECK (((owner_id = auth.uid()) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: insurance_plans insurance_plans: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "insurance_plans: select" ON public.insurance_plans FOR SELECT TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: insurance_plans insurance_plans: update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "insurance_plans: update" ON public.insurance_plans FOR UPDATE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin())) WITH CHECK (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: login_carousel_slides; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.login_carousel_slides ENABLE ROW LEVEL SECURITY;

--
-- Name: math_challenges; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.math_challenges ENABLE ROW LEVEL SECURITY;

--
-- Name: medicos; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.medicos ENABLE ROW LEVEL SECURITY;

--
-- Name: medicos medicos: delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "medicos: delete" ON public.medicos FOR DELETE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: medicos medicos: insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "medicos: insert" ON public.medicos FOR INSERT TO authenticated WITH CHECK (((owner_id = auth.uid()) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: medicos medicos: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "medicos: select" ON public.medicos FOR SELECT TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: medicos medicos: update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "medicos: update" ON public.medicos FOR UPDATE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin())) WITH CHECK (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: module_features; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.module_features ENABLE ROW LEVEL SECURITY;

--
-- Name: module_features module_features_read_authenticated; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY module_features_read_authenticated ON public.module_features FOR SELECT TO authenticated USING (true);


--
-- Name: module_features module_features_write_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY module_features_write_saas_admin ON public.module_features TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: modules; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.modules ENABLE ROW LEVEL SECURITY;

--
-- Name: modules modules_read_authenticated; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY modules_read_authenticated ON public.modules FOR SELECT TO authenticated USING (true);


--
-- Name: modules modules_write_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY modules_write_saas_admin ON public.modules TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: notice_dismissals; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.notice_dismissals ENABLE ROW LEVEL SECURITY;

--
-- Name: notice_dismissals notice_dismissals_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notice_dismissals_own ON public.notice_dismissals TO authenticated USING ((user_id = auth.uid())) WITH CHECK ((user_id = auth.uid()));


--
-- Name: notification_channels notif_channels_delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_channels_delete ON public.notification_channels FOR DELETE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: notification_channels notif_channels_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_channels_insert ON public.notification_channels FOR INSERT TO authenticated WITH CHECK ((public.is_saas_admin() OR ((owner_id = auth.uid()) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))))));


--
-- Name: POLICY notif_channels_insert ON notification_channels; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY notif_channels_insert ON public.notification_channels IS 'SaaS admin pode inserir em nome de qualquer tenant; tenant_member insere pra si mesmo.';


--
-- Name: notification_channels notif_channels_modify; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_channels_modify ON public.notification_channels FOR UPDATE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin())) WITH CHECK (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: notification_channels notif_channels_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_channels_select ON public.notification_channels FOR SELECT USING ((public.is_saas_admin() OR (owner_id = auth.uid()) OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: POLICY notif_channels_select ON notification_channels; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY notif_channels_select ON public.notification_channels IS 'Owner, membros ativos do tenant e saas_admin leem todos os canais (inclusive soft-deleted). Filtro deleted_at fica no codigo aplicativo.';


--
-- Name: notification_logs notif_logs_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_logs_owner ON public.notification_logs FOR SELECT USING ((owner_id = auth.uid()));


--
-- Name: notification_logs notif_logs_tenant_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_logs_tenant_member ON public.notification_logs FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: notification_preferences notif_prefs_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_prefs_owner ON public.notification_preferences USING (((owner_id = auth.uid()) AND (deleted_at IS NULL))) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: notification_queue notif_queue_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_queue_owner ON public.notification_queue FOR SELECT USING ((owner_id = auth.uid()));


--
-- Name: notification_queue notif_queue_tenant_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_queue_tenant_member ON public.notification_queue FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: notification_schedules notif_schedules_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_schedules_owner ON public.notification_schedules USING (((owner_id = auth.uid()) AND (deleted_at IS NULL))) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: notification_templates notif_templates_admin_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_templates_admin_all ON public.notification_templates TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid())))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid()))));


--
-- Name: notification_templates notif_templates_read_global; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_templates_read_global ON public.notification_templates FOR SELECT TO authenticated USING (((deleted_at IS NULL) AND (((tenant_id IS NULL) AND (is_default = true)) OR (owner_id = auth.uid()) OR public.is_tenant_member(tenant_id))));


--
-- Name: notification_templates notif_templates_write_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY notif_templates_write_owner ON public.notification_templates TO authenticated USING (((owner_id = auth.uid()) OR public.is_tenant_member(tenant_id))) WITH CHECK (((owner_id = auth.uid()) OR public.is_tenant_member(tenant_id)));


--
-- Name: notification_channels; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.notification_channels ENABLE ROW LEVEL SECURITY;

--
-- Name: notification_logs; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.notification_logs ENABLE ROW LEVEL SECURITY;

--
-- Name: notification_preferences; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.notification_preferences ENABLE ROW LEVEL SECURITY;

--
-- Name: notification_queue; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.notification_queue ENABLE ROW LEVEL SECURITY;

--
-- Name: notification_schedules; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.notification_schedules ENABLE ROW LEVEL SECURITY;

--
-- Name: notification_templates; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.notification_templates ENABLE ROW LEVEL SECURITY;

--
-- Name: notifications; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.notifications ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_optout_keywords optout_kw: delete custom; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "optout_kw: delete custom" ON public.conversation_optout_keywords FOR DELETE TO authenticated USING (((is_system = false) AND (tenant_id IS NOT NULL) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_optout_keywords.tenant_id) AND (tm.status = 'active'::text)))))));


--
-- Name: conversation_optout_keywords optout_kw: insert custom; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "optout_kw: insert custom" ON public.conversation_optout_keywords FOR INSERT TO authenticated WITH CHECK (((tenant_id IS NOT NULL) AND (is_system = false) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_optout_keywords.tenant_id) AND (tm.status = 'active'::text)))))));


--
-- Name: conversation_optout_keywords optout_kw: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "optout_kw: select" ON public.conversation_optout_keywords FOR SELECT TO authenticated USING (((tenant_id IS NULL) OR public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_optout_keywords.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_optout_keywords optout_kw: update/delete custom; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "optout_kw: update/delete custom" ON public.conversation_optout_keywords FOR UPDATE TO authenticated USING (((is_system = false) AND (tenant_id IS NOT NULL) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_optout_keywords.tenant_id) AND (tm.status = 'active'::text)))))));


--
-- Name: conversation_optouts optouts: insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "optouts: insert" ON public.conversation_optouts FOR INSERT TO authenticated WITH CHECK ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_optouts.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_optouts optouts: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "optouts: select" ON public.conversation_optouts FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_optouts.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_optouts optouts: update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "optouts: update" ON public.conversation_optouts FOR UPDATE TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = conversation_optouts.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: notifications owner only; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "owner only" ON public.notifications USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: owner_users; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.owner_users ENABLE ROW LEVEL SECURITY;

--
-- Name: owner_users owner_users: user can read own links; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "owner_users: user can read own links" ON public.owner_users FOR SELECT TO authenticated USING ((user_id = auth.uid()));


--
-- Name: patient_contacts; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_contacts ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_contacts patient_contacts_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_contacts_select ON public.patient_contacts FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.view'::text)));


--
-- Name: patient_contacts patient_contacts_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_contacts_write ON public.patient_contacts USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text)));


--
-- Name: patient_discounts; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_discounts ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_discounts patient_discounts: owner full access; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "patient_discounts: owner full access" ON public.patient_discounts USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: patient_group_patient; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_group_patient ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_group_patient patient_group_patient_owner_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_group_patient_owner_all ON public.patient_group_patient TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.patients p
  WHERE ((p.id = patient_group_patient.patient_id) AND (p.owner_id = auth.uid()))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.patients p
  WHERE ((p.id = patient_group_patient.patient_id) AND (p.owner_id = auth.uid())))));


--
-- Name: patient_group_patient patient_group_patient_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_group_patient_select ON public.patient_group_patient FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.view'::text)));


--
-- Name: patient_group_patient patient_group_patient_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_group_patient_write ON public.patient_group_patient USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text)));


--
-- Name: patient_groups; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_groups ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_groups patient_groups_owner_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_groups_owner_all ON public.patient_groups TO authenticated USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: patient_groups patient_groups_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_groups_select ON public.patient_groups FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.view'::text)));


--
-- Name: patient_groups patient_groups_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_groups_write ON public.patient_groups USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text)));


--
-- Name: patient_intake_requests; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_intake_requests ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_intake_requests patient_intake_requests_owner_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_intake_requests_owner_all ON public.patient_intake_requests TO authenticated USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: patient_intake_requests patient_intake_requests_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_intake_requests_select ON public.patient_intake_requests FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.view'::text)));


--
-- Name: patient_intake_requests patient_intake_requests_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_intake_requests_write ON public.patient_intake_requests USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text)));


--
-- Name: patient_invite_attempts; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_invite_attempts ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_invite_attempts patient_invite_attempts_owner_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_invite_attempts_owner_read ON public.patient_invite_attempts FOR SELECT TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: patient_invites; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_invites ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_invites patient_invites_owner_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_invites_owner_all ON public.patient_invites TO authenticated USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: patient_invites patient_invites_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_invites_select ON public.patient_invites FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.view'::text)));


--
-- Name: patient_invites patient_invites_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_invites_write ON public.patient_invites USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text)));


--
-- Name: patient_patient_tag; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_patient_tag ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_patient_tag patient_patient_tag_owner_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_patient_tag_owner_all ON public.patient_patient_tag TO authenticated USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: patient_patient_tag patient_patient_tag_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_patient_tag_select ON public.patient_patient_tag FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.view'::text)));


--
-- Name: patient_patient_tag patient_patient_tag_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_patient_tag_write ON public.patient_patient_tag USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text)));


--
-- Name: patient_status_history; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_status_history ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_support_contacts; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_support_contacts ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_tags; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_tags ENABLE ROW LEVEL SECURITY;

--
-- Name: patient_tags patient_tags_owner_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_tags_owner_all ON public.patient_tags TO authenticated USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: patient_tags patient_tags_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_tags_select ON public.patient_tags FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.view'::text)));


--
-- Name: patient_tags patient_tags_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patient_tags_write ON public.patient_tags USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text)));


--
-- Name: patient_timeline; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patient_timeline ENABLE ROW LEVEL SECURITY;

--
-- Name: patients; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.patients ENABLE ROW LEVEL SECURITY;

--
-- Name: patients patients_delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patients_delete ON public.patients FOR DELETE USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.delete'::text)));


--
-- Name: patients patients_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patients_insert ON public.patients FOR INSERT WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.create'::text)));


--
-- Name: patients patients_owner_all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patients_owner_all ON public.patients TO authenticated USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: patients patients_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patients_select ON public.patients FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.view'::text)));


--
-- Name: patients patients_update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY patients_update ON public.patients FOR UPDATE USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text)));


--
-- Name: payment_settings; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.payment_settings ENABLE ROW LEVEL SECURITY;

--
-- Name: payment_settings payment_settings: owner full access; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "payment_settings: owner full access" ON public.payment_settings USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: payment_settings payment_settings: tenant_admin read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "payment_settings: tenant_admin read" ON public.payment_settings FOR SELECT TO authenticated USING (((tenant_id IS NOT NULL) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text])))))));


--
-- Name: plan_features; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.plan_features ENABLE ROW LEVEL SECURITY;

--
-- Name: plan_features plan_features_read_authenticated; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY plan_features_read_authenticated ON public.plan_features FOR SELECT TO authenticated USING (true);


--
-- Name: POLICY plan_features_read_authenticated ON plan_features; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY plan_features_read_authenticated ON public.plan_features IS 'Qualquer logado lê o vínculo plano↔feature (necessário para entitlements).';


--
-- Name: plan_features plan_features_write_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY plan_features_write_saas_admin ON public.plan_features TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: POLICY plan_features_write_saas_admin ON plan_features; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY plan_features_write_saas_admin ON public.plan_features IS 'Somente saas_admin escreve.';


--
-- Name: plan_prices; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.plan_prices ENABLE ROW LEVEL SECURITY;

--
-- Name: plan_prices plan_prices_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY plan_prices_read ON public.plan_prices FOR SELECT TO authenticated USING (true);


--
-- Name: plan_prices plan_prices_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY plan_prices_write ON public.plan_prices TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: plan_public; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.plan_public ENABLE ROW LEVEL SECURITY;

--
-- Name: plan_public_bullets; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.plan_public_bullets ENABLE ROW LEVEL SECURITY;

--
-- Name: plan_public_bullets plan_public_bullets_read_anon; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY plan_public_bullets_read_anon ON public.plan_public_bullets FOR SELECT TO authenticated, anon USING (true);


--
-- Name: plan_public_bullets plan_public_bullets_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY plan_public_bullets_write ON public.plan_public_bullets TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: plan_public plan_public_read_anon; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY plan_public_read_anon ON public.plan_public FOR SELECT TO authenticated, anon USING (true);


--
-- Name: plan_public plan_public_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY plan_public_write ON public.plan_public TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: plans; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.plans ENABLE ROW LEVEL SECURITY;

--
-- Name: plans plans_read_authenticated; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY plans_read_authenticated ON public.plans FOR SELECT TO authenticated USING (true);


--
-- Name: POLICY plans_read_authenticated ON plans; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY plans_read_authenticated ON public.plans IS 'Qualquer usuário autenticado lê o catálogo de planos (vitrine, upgrade UI).';


--
-- Name: plans plans_write_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY plans_write_saas_admin ON public.plans TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: POLICY plans_write_saas_admin ON plans; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY plans_write_saas_admin ON public.plans IS 'Somente saas_admin escreve. DELETE deve ser via RPC delete_plan_safe (V#36).';


--
-- Name: professional_pricing; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.professional_pricing ENABLE ROW LEVEL SECURITY;

--
-- Name: professional_pricing professional_pricing: owner full access; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "professional_pricing: owner full access" ON public.professional_pricing USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: professional_pricing professional_pricing: tenant_admin read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "professional_pricing: tenant_admin read" ON public.professional_pricing FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text]))))));


--
-- Name: profiles; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;

--
-- Name: profiles profiles_insert_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY profiles_insert_own ON public.profiles FOR INSERT TO authenticated WITH CHECK ((id = auth.uid()));


--
-- Name: profiles profiles_read_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY profiles_read_saas_admin ON public.profiles FOR SELECT USING (public.is_saas_admin());


--
-- Name: profiles profiles_select_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY profiles_select_own ON public.profiles FOR SELECT USING ((id = auth.uid()));


--
-- Name: profiles profiles_update_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY profiles_update_own ON public.profiles FOR UPDATE USING ((id = auth.uid())) WITH CHECK ((id = auth.uid()));


--
-- Name: public_submission_attempts psa_read_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY psa_read_saas_admin ON public.public_submission_attempts FOR SELECT TO authenticated USING (public.is_saas_admin());


--
-- Name: patient_support_contacts psc: owner full access; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "psc: owner full access" ON public.patient_support_contacts USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: patient_status_history psh_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY psh_insert ON public.patient_status_history FOR INSERT WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text)));


--
-- Name: patient_status_history psh_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY psh_select ON public.patient_status_history FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.view'::text)));


--
-- Name: patient_timeline pt_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY pt_insert ON public.patient_timeline FOR INSERT WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.edit'::text)));


--
-- Name: patient_timeline pt_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY pt_select ON public.patient_timeline FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'patients.view'::text)));


--
-- Name: login_carousel_slides public_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY public_read ON public.login_carousel_slides FOR SELECT USING ((ativo = true));


--
-- Name: public_submission_attempts; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.public_submission_attempts ENABLE ROW LEVEL SECURITY;

--
-- Name: recurrence_exceptions; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.recurrence_exceptions ENABLE ROW LEVEL SECURITY;

--
-- Name: recurrence_exceptions recurrence_exceptions_tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY recurrence_exceptions_tenant ON public.recurrence_exceptions TO authenticated USING ((tenant_id IN ( SELECT tenant_members.tenant_id
   FROM public.tenant_members
  WHERE (tenant_members.user_id = auth.uid())))) WITH CHECK ((tenant_id IN ( SELECT tenant_members.tenant_id
   FROM public.tenant_members
  WHERE (tenant_members.user_id = auth.uid()))));


--
-- Name: recurrence_rule_services; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.recurrence_rule_services ENABLE ROW LEVEL SECURITY;

--
-- Name: recurrence_rule_services recurrence_rule_services: clinic read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "recurrence_rule_services: clinic read" ON public.recurrence_rule_services FOR SELECT USING ((EXISTS ( SELECT 1
   FROM public.recurrence_rules r
  WHERE ((r.id = recurrence_rule_services.rule_id) AND public.is_clinic_tenant(r.tenant_id) AND public.is_tenant_member(r.tenant_id) AND public.tenant_has_feature(r.tenant_id, 'agenda.view'::text)))));


--
-- Name: recurrence_rule_services recurrence_rule_services: clinic write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "recurrence_rule_services: clinic write" ON public.recurrence_rule_services USING ((EXISTS ( SELECT 1
   FROM public.recurrence_rules r
  WHERE ((r.id = recurrence_rule_services.rule_id) AND public.is_clinic_tenant(r.tenant_id) AND public.is_tenant_member(r.tenant_id) AND public.tenant_has_feature(r.tenant_id, 'agenda.edit'::text))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.recurrence_rules r
  WHERE ((r.id = recurrence_rule_services.rule_id) AND public.is_clinic_tenant(r.tenant_id) AND public.is_tenant_member(r.tenant_id) AND public.tenant_has_feature(r.tenant_id, 'agenda.edit'::text)))));


--
-- Name: recurrence_rule_services recurrence_rule_services: owner full access; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "recurrence_rule_services: owner full access" ON public.recurrence_rule_services TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.recurrence_rules r
  WHERE ((r.id = recurrence_rule_services.rule_id) AND (r.owner_id = auth.uid()))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.recurrence_rules r
  WHERE ((r.id = recurrence_rule_services.rule_id) AND (r.owner_id = auth.uid())))));


--
-- Name: recurrence_rules; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.recurrence_rules ENABLE ROW LEVEL SECURITY;

--
-- Name: recurrence_rules recurrence_rules_clinic_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY recurrence_rules_clinic_read ON public.recurrence_rules FOR SELECT USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.view'::text)));


--
-- Name: recurrence_rules recurrence_rules_clinic_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY recurrence_rules_clinic_write ON public.recurrence_rules USING ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text))) WITH CHECK ((public.is_clinic_tenant(tenant_id) AND public.is_tenant_member(tenant_id) AND public.tenant_has_feature(tenant_id, 'agenda.edit'::text)));


--
-- Name: recurrence_rules recurrence_rules_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY recurrence_rules_owner ON public.recurrence_rules TO authenticated USING ((owner_id = auth.uid())) WITH CHECK ((owner_id = auth.uid()));


--
-- Name: session_reminder_logs reminder_logs: tenant members select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "reminder_logs: tenant members select" ON public.session_reminder_logs FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = session_reminder_logs.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: session_reminder_settings reminder_settings: tenant members all; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "reminder_settings: tenant members all" ON public.session_reminder_settings TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = session_reminder_settings.tenant_id) AND (tm.status = 'active'::text)))))) WITH CHECK ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = session_reminder_settings.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: subscription_intents_legacy saas_admin can read subscription_intents; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "saas_admin can read subscription_intents" ON public.subscription_intents_legacy FOR SELECT TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.saas_admins a
  WHERE (a.user_id = auth.uid()))));


--
-- Name: subscription_intents_legacy saas_admin can update subscription_intents; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "saas_admin can update subscription_intents" ON public.subscription_intents_legacy FOR UPDATE TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.saas_admins a
  WHERE (a.user_id = auth.uid())))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.saas_admins a
  WHERE (a.user_id = auth.uid()))));


--
-- Name: login_carousel_slides saas_admin_full; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY saas_admin_full ON public.login_carousel_slides USING ((EXISTS ( SELECT 1
   FROM public.profiles
  WHERE ((profiles.id = auth.uid()) AND (profiles.role = 'saas_admin'::text)))));


--
-- Name: saas_docs saas_admin_full_access; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY saas_admin_full_access ON public.saas_docs TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid())))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid()))));


--
-- Name: saas_admins; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.saas_admins ENABLE ROW LEVEL SECURITY;

--
-- Name: saas_admins saas_admins_select_self; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY saas_admins_select_self ON public.saas_admins FOR SELECT TO authenticated USING ((user_id = auth.uid()));


--
-- Name: saas_doc_votos; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.saas_doc_votos ENABLE ROW LEVEL SECURITY;

--
-- Name: saas_docs; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.saas_docs ENABLE ROW LEVEL SECURITY;

--
-- Name: saas_faq; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.saas_faq ENABLE ROW LEVEL SECURITY;

--
-- Name: saas_faq_itens; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.saas_faq_itens ENABLE ROW LEVEL SECURITY;

--
-- Name: saas_security_config; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.saas_security_config ENABLE ROW LEVEL SECURITY;

--
-- Name: saas_security_config saas_security_config_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY saas_security_config_read ON public.saas_security_config FOR SELECT TO authenticated USING (true);


--
-- Name: saas_security_config saas_security_config_write; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY saas_security_config_write ON public.saas_security_config FOR UPDATE TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: saas_twilio_config; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.saas_twilio_config ENABLE ROW LEVEL SECURITY;

--
-- Name: saas_twilio_config saas_twilio_config_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY saas_twilio_config_read ON public.saas_twilio_config FOR SELECT TO authenticated USING (public.is_saas_admin());


--
-- Name: agenda_bloqueios select own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "select own" ON public.agenda_bloqueios FOR SELECT USING ((owner_id = auth.uid()));


--
-- Name: twilio_subaccount_usage service_role_manage_usage; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY service_role_manage_usage ON public.twilio_subaccount_usage USING ((auth.role() = 'service_role'::text));


--
-- Name: services; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.services ENABLE ROW LEVEL SECURITY;

--
-- Name: services services: delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "services: delete" ON public.services FOR DELETE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: services services: insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "services: insert" ON public.services FOR INSERT TO authenticated WITH CHECK (((owner_id = auth.uid()) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: services services: select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "services: select" ON public.services FOR SELECT TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: services services: update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "services: update" ON public.services FOR UPDATE TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin())) WITH CHECK (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: session_reminder_logs; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.session_reminder_logs ENABLE ROW LEVEL SECURITY;

--
-- Name: session_reminder_settings; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.session_reminder_settings ENABLE ROW LEVEL SECURITY;

--
-- Name: conversation_sla_breaches sla_breaches: select membros/admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "sla_breaches: select membros/admin" ON public.conversation_sla_breaches FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = conversation_sla_breaches.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_sla_breaches sla_breaches: write service_role; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "sla_breaches: write service_role" ON public.conversation_sla_breaches TO service_role USING (true) WITH CHECK (true);


--
-- Name: conversation_sla_rules sla_rules: select membros/admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "sla_rules: select membros/admin" ON public.conversation_sla_rules FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = conversation_sla_rules.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: conversation_sla_rules sla_rules: write admins; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "sla_rules: write admins" ON public.conversation_sla_rules TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = conversation_sla_rules.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.role = ANY (ARRAY['clinic_admin'::text, 'tenant_admin'::text])) AND (tm.status = 'active'::text)))))) WITH CHECK ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = conversation_sla_rules.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.role = ANY (ARRAY['clinic_admin'::text, 'tenant_admin'::text])) AND (tm.status = 'active'::text))))));


--
-- Name: submission_rate_limits srl_read_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY srl_read_saas_admin ON public.submission_rate_limits FOR SELECT TO authenticated USING (public.is_saas_admin());


--
-- Name: submission_rate_limits; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.submission_rate_limits ENABLE ROW LEVEL SECURITY;

--
-- Name: subscription_events; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.subscription_events ENABLE ROW LEVEL SECURITY;

--
-- Name: subscription_events subscription_events_read_saas; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY subscription_events_read_saas ON public.subscription_events FOR SELECT USING (public.is_saas_admin());


--
-- Name: subscription_events subscription_events_write_saas; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY subscription_events_write_saas ON public.subscription_events FOR INSERT WITH CHECK (public.is_saas_admin());


--
-- Name: subscription_intents_legacy subscription_intents_insert_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY subscription_intents_insert_own ON public.subscription_intents_legacy FOR INSERT TO authenticated WITH CHECK ((user_id = auth.uid()));


--
-- Name: subscription_intents_legacy; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.subscription_intents_legacy ENABLE ROW LEVEL SECURITY;

--
-- Name: subscription_intents_personal; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.subscription_intents_personal ENABLE ROW LEVEL SECURITY;

--
-- Name: subscription_intents_personal subscription_intents_personal_owner; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY subscription_intents_personal_owner ON public.subscription_intents_personal TO authenticated USING (((user_id = auth.uid()) OR public.is_saas_admin())) WITH CHECK (((user_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: subscription_intents_legacy subscription_intents_select_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY subscription_intents_select_own ON public.subscription_intents_legacy FOR SELECT TO authenticated USING ((user_id = auth.uid()));


--
-- Name: subscription_intents_tenant; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.subscription_intents_tenant ENABLE ROW LEVEL SECURITY;

--
-- Name: subscription_intents_tenant subscription_intents_tenant_member; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY subscription_intents_tenant_member ON public.subscription_intents_tenant TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text]))))))) WITH CHECK ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text])))))));


--
-- Name: subscriptions; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.subscriptions ENABLE ROW LEVEL SECURITY;

--
-- Name: subscriptions subscriptions: read if linked owner_users; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "subscriptions: read if linked owner_users" ON public.subscriptions FOR SELECT TO authenticated USING ((EXISTS ( SELECT 1
   FROM public.owner_users ou
  WHERE ((ou.owner_id = subscriptions.user_id) AND (ou.user_id = auth.uid())))));


--
-- Name: POLICY "subscriptions: read if linked owner_users" ON subscriptions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY "subscriptions: read if linked owner_users" ON public.subscriptions IS 'Caso especial: usuários ligados ao owner via owner_users (terapeutas de uma clínica que precisam ver a assinatura do owner).';


--
-- Name: subscriptions subscriptions_insert_own_personal; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY subscriptions_insert_own_personal ON public.subscriptions FOR INSERT TO authenticated WITH CHECK (((user_id = auth.uid()) AND (tenant_id IS NULL)));


--
-- Name: POLICY subscriptions_insert_own_personal ON subscriptions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY subscriptions_insert_own_personal ON public.subscriptions IS 'Usuário cria a própria assinatura pessoal (intent → conversion).';


--
-- Name: subscriptions subscriptions_read_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY subscriptions_read_own ON public.subscriptions FOR SELECT TO authenticated USING (((user_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: POLICY subscriptions_read_own ON subscriptions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY subscriptions_read_own ON public.subscriptions IS 'Dono da assinatura (user_id) ou saas_admin. Cobre o caso pessoal.';


--
-- Name: subscriptions subscriptions_select_for_tenant_members; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY subscriptions_select_for_tenant_members ON public.subscriptions FOR SELECT TO authenticated USING (((tenant_id IS NOT NULL) AND (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = subscriptions.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: POLICY subscriptions_select_for_tenant_members ON subscriptions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY subscriptions_select_for_tenant_members ON public.subscriptions IS 'Membros ativos do tenant leem assinaturas do tenant.';


--
-- Name: subscriptions subscriptions_update_only_saas_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY subscriptions_update_only_saas_admin ON public.subscriptions FOR UPDATE TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: POLICY subscriptions_update_only_saas_admin ON subscriptions; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON POLICY subscriptions_update_only_saas_admin ON public.subscriptions IS 'UPDATE direto somente via saas_admin. Mudanças de tenant devem passar por RPC dedicada.';


--
-- Name: support_sessions; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.support_sessions ENABLE ROW LEVEL SECURITY;

--
-- Name: support_sessions support_sessions_saas_delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY support_sessions_saas_delete ON public.support_sessions FOR DELETE USING (((auth.uid() = admin_id) AND (EXISTS ( SELECT 1
   FROM public.profiles
  WHERE ((profiles.id = auth.uid()) AND (profiles.role = 'saas_admin'::text))))));


--
-- Name: support_sessions support_sessions_saas_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY support_sessions_saas_insert ON public.support_sessions FOR INSERT TO authenticated WITH CHECK (((admin_id = auth.uid()) AND (EXISTS ( SELECT 1
   FROM public.saas_admins sa
  WHERE (sa.user_id = auth.uid())))));


--
-- Name: support_sessions support_sessions_saas_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY support_sessions_saas_select ON public.support_sessions FOR SELECT USING (((auth.uid() = admin_id) AND (EXISTS ( SELECT 1
   FROM public.profiles
  WHERE ((profiles.id = auth.uid()) AND (profiles.role = 'saas_admin'::text))))));


--
-- Name: tenant_feature_exceptions_log; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.tenant_feature_exceptions_log ENABLE ROW LEVEL SECURITY;

--
-- Name: tenant_feature_exceptions_log tenant_feature_exceptions_log_read; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_feature_exceptions_log_read ON public.tenant_feature_exceptions_log FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: tenant_features; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.tenant_features ENABLE ROW LEVEL SECURITY;

--
-- Name: tenant_features tenant_features_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_features_select ON public.tenant_features FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: tenant_features tenant_features_write_saas_only; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_features_write_saas_only ON public.tenant_features TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: tenant_invites; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.tenant_invites ENABLE ROW LEVEL SECURITY;

--
-- Name: tenant_invites tenant_invites_delete; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_invites_delete ON public.tenant_invites FOR DELETE TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text])))))));


--
-- Name: tenant_invites tenant_invites_insert; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_invites_insert ON public.tenant_invites FOR INSERT TO authenticated WITH CHECK (((invited_by = auth.uid()) AND (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text])))))));


--
-- Name: tenant_invites tenant_invites_select; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_invites_select ON public.tenant_invites FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text])))))));


--
-- Name: tenant_invites tenant_invites_update; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_invites_update ON public.tenant_invites FOR UPDATE TO authenticated USING ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text]))))))) WITH CHECK ((public.is_saas_admin() OR (tenant_id IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND (tm.role = ANY (ARRAY['tenant_admin'::text, 'admin'::text, 'owner'::text])))))));


--
-- Name: tenant_members; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.tenant_members ENABLE ROW LEVEL SECURITY;

--
-- Name: tenant_members tenant_members_write_saas; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_members_write_saas ON public.tenant_members TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: tenant_modules; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.tenant_modules ENABLE ROW LEVEL SECURITY;

--
-- Name: tenant_modules tenant_modules_read_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_modules_read_own ON public.tenant_modules FOR SELECT TO authenticated USING (((owner_id = auth.uid()) OR public.is_saas_admin()));


--
-- Name: tenant_modules tenant_modules_write_saas; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_modules_write_saas ON public.tenant_modules TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: twilio_subaccount_usage tenant_select_own_usage; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenant_select_own_usage ON public.twilio_subaccount_usage FOR SELECT USING ((tenant_id IN ( SELECT tenant_members.tenant_id
   FROM public.tenant_members
  WHERE (tenant_members.user_id = auth.uid()))));


--
-- Name: tenants; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.tenants ENABLE ROW LEVEL SECURITY;

--
-- Name: tenants tenants_read_members; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenants_read_members ON public.tenants FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = tenants.id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: tenants tenants_write_saas; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tenants_write_saas ON public.tenants TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: therapist_payout_records; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.therapist_payout_records ENABLE ROW LEVEL SECURITY;

--
-- Name: therapist_payout_records therapist_payout_records_self; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY therapist_payout_records_self ON public.therapist_payout_records USING ((EXISTS ( SELECT 1
   FROM public.therapist_payouts tp
  WHERE ((tp.id = therapist_payout_records.payout_id) AND (tp.owner_id = auth.uid()))))) WITH CHECK ((EXISTS ( SELECT 1
   FROM public.therapist_payouts tp
  WHERE ((tp.id = therapist_payout_records.payout_id) AND (tp.owner_id = auth.uid())))));


--
-- Name: therapist_payout_records therapist_payout_records_tenant_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY therapist_payout_records_tenant_admin ON public.therapist_payout_records FOR SELECT USING ((EXISTS ( SELECT 1
   FROM public.therapist_payouts tp
  WHERE ((tp.id = therapist_payout_records.payout_id) AND public.is_tenant_admin(tp.tenant_id)))));


--
-- Name: therapist_payouts; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.therapist_payouts ENABLE ROW LEVEL SECURITY;

--
-- Name: therapist_payouts therapist_payouts_self; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY therapist_payouts_self ON public.therapist_payouts USING ((auth.uid() = owner_id)) WITH CHECK ((auth.uid() = owner_id));


--
-- Name: therapist_payouts therapist_payouts_tenant_admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY therapist_payouts_tenant_admin ON public.therapist_payouts FOR SELECT USING (((tenant_id IS NOT NULL) AND public.is_tenant_admin(tenant_id)));


--
-- Name: tenant_members tm_select_admin_all_members; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tm_select_admin_all_members ON public.tenant_members FOR SELECT TO authenticated USING (public.is_tenant_admin(tenant_id));


--
-- Name: tenant_members tm_select_own_membership; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY tm_select_own_membership ON public.tenant_members FOR SELECT TO authenticated USING ((user_id = auth.uid()));


--
-- Name: twilio_subaccount_usage; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.twilio_subaccount_usage ENABLE ROW LEVEL SECURITY;

--
-- Name: agenda_bloqueios update own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "update own" ON public.agenda_bloqueios FOR UPDATE USING ((owner_id = auth.uid()));


--
-- Name: user_settings; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.user_settings ENABLE ROW LEVEL SECURITY;

--
-- Name: user_settings user_settings_insert_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY user_settings_insert_own ON public.user_settings FOR INSERT TO authenticated WITH CHECK ((user_id = auth.uid()));


--
-- Name: user_settings user_settings_select_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY user_settings_select_own ON public.user_settings FOR SELECT USING ((user_id = auth.uid()));


--
-- Name: user_settings user_settings_update_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY user_settings_update_own ON public.user_settings FOR UPDATE USING ((user_id = auth.uid())) WITH CHECK ((user_id = auth.uid()));


--
-- Name: saas_docs users_read_usuario_docs; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY users_read_usuario_docs ON public.saas_docs FOR SELECT TO authenticated USING (((ativo = true) AND (tipo_acesso = 'usuario'::text)));


--
-- Name: saas_doc_votos votos_select_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY votos_select_own ON public.saas_doc_votos FOR SELECT TO authenticated USING ((user_id = auth.uid()));


--
-- Name: saas_doc_votos votos_upsert_own; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY votos_upsert_own ON public.saas_doc_votos TO authenticated USING ((user_id = auth.uid())) WITH CHECK ((user_id = auth.uid()));


--
-- Name: whatsapp_credits_balance wa_credits_balance: select tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "wa_credits_balance: select tenant" ON public.whatsapp_credits_balance FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = whatsapp_credits_balance.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: whatsapp_credits_balance wa_credits_balance: update tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "wa_credits_balance: update tenant" ON public.whatsapp_credits_balance FOR UPDATE TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = whatsapp_credits_balance.tenant_id) AND (tm.status = 'active'::text)))))) WITH CHECK ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = whatsapp_credits_balance.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: whatsapp_credits_transactions wa_credits_tx: select tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "wa_credits_tx: select tenant" ON public.whatsapp_credits_transactions FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = whatsapp_credits_transactions.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: whatsapp_connection_incidents wa_incidents: select membros/admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "wa_incidents: select membros/admin" ON public.whatsapp_connection_incidents FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.tenant_id = whatsapp_connection_incidents.tenant_id) AND (tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: whatsapp_connection_incidents wa_incidents: write service_role; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "wa_incidents: write service_role" ON public.whatsapp_connection_incidents TO service_role USING (true) WITH CHECK (true);


--
-- Name: whatsapp_credit_packages wa_packages: manage saas admin; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "wa_packages: manage saas admin" ON public.whatsapp_credit_packages TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());


--
-- Name: whatsapp_credit_packages wa_packages: select active; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "wa_packages: select active" ON public.whatsapp_credit_packages FOR SELECT TO authenticated USING (((is_active = true) OR public.is_saas_admin()));


--
-- Name: whatsapp_credit_purchases wa_purchases: select tenant; Type: POLICY; Schema: public; Owner: -
--

CREATE POLICY "wa_purchases: select tenant" ON public.whatsapp_credit_purchases FOR SELECT TO authenticated USING ((public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.tenant_id = whatsapp_credit_purchases.tenant_id) AND (tm.status = 'active'::text))))));


--
-- Name: whatsapp_connection_incidents; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.whatsapp_connection_incidents ENABLE ROW LEVEL SECURITY;

--
-- Name: whatsapp_credit_packages; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.whatsapp_credit_packages ENABLE ROW LEVEL SECURITY;

--
-- Name: whatsapp_credit_purchases; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.whatsapp_credit_purchases ENABLE ROW LEVEL SECURITY;

--
-- Name: whatsapp_credits_balance; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.whatsapp_credits_balance ENABLE ROW LEVEL SECURITY;

--
-- Name: whatsapp_credits_transactions; Type: ROW SECURITY; Schema: public; Owner: -
--

ALTER TABLE public.whatsapp_credits_transactions ENABLE ROW LEVEL SECURITY;

--
-- Name: messages; Type: ROW SECURITY; Schema: realtime; Owner: -
--

ALTER TABLE realtime.messages ENABLE ROW LEVEL SECURITY;

--
-- Name: objects Allow authenticated updates; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "Allow authenticated updates" ON storage.objects FOR UPDATE TO authenticated USING ((bucket_id = ANY (ARRAY['avatars'::text, 'logos'::text])));


--
-- Name: objects Allow authenticated uploads; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "Allow authenticated uploads" ON storage.objects FOR INSERT TO authenticated WITH CHECK ((bucket_id = ANY (ARRAY['avatars'::text, 'logos'::text])));


--
-- Name: objects Public read; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "Public read" ON storage.objects FOR SELECT USING ((bucket_id = ANY (ARRAY['avatars'::text, 'logos'::text])));


--
-- Name: objects agendador_storage_owner_delete; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY agendador_storage_owner_delete ON storage.objects FOR DELETE TO authenticated USING (((bucket_id = 'agendador'::text) AND ((storage.foldername(name))[1] = (auth.uid())::text)));


--
-- Name: objects agendador_storage_owner_insert; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY agendador_storage_owner_insert ON storage.objects FOR INSERT TO authenticated WITH CHECK (((bucket_id = 'agendador'::text) AND ((storage.foldername(name))[1] = (auth.uid())::text)));


--
-- Name: objects agendador_storage_owner_update; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY agendador_storage_owner_update ON storage.objects FOR UPDATE TO authenticated USING (((bucket_id = 'agendador'::text) AND ((storage.foldername(name))[1] = (auth.uid())::text)));


--
-- Name: objects agendador_storage_public_read; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY agendador_storage_public_read ON storage.objects FOR SELECT USING ((bucket_id = 'agendador'::text));


--
-- Name: objects avatars authenticated upload; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "avatars authenticated upload" ON storage.objects FOR INSERT WITH CHECK (((bucket_id = 'avatars'::text) AND (auth.role() = 'authenticated'::text) AND ((storage.foldername(name))[1] = (auth.uid())::text)));


--
-- Name: objects avatars owner delete; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "avatars owner delete" ON storage.objects FOR DELETE USING (((bucket_id = 'avatars'::text) AND ((storage.foldername(name))[1] = (auth.uid())::text)));


--
-- Name: objects avatars owner update; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "avatars owner update" ON storage.objects FOR UPDATE USING (((bucket_id = 'avatars'::text) AND ((storage.foldername(name))[1] = (auth.uid())::text)));


--
-- Name: objects avatars public read; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "avatars public read" ON storage.objects FOR SELECT USING ((bucket_id = 'avatars'::text));


--
-- Name: objects avatars_delete_own; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY avatars_delete_own ON storage.objects FOR DELETE TO authenticated USING (((bucket_id = 'avatars'::text) AND (name ~~ ((auth.uid())::text || '/%'::text))));


--
-- Name: objects avatars_delete_own_folder; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY avatars_delete_own_folder ON storage.objects FOR DELETE USING (((bucket_id = 'avatars'::text) AND (auth.role() = 'authenticated'::text) AND (name ~~ (('owners/'::text || auth.uid()) || '/%'::text))));


--
-- Name: objects avatars_insert_own; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY avatars_insert_own ON storage.objects FOR INSERT TO authenticated WITH CHECK (((bucket_id = 'avatars'::text) AND (name ~~ ((auth.uid())::text || '/%'::text))));


--
-- Name: objects avatars_insert_own_folder; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY avatars_insert_own_folder ON storage.objects FOR INSERT WITH CHECK (((bucket_id = 'avatars'::text) AND (auth.role() = 'authenticated'::text) AND (name ~~ (('owners/'::text || auth.uid()) || '/%'::text))));


--
-- Name: objects avatars_read; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY avatars_read ON storage.objects FOR SELECT USING ((bucket_id = 'avatars'::text));


--
-- Name: objects avatars_select_own; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY avatars_select_own ON storage.objects FOR SELECT TO authenticated USING (((bucket_id = 'avatars'::text) AND (name ~~ ((auth.uid())::text || '/%'::text))));


--
-- Name: objects avatars_update_own; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY avatars_update_own ON storage.objects FOR UPDATE TO authenticated USING (((bucket_id = 'avatars'::text) AND (name ~~ ((auth.uid())::text || '/%'::text)))) WITH CHECK (((bucket_id = 'avatars'::text) AND (name ~~ ((auth.uid())::text || '/%'::text))));


--
-- Name: objects avatars_update_own_folder; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY avatars_update_own_folder ON storage.objects FOR UPDATE USING (((bucket_id = 'avatars'::text) AND (auth.role() = 'authenticated'::text) AND (name ~~ (('owners/'::text || auth.uid()) || '/%'::text)))) WITH CHECK (((bucket_id = 'avatars'::text) AND (auth.role() = 'authenticated'::text) AND (name ~~ (('owners/'::text || auth.uid()) || '/%'::text))));


--
-- Name: buckets; Type: ROW SECURITY; Schema: storage; Owner: -
--

ALTER TABLE storage.buckets ENABLE ROW LEVEL SECURITY;

--
-- Name: buckets_analytics; Type: ROW SECURITY; Schema: storage; Owner: -
--

ALTER TABLE storage.buckets_analytics ENABLE ROW LEVEL SECURITY;

--
-- Name: buckets_vectors; Type: ROW SECURITY; Schema: storage; Owner: -
--

ALTER TABLE storage.buckets_vectors ENABLE ROW LEVEL SECURITY;

--
-- Name: objects documents: tenant member delete; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "documents: tenant member delete" ON storage.objects FOR DELETE TO authenticated USING (((bucket_id = 'documents'::text) AND (public.is_saas_admin() OR (((storage.foldername(name))[1])::uuid IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))))));


--
-- Name: objects documents: tenant member read; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "documents: tenant member read" ON storage.objects FOR SELECT TO authenticated USING (((bucket_id = 'documents'::text) AND (public.is_saas_admin() OR (((storage.foldername(name))[1])::uuid IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))))));


--
-- Name: objects documents: tenant member upload; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "documents: tenant member upload" ON storage.objects FOR INSERT TO authenticated WITH CHECK (((bucket_id = 'documents'::text) AND (((storage.foldername(name))[1])::uuid IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: objects generated-docs: tenant member delete; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "generated-docs: tenant member delete" ON storage.objects FOR DELETE TO authenticated USING (((bucket_id = 'generated-docs'::text) AND (public.is_saas_admin() OR (((storage.foldername(name))[1])::uuid IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))))));


--
-- Name: objects generated-docs: tenant member read; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "generated-docs: tenant member read" ON storage.objects FOR SELECT TO authenticated USING (((bucket_id = 'generated-docs'::text) AND (public.is_saas_admin() OR (((storage.foldername(name))[1])::uuid IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text)))))));


--
-- Name: objects generated-docs: tenant member upload; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "generated-docs: tenant member upload" ON storage.objects FOR INSERT TO authenticated WITH CHECK (((bucket_id = 'generated-docs'::text) AND (((storage.foldername(name))[1])::uuid IN ( SELECT tm.tenant_id
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text))))));


--
-- Name: iceberg_namespaces; Type: ROW SECURITY; Schema: storage; Owner: -
--

ALTER TABLE storage.iceberg_namespaces ENABLE ROW LEVEL SECURITY;

--
-- Name: iceberg_tables; Type: ROW SECURITY; Schema: storage; Owner: -
--

ALTER TABLE storage.iceberg_tables ENABLE ROW LEVEL SECURITY;

--
-- Name: objects intake_read_owner_only; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY intake_read_owner_only ON storage.objects FOR SELECT TO authenticated USING (((bucket_id = 'avatars'::text) AND ((storage.foldername(name))[1] = 'intakes'::text)));


--
-- Name: migrations; Type: ROW SECURITY; Schema: storage; Owner: -
--

ALTER TABLE storage.migrations ENABLE ROW LEVEL SECURITY;

--
-- Name: objects; Type: ROW SECURITY; Schema: storage; Owner: -
--

ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;

--
-- Name: objects public_read; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY public_read ON storage.objects FOR SELECT USING ((bucket_id = 'saas-docs'::text));


--
-- Name: s3_multipart_uploads; Type: ROW SECURITY; Schema: storage; Owner: -
--

ALTER TABLE storage.s3_multipart_uploads ENABLE ROW LEVEL SECURITY;

--
-- Name: s3_multipart_uploads_parts; Type: ROW SECURITY; Schema: storage; Owner: -
--

ALTER TABLE storage.s3_multipart_uploads_parts ENABLE ROW LEVEL SECURITY;

--
-- Name: objects saas_admin_delete; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY saas_admin_delete ON storage.objects FOR DELETE TO authenticated USING (((bucket_id = 'saas-docs'::text) AND (EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid())))));


--
-- Name: objects saas_admin_upload; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY saas_admin_upload ON storage.objects FOR INSERT TO authenticated WITH CHECK (((bucket_id = 'saas-docs'::text) AND (EXISTS ( SELECT 1
   FROM public.saas_admins
  WHERE (saas_admins.user_id = auth.uid())))));


--
-- Name: vector_indexes; Type: ROW SECURITY; Schema: storage; Owner: -
--

ALTER TABLE storage.vector_indexes ENABLE ROW LEVEL SECURITY;

--
-- Name: objects whatsapp-media: delete saas admin; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "whatsapp-media: delete saas admin" ON storage.objects FOR DELETE TO authenticated USING (((bucket_id = 'whatsapp-media'::text) AND public.is_saas_admin()));


--
-- Name: objects whatsapp-media: read tenant members; Type: POLICY; Schema: storage; Owner: -
--

CREATE POLICY "whatsapp-media: read tenant members" ON storage.objects FOR SELECT TO authenticated USING (((bucket_id = 'whatsapp-media'::text) AND (public.is_saas_admin() OR (EXISTS ( SELECT 1
   FROM public.tenant_members tm
  WHERE ((tm.user_id = auth.uid()) AND (tm.status = 'active'::text) AND ((storage.foldername(objects.name))[1] = (tm.tenant_id)::text)))))));


--
-- Name: supabase_realtime; Type: PUBLICATION; Schema: -; Owner: -
--

CREATE PUBLICATION supabase_realtime WITH (publish = 'insert, update, delete, truncate');


--
-- Name: supabase_realtime_messages_publication; Type: PUBLICATION; Schema: -; Owner: -
--

CREATE PUBLICATION supabase_realtime_messages_publication WITH (publish = 'insert, update, delete, truncate');


--
-- Name: supabase_realtime conversation_messages; Type: PUBLICATION TABLE; Schema: public; Owner: -
--

ALTER PUBLICATION supabase_realtime ADD TABLE ONLY public.conversation_messages;


--
-- Name: supabase_realtime notifications; Type: PUBLICATION TABLE; Schema: public; Owner: -
--

ALTER PUBLICATION supabase_realtime ADD TABLE ONLY public.notifications;


--
-- Name: supabase_realtime_messages_publication messages; Type: PUBLICATION TABLE; Schema: realtime; Owner: -
--

ALTER PUBLICATION supabase_realtime_messages_publication ADD TABLE ONLY realtime.messages;


--
-- Name: issue_graphql_placeholder; Type: EVENT TRIGGER; Schema: -; Owner: -
--

CREATE EVENT TRIGGER issue_graphql_placeholder ON sql_drop
         WHEN TAG IN ('DROP EXTENSION')
   EXECUTE FUNCTION extensions.set_graphql_placeholder();


--
-- Name: issue_pg_cron_access; Type: EVENT TRIGGER; Schema: -; Owner: -
--

CREATE EVENT TRIGGER issue_pg_cron_access ON ddl_command_end
         WHEN TAG IN ('CREATE EXTENSION')
   EXECUTE FUNCTION extensions.grant_pg_cron_access();


--
-- Name: issue_pg_graphql_access; Type: EVENT TRIGGER; Schema: -; Owner: -
--

CREATE EVENT TRIGGER issue_pg_graphql_access ON ddl_command_end
         WHEN TAG IN ('CREATE FUNCTION')
   EXECUTE FUNCTION extensions.grant_pg_graphql_access();


--
-- Name: issue_pg_net_access; Type: EVENT TRIGGER; Schema: -; Owner: -
--

CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end
         WHEN TAG IN ('CREATE EXTENSION')
   EXECUTE FUNCTION extensions.grant_pg_net_access();


--
-- Name: pgrst_ddl_watch; Type: EVENT TRIGGER; Schema: -; Owner: -
--

CREATE EVENT TRIGGER pgrst_ddl_watch ON ddl_command_end
   EXECUTE FUNCTION extensions.pgrst_ddl_watch();


--
-- Name: pgrst_drop_watch; Type: EVENT TRIGGER; Schema: -; Owner: -
--

CREATE EVENT TRIGGER pgrst_drop_watch ON sql_drop
   EXECUTE FUNCTION extensions.pgrst_drop_watch();


--
-- PostgreSQL database dump complete
--

\unrestrict OYZjCTq2gLqELdyCChgzk1NjEGA2C8OKTRGUJKsllGft2WtbPKKeGe6fjZh3Yzj

