-- ============================================================
-- Sprint 4 (v3.0.0) — Dashboard: RPCs de la Mirada Empresa
-- Fuente: docs/dashboard/SPEC_MIRADA_EMPRESA.md (contrato validado)
--
-- Patrón: idéntico al de la migración 012 (rpc_dashboard_kpis, etc.) —
-- SECURITY DEFINER, filtro survey_id + flag p_all delegado a
-- _dash_responses (sin tocar), k≥5 en dos niveles, shape {status, n, ...}.
-- No se modifican las 9 RPCs viejas de v2 (quedan como están).
--
-- Mapeo métrica de la spec → función:
--   E1  prevalencia de cuidadores      → rpc_me_prevalencia_cuidadores
--   E2  riesgo de rotación             → rpc_me_riesgo_rotacion
--   E3  ausentismo                     → rpc_me_ausentismo
--   E4  presentismo                    → rpc_me_presentismo
--   E6  punto ciego organizacional     → rpc_me_punto_ciego
--   E7  demanda de apoyos              → rpc_me_apoyos_demandados
--   ★   talento en riesgo              → rpc_me_talento_en_riesgo
--   E5  carrera congelada              → rpc_me_carrera_congelada
--   E8  intensidad de carga            → rpc_me_intensidad_carga
--   E9  segmentación nivel/modalidad   → rpc_me_segmentacion
--   E10 disposición a red de apoyo     → rpc_me_disposicion_red
-- ============================================================

-- ════════════════════════════════════════════════════════════
-- HELPER — CUIDADOR_ACTIVO (único punto de la definición, ver spec)
-- "Hijo menor sin discapacidad" SÍ cuenta como cuidador (criterio UNFPA,
-- validado por Maria, jun 2026) — ensancha la población respecto a v2.
-- Toda métrica que use "cuidador" como población llama a este helper;
-- si la definición cambia, se cambia ACÁ y nada más.
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public._dash_es_cuidador(p_response_id uuid)
RETURNS boolean
LANGUAGE sql STABLE SECURITY DEFINER
SET search_path = public
AS $$
  -- CASE (no AND) a propósito: Postgres no garantiza orden de evaluación en
  -- AND/OR, solo en CASE — esto asegura que el operador jsonb "-" (solo
  -- válido sobre arrays) nunca se ejecute sobre un valor que no lo sea.
  SELECT EXISTS (
    SELECT 1
    FROM public.answers a
    JOIN public.questions q ON q.id = a.question_id
    WHERE a.response_id = p_response_id
      AND q.code = '4.1'
      AND CASE WHEN jsonb_typeof(a.value) = 'array'
               THEN jsonb_array_length(
                      a.value - 'Ninguna de las anteriores / No tengo vinculación de cuidado'
                    ) > 0
               ELSE false
          END
  )
$$;

-- ════════════════════════════════════════════════════════════
-- [NÚCLEO] E1 — Prevalencia de cuidadores en la plantilla
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_prevalencia_cuidadores(
    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_cuid   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(*) INTO v_cuid
    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 public._dash_es_cuidador(dr.response_id);

    RETURN jsonb_build_object(
        'status', 'ok',
        'n', v_total,
        'pct_cuidadores', ROUND(v_cuid::numeric / v_total * 100, 1)
    );
END;
$$;

-- ════════════════════════════════════════════════════════════
-- [NÚCLEO] E2 — Riesgo de rotación por cuidado
-- Denominador: cuidadores que RESPONDIERON 7.6 — no "todos los cuidadores",
-- porque 7.6 vive en Sección 7, oculta para soloNino (solo "Hijo menor sin
-- discapacidad") y para "Ninguna". Si el denominador fuera "todos los
-- cuidadores" sin este matiz, se diluiría el % con gente a la que nunca se
-- le mostró la pregunta — no es lo mismo "no está en riesgo" que "no se le
-- preguntó". El JOIN a la pregunta concreta resuelve esto solo.
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_riesgo_rotacion(
    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_cuid   bigint;
    v_algo     bigint;
    v_muy      bigint;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);

    SELECT
        count(*),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Algo probable'),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Muy probable')
    INTO v_n_cuid, v_algo, v_muy
    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 AND q.code = '7.6'
    WHERE public._dash_es_cuidador(dr.response_id);

    IF v_n_cuid < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n_cuidadores',v_n_cuid);
    END IF;

    RETURN jsonb_build_object(
        'status', 'ok',
        'n_cuidadores', v_n_cuid,
        'pct_riesgo', ROUND((v_algo + v_muy)::numeric / v_n_cuid * 100, 1),
        'desglose', jsonb_build_object(
            'algo', ROUND(v_algo::numeric / v_n_cuid * 100, 1),
            'muy',  ROUND(v_muy::numeric / v_n_cuid * 100, 1)
        )
    );
END;
$$;

-- ════════════════════════════════════════════════════════════
-- [NÚCLEO] E3 — Ausentismo atribuible al cuidado
-- Mismo matiz de denominador que E2 (7.3 vive en Sección 7, oculta para
-- soloNino/Ninguna) — denominador = cuidadores que respondieron 7.3.
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_ausentismo(
    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_nunca   bigint;
    v_pocas   bigint;
    v_varias  bigint;
    v_frec    bigint;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);

    SELECT
        count(*),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Nunca'),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Pocas veces (1–3 al año)'),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Varias veces (4–10 al año)'),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Con frecuencia (más de 10 al año)')
    INTO v_n, v_nunca, v_pocas, v_varias, v_frec
    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 AND q.code = '7.3'
    WHERE public._dash_es_cuidador(dr.response_id);

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

    RETURN jsonb_build_object(
        'status', 'ok',
        'n', v_n,
        'pct_ausentismo_recurrente', ROUND((v_varias + v_frec)::numeric / v_n * 100, 1),
        'desglose', jsonb_build_object(
            'nunca',  ROUND(v_nunca::numeric  / v_n * 100, 1),
            'pocas',  ROUND(v_pocas::numeric  / v_n * 100, 1),
            'varias', ROUND(v_varias::numeric / v_n * 100, 1),
            'frecuente', ROUND(v_frec::numeric / v_n * 100, 1)
        )
    );
END;
$$;

-- ════════════════════════════════════════════════════════════
-- [NÚCLEO] E4 — Presentismo (productividad reducida)
-- Mismo matiz de denominador (7.2, Sección 7) que E2/E3.
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_presentismo(
    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_nunca   bigint;
    v_algunas bigint;
    v_varias  bigint;
    v_casi    bigint;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);

    SELECT
        count(*),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Nunca o casi nunca'),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Algunas veces al mes'),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Varias veces por semana'),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Casi todos los días')
    INTO v_n, v_nunca, v_algunas, v_varias, v_casi
    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 AND q.code = '7.2'
    WHERE public._dash_es_cuidador(dr.response_id);

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

    RETURN jsonb_build_object(
        'status', 'ok',
        'n', v_n,
        'pct_presentismo', ROUND((v_varias + v_casi)::numeric / v_n * 100, 1),
        'desglose', jsonb_build_object(
            'nunca_o_casi_nunca', ROUND(v_nunca::numeric   / v_n * 100, 1),
            'algunas_al_mes',     ROUND(v_algunas::numeric / v_n * 100, 1),
            'varias_por_semana',  ROUND(v_varias::numeric  / v_n * 100, 1),
            'casi_todos_los_dias',ROUND(v_casi::numeric    / v_n * 100, 1)
        )
    );
END;
$$;

-- ════════════════════════════════════════════════════════════
-- [NÚCLEO] E6 — Punto ciego organizacional
-- carga_real_pct: sobre TODA la plantilla encuestada (= E1).
-- apoyo_percibido_pct: sobre quienes respondieron 9.1 (oculta solo para
-- "Ninguna" — soloNino SÍ la ve, a diferencia de 7.x). Son denominadores
-- distintos a propósito (carga real de TODOS vs. percepción de quienes
-- tienen vínculo) — es justamente la brecha que la métrica busca mostrar.
-- Cada población se suprime de forma independiente si n<5.
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_punto_ciego(
    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_cuid       bigint;
    v_n_apoyo    bigint;
    v_apoyo_si   bigint;
    v_carga_pct  numeric;
    v_apoyo_pct  numeric;
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(*) INTO v_cuid
    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 public._dash_es_cuidador(dr.response_id);
    v_carga_pct := ROUND(v_cuid::numeric / v_total * 100, 1);

    SELECT
        count(*),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Sí, me siento acompañado/a')
    INTO v_n_apoyo, v_apoyo_si
    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 AND q.code = '9.1';

    IF v_n_apoyo >= 5 THEN
        v_apoyo_pct := ROUND(v_apoyo_si::numeric / v_n_apoyo * 100, 1);
    ELSE
        v_apoyo_pct := NULL;
    END IF;

    RETURN jsonb_build_object(
        'status', 'ok',
        'n', v_total,
        'carga_real_pct', v_carga_pct,
        'apoyo_percibido_pct', v_apoyo_pct,
        'apoyo_percibido_suppressed', (v_n_apoyo < 5),
        'brecha_pp', CASE WHEN v_apoyo_pct IS NOT NULL THEN ROUND(v_carga_pct - v_apoyo_pct, 1) ELSE NULL END
    );
END;
$$;

-- ════════════════════════════════════════════════════════════
-- [NÚCLEO] E7 — Demanda de apoyos priorizada
-- Denominador = TODA la plantilla encuestada (igual molde que
-- rpc_apoyos_demandados de v2: divide por v_total, no por quienes
-- respondieron 9.2). 9.2 ya está oculta para Ninguna/soloNino, así que en
-- la práctica solo cuidadores "completos" la responden — coherente con
-- cómo se comportaba 6.2 en v2.
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_apoyos_demandados(
    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 ORDER BY cnt DESC) INTO v_rows FROM (
        SELECT jsonb_build_object(
            'apoyo', 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 = '9.2' AND jsonb_typeof(a.value) = 'array'
            GROUP BY 1
        ) sub
        WHERE cnt >= 5
        ORDER BY cnt DESC
    ) t;

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

-- ════════════════════════════════════════════════════════════
-- [NÚCLEO] ★ Métrica estrella — "Talento en riesgo"
-- Umbral PARAMETRIZABLE (p_umbral_senales, default 2 de 3) — cambiarlo no
-- requiere tocar el cuerpo de la función. ADVERTENCIA: esto resuelve
-- "cambiar el número de corte"; NO resuelve "pasar a un esquema ponderado"
-- (eso sería una fórmula distinta — pesos por señal, no un conteo — y
-- necesitaría otra firma/cuerpo de función, no solo este parámetro).
--
-- Población: cuidadores que respondieron 7.6 (ancla la Sección 7 visible;
-- 7.3/7.5 se traen con LEFT JOIN porque comparten la misma regla de
-- ocultamiento que 7.6 — en la práctica siempre van juntas, pero LEFT JOIN
-- es defensivo ante una respuesta parcial real).
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_talento_en_riesgo(
    p_survey_id      uuid,
    p_filters        jsonb DEFAULT '{}'::jsonb,
    p_umbral_senales int   DEFAULT 2
)
RETURNS jsonb
LANGUAGE plpgsql STABLE SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_f       record;
    v_n       bigint;
    v_riesgo  bigint;
    v_avg     numeric;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);

    WITH base AS (
        SELECT
            dr.response_id,
            CASE WHEN s76.value #>> '{}' IN ('Algo probable','Muy probable') THEN 1 ELSE 0 END
              AS sig_riesgo,
            CASE WHEN s73.value #>> '{}' IN ('Varias veces (4–10 al año)','Con frecuencia (más de 10 al año)') THEN 1 ELSE 0 END
              AS sig_ausentismo,
            CASE WHEN jsonb_typeof(s75.value) = 'array' THEN
                   CASE WHEN jsonb_array_length(s75.value - 'Ninguna de las anteriores') > 0
                        THEN 1 ELSE 0 END
                 ELSE 0
            END AS sig_carrera
        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 s76 ON s76.response_id = dr.response_id
        JOIN public.questions q76 ON q76.id = s76.question_id AND q76.code = '7.6'
        LEFT JOIN public.questions q73 ON q73.code = '7.3' AND q73.survey_id = q76.survey_id
        LEFT JOIN public.answers s73 ON s73.response_id = dr.response_id AND s73.question_id = q73.id
        LEFT JOIN public.questions q75 ON q75.code = '7.5' AND q75.survey_id = q76.survey_id
        LEFT JOIN public.answers s75 ON s75.response_id = dr.response_id AND s75.question_id = q75.id
        WHERE public._dash_es_cuidador(dr.response_id)
    )
    SELECT
        count(*),
        count(*) FILTER (WHERE (sig_riesgo + sig_ausentismo + sig_carrera) >= p_umbral_senales),
        avg(sig_riesgo + sig_ausentismo + sig_carrera)
    INTO v_n, v_riesgo, v_avg
    FROM base;

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

    RETURN jsonb_build_object(
        'status', 'ok',
        'n_cuidadores', v_n,
        'umbral_senales', p_umbral_senales,
        'pct_talento_riesgo', ROUND(v_riesgo::numeric / v_n * 100, 1),
        'criterios_promedio', ROUND(v_avg, 2)
    );
END;
$$;

-- ════════════════════════════════════════════════════════════
-- [SEC] E5 — Carrera congelada
-- Mismo matiz de denominador (7.5, Sección 7) que E2/E3/E4.
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_carrera_congelada(
    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_cong  bigint;
    v_rows  jsonb;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);

    SELECT
        count(*),
        count(*) FILTER (
            WHERE CASE WHEN jsonb_typeof(a.value) = 'array'
                       THEN jsonb_array_length(a.value - 'Ninguna de las anteriores') > 0
                       ELSE false
                  END
        )
    INTO v_n, v_cong
    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 AND q.code = '7.5'
    WHERE public._dash_es_cuidador(dr.response_id);

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

    SELECT jsonb_agg(row ORDER BY cnt DESC) INTO v_rows FROM (
        SELECT jsonb_build_object('opcion', opt, 'n', cnt, 'pct', ROUND(cnt::numeric / v_n * 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 AND q.code = '7.5'
            WHERE public._dash_es_cuidador(dr.response_id)
              AND jsonb_typeof(a.value) = 'array'
              AND a.value #>> '{}' != 'Ninguna de las anteriores'
            GROUP BY 1
        ) sub
        WHERE cnt >= 5
        ORDER BY cnt DESC
    ) t;

    RETURN jsonb_build_object(
        'status', 'ok',
        'n', v_n,
        'pct_carrera_congelada', ROUND(v_cong::numeric / v_n * 100, 1),
        'top_opciones', COALESCE(v_rows, '[]'::jsonb)
    );
END;
$$;

-- ════════════════════════════════════════════════════════════
-- [SEC] E8 — Intensidad de carga
-- Población: cuidadores que respondieron 6.1 (ancla Sección 6, misma regla
-- de ocultamiento que 6.2/6.4 — comparten r_hide_ning_sn).
-- Son rangos categóricos (no números) — se reporta distribución por
-- combinación, no un promedio, tal como pide la spec.
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_intensidad_carga(
    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_guardia bigint;
    v_rows    jsonb;
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) dr
    JOIN public.answers a ON a.response_id = dr.response_id
    JOIN public.questions q ON q.id = a.question_id AND q.code = '6.1'
    WHERE public._dash_es_cuidador(dr.response_id);

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

    SELECT jsonb_agg(row) INTO v_rows FROM (
        SELECT jsonb_build_object(
            'dias_semana', dias_bin,
            'horas_dia', horas_bin,
            'n', CASE WHEN cnt >= 5 THEN cnt ELSE NULL END,
            'suppressed', (cnt < 5)
        ) AS row
        FROM (
            SELECT
                ad.value #>> '{}' AS dias_bin,
                ah.value #>> '{}' AS horas_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 ad ON ad.response_id = dr.response_id
            JOIN public.questions qd ON qd.id = ad.question_id AND qd.code = '6.1'
            JOIN public.answers ah ON ah.response_id = dr.response_id
            JOIN public.questions qh ON qh.id = ah.question_id AND qh.code = '6.2'
            WHERE public._dash_es_cuidador(dr.response_id)
            GROUP BY 1, 2
        ) sub
        ORDER BY dias_bin, horas_bin
    ) t;

    SELECT count(*) INTO v_guardia
    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 AND q.code = '6.4'
    WHERE public._dash_es_cuidador(dr.response_id)
      AND a.value #>> '{}' = 'De forma permanente / sin horario';

    RETURN jsonb_build_object(
        'status', 'ok',
        'n', v_n,
        'distribucion_intensidad', COALESCE(v_rows, '[]'::jsonb),
        'pct_guardia_permanente', ROUND(v_guardia::numeric / v_n * 100, 1)
    );
END;
$$;

-- ════════════════════════════════════════════════════════════
-- [SEC] E9 — Segmentación por nivel y modalidad
-- pct_cuidadores cruzado con 2.6 (nivel organizacional) y 2.7 (modalidad).
-- k≥5 por fila — segmento con n<5 se omite (no se devuelve null disfrazado
-- de 0%, se OMITE la fila, igual criterio que rpc_prevalencia_por_depto).
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_segmentacion(
    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_por_nivel  jsonb;
    v_por_modal  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;

    -- Nivel organizacional (2.6) ya se promueve a responses.qi_sector en el
    -- submit (Etapa 4G) — se lee directo de ahí, mismo patrón que
    -- rpc_prevalencia_por_depto (v2) lee qi_zone. NO se lee de answers/2.6:
    -- esa fue la causa del bug original (la RPC buscaba la respuesta cruda
    -- en vez de la columna ya denormalizada).
    SELECT jsonb_agg(row) INTO v_por_nivel FROM (
        SELECT jsonb_build_object(
            'nivel', nivel_val,
            'n', cnt,
            'pct', ROUND(cnt_cuid::numeric / cnt * 100, 1)
        ) AS row
        FROM (
            SELECT
                r.qi_sector AS nivel_val,
                count(DISTINCT dr.response_id) AS cnt,
                count(DISTINCT dr.response_id) FILTER (WHERE public._dash_es_cuidador(dr.response_id)) AS cnt_cuid
            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.responses r ON r.id = dr.response_id
            WHERE r.qi_sector IS NOT NULL
            GROUP BY 1
        ) sub
        WHERE cnt >= 5
        ORDER BY nivel_val
    ) t;

    -- Modalidad (2.7) NO tiene columna qi_modalidad — decisión consciente
    -- para no reabrir rpc_submit_response, ya verificada en 4G. Se lee por
    -- JOIN a answers/questions en tiempo de consulta. Ver TECH-DEBT-010.
    SELECT jsonb_agg(row) INTO v_por_modal FROM (
        SELECT jsonb_build_object(
            'modalidad', modal_val,
            'n', cnt,
            'pct', ROUND(cnt_cuid::numeric / cnt * 100, 1)
        ) AS row
        FROM (
            SELECT
                a.value #>> '{}' AS modal_val,
                count(DISTINCT dr.response_id) AS cnt,
                count(DISTINCT dr.response_id) FILTER (WHERE public._dash_es_cuidador(dr.response_id)) AS cnt_cuid
            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 AND q.code = '2.7'
            GROUP BY 1
        ) sub
        WHERE cnt >= 5
        ORDER BY modal_val
    ) t;

    RETURN jsonb_build_object(
        'status', 'ok',
        'n', v_total,
        'por_nivel', COALESCE(v_por_nivel, '[]'::jsonb),
        'por_modalidad', COALESCE(v_por_modal, '[]'::jsonb)
    );
END;
$$;

-- ════════════════════════════════════════════════════════════
-- [SEC] E10 — Disposición a red de apoyo interna
-- 9.3 (oculta para Ninguna Y soloNino) y 9.4 (oculta solo para Ninguna)
-- tienen reglas de ocultamiento DISTINTAS → poblaciones distintas →
-- cada porcentaje se calcula y se suprime de forma independiente,
-- mismo criterio que E6.
-- ════════════════════════════════════════════════════════════
CREATE OR REPLACE FUNCTION public.rpc_me_disposicion_red(
    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_grupo    bigint;
    v_grupo_si   bigint;
    v_n_vol      bigint;
    v_vol_si     bigint;
    v_pct_grupo  numeric;
    v_pct_vol    numeric;
BEGIN
    SELECT * INTO v_f FROM public._dash_parse_filters(p_filters);

    SELECT
        count(*),
        count(*) FILTER (WHERE a.value #>> '{}' IN ('Sí, definitivamente','Tal vez, si hay más personas en la misma situación'))
    INTO v_n_grupo, v_grupo_si
    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 AND q.code = '9.3';

    SELECT
        count(*),
        count(*) FILTER (WHERE a.value #>> '{}' = 'Sí')
    INTO v_n_vol, v_vol_si
    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 AND q.code = '9.4';

    IF v_n_grupo < 5 AND v_n_vol < 5 THEN
        RETURN jsonb_build_object('status','suppressed','min_n',5,'n_grupo_apoyo',v_n_grupo,'n_voluntariado',v_n_vol);
    END IF;

    v_pct_grupo := CASE WHEN v_n_grupo >= 5 THEN ROUND(v_grupo_si::numeric / v_n_grupo * 100, 1) ELSE NULL END;
    v_pct_vol   := CASE WHEN v_n_vol   >= 5 THEN ROUND(v_vol_si::numeric   / v_n_vol   * 100, 1) ELSE NULL END;

    RETURN jsonb_build_object(
        'status', 'ok',
        'n_grupo_apoyo', v_n_grupo,
        'pct_grupo_apoyo', v_pct_grupo,
        'n_voluntariado', v_n_vol,
        'pct_voluntariado', v_pct_vol
    );
END;
$$;
