Files
motor-de-encuestas/supabase/migrations/20260605093948_dashboard_rpc.sql
2026-06-05 07:18:10 -03:00

625 lines
25 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ============================================================
-- 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 #>> '{}' = ''
);
-- 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 #>> '{}' = ''
)
) 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 #>> '{}' = '';
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 #>> '{}' = '';
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 #>> '{}' = '';
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 #>> '{}' = ''
);
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;
$$;