-- ============================================================
-- Migration 009: Dashboard RPC functions
-- Todas son SECURITY DEFINER + search_path fijado.
-- Aplican k-anonimato k>=5 ANTES de devolver.
-- NUNCA tocan question codes 3.4 (salario) ni 7.1 (texto libre).
-- El cliente NUNCA accede a `answers` directamente — solo via estas funciones.
-- ============================================================

-- ── Helper: responses filtradas por cohort ────────────────────
-- Devuelve IDs de respuestas que cumplen los filtros opcionales.
-- Filtros pasados como text arrays o NULL (sin filtro).
CREATE OR REPLACE FUNCTION public._dash_responses(
    p_survey_id uuid,
    p_depto     text    DEFAULT NULL,
    p_genero    text[]  DEFAULT NULL,
    p_etario    text[]  DEFAULT NULL,
    p_modalidad text[]  DEFAULT NULL
)
RETURNS TABLE(response_id uuid)
LANGUAGE sql STABLE SECURITY DEFINER
SET search_path = public
AS $$
  SELECT r.id
  FROM public.responses r
  WHERE r.survey_id = p_survey_id
    -- Departamento: A1
    AND (p_depto IS NULL OR EXISTS (
        SELECT 1 FROM public.answers a
        JOIN public.questions q ON q.id = a.question_id
        WHERE a.response_id = r.id AND q.code = 'A1'
          AND a.value #>> '{}' = p_depto
    ))
    -- Género: A6
    AND (p_genero IS NULL OR cardinality(p_genero) = 0 OR EXISTS (
        SELECT 1 FROM public.answers a
        JOIN public.questions q ON q.id = a.question_id
        WHERE a.response_id = r.id AND q.code = 'A6'
          AND a.value #>> '{}' = ANY(p_genero)
    ))
    -- Rango etario: qi_age_bucket en responses
    AND (p_etario IS NULL OR cardinality(p_etario) = 0
         OR r.qi_age_bucket = ANY(p_etario))
    -- Modalidad: A7
    AND (p_modalidad IS NULL OR cardinality(p_modalidad) = 0 OR EXISTS (
        SELECT 1 FROM public.answers a
        JOIN public.questions q ON q.id = a.question_id
        WHERE a.response_id = r.id AND q.code = 'A7'
          AND a.value #>> '{}' = ANY(p_modalidad)
    ));
$$;

-- ── Helper: parse filters JSONB → arrays ──────────────────────
-- Convierte el objeto de filtros recibido del cliente en arrays tipados.
-- Devuelve NULL para filtros no presentes o vacíos.
CREATE OR REPLACE FUNCTION public._dash_parse_filters(p_filters jsonb)
RETURNS TABLE(
    f_depto     text,
    f_genero    text[],
    f_etario    text[],
    f_modalidad text[]
)
LANGUAGE sql IMMUTABLE SECURITY DEFINER
SET search_path = public
AS $$
  SELECT
    NULLIF(p_filters->>'depto',''),
    ARRAY(SELECT jsonb_array_elements_text(p_filters->'genero'))::text[],
    ARRAY(SELECT jsonb_array_elements_text(p_filters->'etario'))::text[],
    ARRAY(SELECT jsonb_array_elements_text(p_filters->'modalidad'))::text[];
$$;

-- ── KPIs ─────────────────────────────────────────────────────
CREATE OR REPLACE FUNCTION public.rpc_dashboard_kpis(
    p_survey_id uuid,
    p_filters   jsonb DEFAULT '{}'::jsonb
)
RETURNS jsonb
LANGUAGE plpgsql STABLE SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_f         record;
    v_n         bigint;
    v_cuidadores bigint;
    v_bienestar_avg numeric;
    v_bienestar_n   bigint;
    v_ausencias_pct numeric;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);

    SELECT count(*) INTO v_n
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad);

    IF v_n < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n',v_n);
    END IF;

    -- % cuidadores: algún trigger = 'Sí'
    SELECT count(DISTINCT dr.response_id) INTO v_cuidadores
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    WHERE EXISTS (
        SELECT 1 FROM public.answers a
        JOIN public.questions q ON q.id = a.question_id
        WHERE a.response_id = dr.response_id
          AND q.code IN ('1.1','1.6','1.13')
          AND a.value #>> '{}' = 'Sí'
    );

    -- Score bienestar: media de 4.3 (rating 1-5), normalizado a 0-100
    SELECT avg((a.value #>> '{}')::numeric), count(*)
      INTO v_bienestar_avg, v_bienestar_n
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    JOIN public.answers a ON a.response_id = dr.response_id
    JOIN public.questions q ON q.id = a.question_id
    WHERE q.code = '4.3'
      AND a.value #>> '{}' ~ '^[0-9]+$';

    -- % ausencias frecuentes: 5.2 = frecuentes (Varias veces o Con frecuencia)
    SELECT count(DISTINCT dr.response_id) INTO v_ausencias_pct
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    JOIN public.answers a ON a.response_id = dr.response_id
    JOIN public.questions q ON q.id = a.question_id
    WHERE q.code = '5.2'
      AND a.value #>> '{}' IN ('Varias veces (4-10 veces al año)','Con frecuencia (más de 10 veces al año)');

    RETURN jsonb_build_object(
        'status',            'ok',
        'n',                 v_n,
        'pct_cuidadores',    ROUND(v_cuidadores::numeric / v_n * 100, 1),
        'score_bienestar',   CASE WHEN v_bienestar_n > 0
                               THEN ROUND((v_bienestar_avg - 1) / 4 * 100, 1)
                               ELSE NULL END,
        'pct_ausencias',     ROUND(v_ausencias_pct::numeric / v_n * 100, 1)
    );
END;
$$;

-- ── Prevalencia por departamento ──────────────────────────────
CREATE OR REPLACE FUNCTION public.rpc_prevalencia_por_depto(
    p_survey_id uuid,
    p_filters   jsonb DEFAULT '{}'::jsonb
)
RETURNS jsonb
LANGUAGE plpgsql STABLE SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_f     record;
    v_total bigint;
    v_rows  jsonb;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);

    SELECT count(*) INTO v_total
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad);

    IF v_total < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n',v_total);
    END IF;

    SELECT jsonb_agg(row) INTO v_rows FROM (
        SELECT jsonb_build_object(
            'depto',         depto_val,
            'n',             CASE WHEN n_depto >= 5 THEN n_depto ELSE NULL END,
            'pct_cuidadores',CASE WHEN n_depto >= 5
                               THEN ROUND(n_cuid::numeric / n_depto * 100, 1)
                               ELSE NULL END,
            'suppressed',    (n_depto < 5)
        ) AS row
        FROM (
            SELECT
                a.value #>> '{}' AS depto_val,
                count(DISTINCT dr.response_id) AS n_depto,
                count(DISTINCT dr.response_id) FILTER (
                    WHERE EXISTS (
                        SELECT 1 FROM public.answers a2
                        JOIN public.questions q2 ON q2.id = a2.question_id
                        WHERE a2.response_id = dr.response_id
                          AND q2.code IN ('1.1','1.6','1.13')
                          AND a2.value #>> '{}' = 'Sí'
                    )
                ) AS n_cuid
            FROM public._dash_responses(p_survey_id, NULL, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
            JOIN public.answers a ON a.response_id = dr.response_id
            JOIN public.questions q ON q.id = a.question_id
            WHERE q.code = 'A1'
            GROUP BY a.value #>> '{}'
        ) sub
        ORDER BY depto_val
    ) t;

    RETURN jsonb_build_object('status','ok','n_total',v_total,'data', COALESCE(v_rows,'[]'::jsonb));
END;
$$;

-- ── Heatmap: horas de cuidado × ausencias ────────────────────
CREATE OR REPLACE FUNCTION public.rpc_heatmap_intensidad(
    p_survey_id uuid,
    p_filters   jsonb DEFAULT '{}'::jsonb
)
RETURNS jsonb
LANGUAGE plpgsql STABLE SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_f     record;
    v_total bigint;
    v_rows  jsonb;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);
    SELECT count(*) INTO v_total
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad);
    IF v_total < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n',v_total);
    END IF;

    SELECT jsonb_agg(row) INTO v_rows FROM (
        SELECT jsonb_build_object(
            'horas',       horas_bin,
            'ausencias',   aus_bin,
            'n',           CASE WHEN cnt >= 5 THEN cnt ELSE NULL END,
            'suppressed',  (cnt < 5)
        ) AS row
        FROM (
            SELECT
                ah.value #>> '{}' AS horas_bin,
                aa.value #>> '{}' AS aus_bin,
                count(DISTINCT dr.response_id) AS cnt
            FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
            JOIN public.answers ah ON ah.response_id = dr.response_id
            JOIN public.questions qh ON qh.id = ah.question_id AND qh.code = '2.2'
            JOIN public.answers aa ON aa.response_id = dr.response_id
            JOIN public.questions qa ON qa.id = aa.question_id AND qa.code = '5.2'
            GROUP BY 1, 2
        ) sub
        ORDER BY horas_bin, aus_bin
    ) t;

    RETURN jsonb_build_object('status','ok','n_total',v_total,'data', COALESCE(v_rows,'[]'::jsonb));
END;
$$;

-- ── Distribución tipo de cuidado (donut) ─────────────────────
CREATE OR REPLACE FUNCTION public.rpc_distribucion_cuidado(
    p_survey_id uuid,
    p_filters   jsonb DEFAULT '{}'::jsonb
)
RETURNS jsonb
LANGUAGE plpgsql STABLE SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_f          record;
    v_total      bigint;
    v_disc       bigint;
    v_familiar   bigint;
    v_mayor      bigint;
    v_ninguno    bigint;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);
    SELECT count(*) INTO v_total
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad);
    IF v_total < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n',v_total);
    END IF;

    SELECT count(DISTINCT dr.response_id) INTO v_disc
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    JOIN public.answers a ON a.response_id = dr.response_id
    JOIN public.questions q ON q.id = a.question_id
    WHERE q.code = '1.1' AND a.value #>> '{}' = 'Sí';

    SELECT count(DISTINCT dr.response_id) INTO v_familiar
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    JOIN public.answers a ON a.response_id = dr.response_id
    JOIN public.questions q ON q.id = a.question_id
    WHERE q.code = '1.6' AND a.value #>> '{}' = 'Sí';

    SELECT count(DISTINCT dr.response_id) INTO v_mayor
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    JOIN public.answers a ON a.response_id = dr.response_id
    JOIN public.questions q ON q.id = a.question_id
    WHERE q.code = '1.13' AND a.value #>> '{}' = 'Sí';

    SELECT count(DISTINCT dr.response_id) INTO v_ninguno
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    WHERE NOT EXISTS (
        SELECT 1 FROM public.answers a
        JOIN public.questions q ON q.id = a.question_id
        WHERE a.response_id = dr.response_id
          AND q.code IN ('1.1','1.6','1.13')
          AND a.value #>> '{}' = 'Sí'
    );

    RETURN jsonb_build_object(
        'status',       'ok',
        'n',            v_total,
        'disc_propia',  ROUND(v_disc::numeric / v_total * 100, 1),
        'familiar',     ROUND(v_familiar::numeric / v_total * 100, 1),
        'adulto_mayor', ROUND(v_mayor::numeric / v_total * 100, 1),
        'ninguno',      ROUND(v_ninguno::numeric / v_total * 100, 1)
    );
END;
$$;

-- ── Gap de políticas (barras) ─────────────────────────────────
CREATE OR REPLACE FUNCTION public.rpc_politicas_gap(
    p_survey_id uuid,
    p_filters   jsonb DEFAULT '{}'::jsonb
)
RETURNS jsonb
LANGUAGE plpgsql STABLE SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_f     record;
    v_total bigint;
    v_rows  jsonb;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);
    SELECT count(*) INTO v_total
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad);
    IF v_total < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n',v_total);
    END IF;

    -- 6.2 = apoyos que ayudarían (necesitadas), 6.2 = ya existentes (same question, max_select=3)
    -- Nota: el instrumento no tiene una pregunta separada "políticas existentes".
    -- Widget 5 muestra TOP políticas demandadas (6.2) para priorización.
    SELECT jsonb_agg(row ORDER BY cnt DESC) INTO v_rows FROM (
        SELECT jsonb_build_object(
            'politica', opt,
            'n',        cnt,
            'pct',      ROUND(cnt::numeric / v_total * 100, 1)
        ) AS row
        FROM (
            SELECT
                jsonb_array_elements_text(a.value) AS opt,
                count(DISTINCT dr.response_id) AS cnt
            FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
            JOIN public.answers a ON a.response_id = dr.response_id
            JOIN public.questions q ON q.id = a.question_id
            WHERE q.code = '6.2'
              AND jsonb_typeof(a.value) = 'array'
            GROUP BY 1
        ) sub
        WHERE cnt >= 5
        ORDER BY cnt DESC
        LIMIT 8
    ) t;

    RETURN jsonb_build_object('status','ok','n_total',v_total,'data', COALESCE(v_rows,'[]'::jsonb));
END;
$$;

-- ── Score organizacional (gauge) ──────────────────────────────
CREATE OR REPLACE FUNCTION public.rpc_score_organizacional(
    p_survey_id uuid,
    p_filters   jsonb DEFAULT '{}'::jsonb
)
RETURNS jsonb
LANGUAGE plpgsql STABLE SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_f           record;
    v_total       bigint;
    v_score_6_1   numeric;
    v_score_4_3   numeric;
    v_score_final numeric;
    v_zona        text;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);
    SELECT count(*) INTO v_total
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad);
    IF v_total < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n',v_total);
    END IF;

    -- 6.1: Sí=100, Parcialmente=50, No=0
    SELECT AVG(CASE
        WHEN a.value #>> '{}' = 'Sí, me siento acompañado/a' THEN 100.0
        WHEN a.value #>> '{}' = 'Parcialmente, hay cierta comprensión' THEN 50.0
        ELSE 0.0
    END) INTO v_score_6_1
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    JOIN public.answers a ON a.response_id = dr.response_id
    JOIN public.questions q ON q.id = a.question_id
    WHERE q.code = '6.1';

    -- 4.3: rating 1-5 → 0-100
    SELECT AVG((a.value #>> '{}')::numeric - 1) / 4 * 100 INTO v_score_4_3
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    JOIN public.answers a ON a.response_id = dr.response_id
    JOIN public.questions q ON q.id = a.question_id
    WHERE q.code = '4.3' AND a.value #>> '{}' ~ '^[0-9]+$';

    v_score_final := ROUND(COALESCE(v_score_6_1, 0) * 0.6 + COALESCE(v_score_4_3, 0) * 0.4, 1);

    v_zona := CASE
        WHEN v_score_final < 34 THEN 'baja'
        WHEN v_score_final < 67 THEN 'desarrollo'
        ELSE 'consciente'
    END;

    RETURN jsonb_build_object('status','ok','n',v_total,'score',v_score_final,'zona',v_zona);
END;
$$;

-- ── Pirámide demográfica ──────────────────────────────────────
CREATE OR REPLACE FUNCTION public.rpc_piramide_demografica(
    p_survey_id uuid,
    p_filters   jsonb DEFAULT '{}'::jsonb
)
RETURNS jsonb
LANGUAGE plpgsql STABLE SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_f     record;
    v_total bigint;
    v_rows  jsonb;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);
    SELECT count(*) INTO v_total
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad);
    IF v_total < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n',v_total);
    END IF;

    SELECT jsonb_agg(row) INTO v_rows FROM (
        SELECT jsonb_build_object(
            'etario',     etario_val,
            'genero',     genero_val,
            'n',          CASE WHEN cnt >= 5 THEN cnt ELSE NULL END,
            'suppressed', (cnt < 5)
        ) AS row
        FROM (
            SELECT
                ae.value #>> '{}' AS etario_val,
                ag.value #>> '{}' AS genero_val,
                count(DISTINCT dr.response_id) AS cnt
            FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
            JOIN public.answers ae ON ae.response_id = dr.response_id
            JOIN public.questions qe ON qe.id = ae.question_id AND qe.code = 'A5'
            JOIN public.answers ag ON ag.response_id = dr.response_id
            JOIN public.questions qg ON qg.id = ag.question_id AND qg.code = 'A6'
            GROUP BY 1, 2
        ) sub
        ORDER BY etario_val, genero_val
    ) t;

    RETURN jsonb_build_object('status','ok','n_total',v_total,'data', COALESCE(v_rows,'[]'::jsonb));
END;
$$;

-- ── Intensidad de cuidado vs. bienestar (hexbin) ─────────────
CREATE OR REPLACE FUNCTION public.rpc_intensidad_vs_productividad(
    p_survey_id uuid,
    p_filters   jsonb DEFAULT '{}'::jsonb
)
RETURNS jsonb
LANGUAGE plpgsql STABLE SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_f     record;
    v_total bigint;
    v_rows  jsonb;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);
    SELECT count(*) INTO v_total
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad);
    IF v_total < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n',v_total);
    END IF;

    SELECT jsonb_agg(row) INTO v_rows FROM (
        SELECT jsonb_build_object(
            'horas',      horas_bin,
            'bienestar',  bien_bin,
            'n',          CASE WHEN cnt >= 5 THEN cnt ELSE NULL END,
            'suppressed', (cnt < 5)
        ) AS row
        FROM (
            SELECT
                ah.value #>> '{}' AS horas_bin,
                ab.value #>> '{}' AS bien_bin,
                count(DISTINCT dr.response_id) AS cnt
            FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
            JOIN public.answers ah ON ah.response_id = dr.response_id
            JOIN public.questions qh ON qh.id = ah.question_id AND qh.code = '2.2'
            JOIN public.answers ab ON ab.response_id = dr.response_id
            JOIN public.questions qb ON qb.id = ab.question_id AND qb.code = '4.3'
            WHERE ab.value #>> '{}' ~ '^[0-9]+$'
            GROUP BY 1, 2
        ) sub
        ORDER BY horas_bin, bien_bin
    ) t;

    RETURN jsonb_build_object('status','ok','n_total',v_total,'data', COALESCE(v_rows,'[]'::jsonb));
END;
$$;

-- ── Tabla de riesgo de retención ──────────────────────────────
-- Sin columna de departamento (vector de reidentificación en N pequeños).
CREATE OR REPLACE FUNCTION public.rpc_riesgo_retencion(
    p_survey_id uuid,
    p_filters   jsonb DEFAULT '{}'::jsonb
)
RETURNS jsonb
LANGUAGE plpgsql STABLE SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_f            record;
    v_total        bigint;
    v_n_alto       bigint;
    v_n_medio      bigint;
    v_n_bajo       bigint;
    v_pol_alto     text;
    v_pol_medio    text;
    v_pol_bajo     text;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);
    SELECT count(*) INTO v_total
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad);
    IF v_total < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n',v_total);
    END IF;

    -- Alto: ausencias frecuentes AND empresa no comprende
    SELECT count(DISTINCT dr.response_id) INTO v_n_alto
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    WHERE EXISTS (
        SELECT 1 FROM public.answers a JOIN public.questions q ON q.id = a.question_id
        WHERE a.response_id = dr.response_id AND q.code = '5.2'
          AND a.value #>> '{}' IN ('Varias veces (4-10 veces al año)','Con frecuencia (más de 10 veces al año)')
    )
    AND EXISTS (
        SELECT 1 FROM public.answers a JOIN public.questions q ON q.id = a.question_id
        WHERE a.response_id = dr.response_id AND q.code = '6.1'
          AND a.value #>> '{}' IN ('No, es algo bastante invisible','No, nunca se ha hablado del tema')
    );

    -- Medio: exactamente una de las dos condiciones (XOR en SQL estándar)
    SELECT count(DISTINCT dr.response_id) INTO v_n_medio
    FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
    WHERE (
        EXISTS (
            SELECT 1 FROM public.answers a JOIN public.questions q ON q.id = a.question_id
            WHERE a.response_id = dr.response_id AND q.code = '5.2'
              AND a.value #>> '{}' IN ('Varias veces (4-10 veces al año)','Con frecuencia (más de 10 veces al año)')
        )
        AND NOT EXISTS (
            SELECT 1 FROM public.answers a JOIN public.questions q ON q.id = a.question_id
            WHERE a.response_id = dr.response_id AND q.code = '6.1'
              AND a.value #>> '{}' IN ('No, es algo bastante invisible','No, nunca se ha hablado del tema')
        )
    ) OR (
        NOT EXISTS (
            SELECT 1 FROM public.answers a JOIN public.questions q ON q.id = a.question_id
            WHERE a.response_id = dr.response_id AND q.code = '5.2'
              AND a.value #>> '{}' IN ('Varias veces (4-10 veces al año)','Con frecuencia (más de 10 veces al año)')
        )
        AND EXISTS (
            SELECT 1 FROM public.answers a JOIN public.questions q ON q.id = a.question_id
            WHERE a.response_id = dr.response_id AND q.code = '6.1'
              AND a.value #>> '{}' IN ('No, es algo bastante invisible','No, nunca se ha hablado del tema')
        )
    );

    v_n_bajo := v_total - v_n_alto - v_n_medio;

    -- Política más demandada por segmento (solo si N>=5)
    IF v_n_alto >= 5 THEN
        SELECT jsonb_array_elements_text(a.value) INTO v_pol_alto
        FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
        JOIN public.answers a ON a.response_id = dr.response_id
        JOIN public.questions q ON q.id = a.question_id
        WHERE q.code = '6.2' AND jsonb_typeof(a.value) = 'array'
          AND EXISTS (
            SELECT 1 FROM public.answers a2 JOIN public.questions q2 ON q2.id = a2.question_id
            WHERE a2.response_id = dr.response_id AND q2.code = '5.2'
              AND a2.value #>> '{}' IN ('Varias veces (4-10 veces al año)','Con frecuencia (más de 10 veces al año)')
          )
          AND EXISTS (
            SELECT 1 FROM public.answers a3 JOIN public.questions q3 ON q3.id = a3.question_id
            WHERE a3.response_id = dr.response_id AND q3.code = '6.1'
              AND a3.value #>> '{}' IN ('No, es algo bastante invisible','No, nunca se ha hablado del tema')
          )
        GROUP BY jsonb_array_elements_text(a.value)
        ORDER BY count(*) DESC LIMIT 1;
    END IF;

    RETURN jsonb_build_object(
        'status',  'ok',
        'n_total', v_total,
        'segmentos', jsonb_build_array(
            jsonb_build_object(
                'segmento', 'Alto',
                'n',        CASE WHEN v_n_alto >= 5 THEN v_n_alto ELSE NULL END,
                'pct',      CASE WHEN v_n_alto >= 5 THEN ROUND(v_n_alto::numeric/v_total*100,1) ELSE NULL END,
                'politica', CASE WHEN v_n_alto >= 5 THEN v_pol_alto ELSE NULL END,
                'suppressed', (v_n_alto < 5)
            ),
            jsonb_build_object(
                'segmento', 'Medio',
                'n',        CASE WHEN v_n_medio >= 5 THEN v_n_medio ELSE NULL END,
                'pct',      CASE WHEN v_n_medio >= 5 THEN ROUND(v_n_medio::numeric/v_total*100,1) ELSE NULL END,
                'politica', v_pol_medio,
                'suppressed', (v_n_medio < 5)
            ),
            jsonb_build_object(
                'segmento', 'Bajo',
                'n',        CASE WHEN v_n_bajo >= 5 THEN v_n_bajo ELSE NULL END,
                'pct',      CASE WHEN v_n_bajo >= 5 THEN ROUND(v_n_bajo::numeric/v_total*100,1) ELSE NULL END,
                'politica', v_pol_bajo,
                'suppressed', (v_n_bajo < 5)
            )
        )
    );
END;
$$;
