refactor completo + truncate de base de datos + reingenieria dashboard
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
-- Migration 011: Truncar datos de testing
|
||||
-- Ejecutado directamente el 2026-06-05 antes del push (tablas en 0 al momento del push).
|
||||
-- TRUNCATE en tablas ya vacías es idempotente — este archivo solo registra el evento.
|
||||
BEGIN;
|
||||
TRUNCATE TABLE public.answers, public.responses, public.consent_records RESTART IDENTITY CASCADE;
|
||||
COMMIT;
|
||||
677
supabase/migrations/20260605140001_012_dashboard_rpc_v2.sql
Normal file
677
supabase/migrations/20260605140001_012_dashboard_rpc_v2.sql
Normal file
@@ -0,0 +1,677 @@
|
||||
-- ============================================================
|
||||
-- Migration 012: Dashboard RPCs v2 — alineación al instrumento real
|
||||
-- Correcciones respecto a migration 009:
|
||||
-- _dash_responses : departamento filtra por responses.qi_zone (no JOIN a answers)
|
||||
-- rpc_dashboard_kpis : K1 = 1.6+1.13 (sin 1.1); K2 = 5.2 en-dash; K3 = avg 4.3 raw 1–5
|
||||
-- rpc_prevalencia_por_depto : group by qi_zone
|
||||
-- rpc_perfil_poblacion : (era rpc_distribucion_cuidado) — renombrada
|
||||
-- rpc_score_organizacional : 0.7×6.1 + 0.3×6.5; 6.5='No lo sé' → NULL
|
||||
-- rpc_apoyos_demandados : (era rpc_politicas_gap) — solo 6.2, renombrada
|
||||
-- rpc_intensidad_vs_bienestar : (era rpc_intensidad_vs_productividad) — renombrada
|
||||
-- rpc_saturacion_cuidador : (era rpc_riesgo_retencion) — nueva lógica 4.3+5.1+5.2
|
||||
-- Todas mantienen: SECURITY DEFINER, search_path fijado, k-anonimato k≥5.
|
||||
-- ============================================================
|
||||
|
||||
-- Eliminar funciones renombradas
|
||||
DROP FUNCTION IF EXISTS public.rpc_distribucion_cuidado(uuid, jsonb);
|
||||
DROP FUNCTION IF EXISTS public.rpc_politicas_gap(uuid, jsonb);
|
||||
DROP FUNCTION IF EXISTS public.rpc_intensidad_vs_productividad(uuid, jsonb);
|
||||
DROP FUNCTION IF EXISTS public.rpc_riesgo_retencion(uuid, jsonb);
|
||||
|
||||
-- ── Helper: responses filtradas por cohort ────────────────────
|
||||
-- Usa qi_zone directamente (poblado desde A1 en submit-survey.ts).
|
||||
-- Evita un JOIN a answers por consulta.
|
||||
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
|
||||
AND (p_depto IS NULL OR r.qi_zone = p_depto)
|
||||
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)
|
||||
))
|
||||
AND (p_etario IS NULL OR cardinality(p_etario) = 0
|
||||
OR r.qi_age_bucket = ANY(p_etario))
|
||||
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 ──────────────────────
|
||||
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 ─────────────────────────────────────────────────────
|
||||
-- K1: cuidadores = 1.6='Sí' OR 1.13='Sí'
|
||||
-- K2: 5.2 IN ('Varias veces (4–10 veces al año)', 'Con frecuencia (más de 10 veces al año)') ← en-dash
|
||||
-- K3: promedio raw de 4.3 (rating 1–5), devuelto como decimal 1.0–5.0
|
||||
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 bigint;
|
||||
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;
|
||||
|
||||
-- K1: 1.6='Sí' OR 1.13='Sí' (cuidadores de otros)
|
||||
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.6','1.13')
|
||||
AND a.value #>> '{}' = 'Sí'
|
||||
);
|
||||
|
||||
-- K3: promedio raw 4.3 (1–5)
|
||||
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]+$';
|
||||
|
||||
-- K2: ausencias recurrentes 5.2 — EN-DASH obligatorio
|
||||
SELECT count(DISTINCT dr.response_id) INTO v_ausencias
|
||||
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)
|
||||
ELSE NULL END,
|
||||
'pct_ausencias', ROUND(v_ausencias::numeric / v_n * 100, 1)
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- ── Prevalencia por departamento ──────────────────────────────
|
||||
-- Usa qi_zone de responses (sin JOIN a answers para departamento).
|
||||
-- Cuidadores = 1.6='Sí' OR 1.13='Sí'.
|
||||
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
|
||||
r.qi_zone 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.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.responses r ON r.id = dr.response_id
|
||||
WHERE r.qi_zone IS NOT NULL
|
||||
GROUP BY r.qi_zone
|
||||
) sub
|
||||
ORDER BY depto_val
|
||||
) t;
|
||||
|
||||
RETURN jsonb_build_object('status','ok','n_total',v_total,'data', COALESCE(v_rows,'[]'::jsonb));
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- ── Perfil de la población (era rpc_distribucion_cuidado) ─────
|
||||
-- 1.1 = disc. propia, 1.6 = familiar, 1.13 = adulto mayor
|
||||
CREATE OR REPLACE FUNCTION public.rpc_perfil_poblacion(
|
||||
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;
|
||||
$$;
|
||||
|
||||
-- ── Score organizacional (Madurez) ────────────────────────────
|
||||
-- Formula: 0.7 × 6.1_norm + 0.3 × 6.5_norm
|
||||
-- 6.1: Sí=100, Parcialmente=66, No invisible=33, No nunca=0
|
||||
-- 6.5: Sí=100, Tal vez=66, No necesario=33, No lo sé=NULL (no entra al avg)
|
||||
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_6_5 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;
|
||||
|
||||
SELECT AVG(CASE
|
||||
WHEN a.value #>> '{}' = 'Sí, me siento acompañado/a' THEN 100.0
|
||||
WHEN a.value #>> '{}' = 'Parcialmente, hay cierta comprensión' THEN 66.0
|
||||
WHEN a.value #>> '{}' = 'No, es algo bastante invisible' THEN 33.0
|
||||
WHEN a.value #>> '{}' = 'No, nunca se ha hablado del tema' THEN 0.0
|
||||
ELSE NULL
|
||||
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';
|
||||
|
||||
-- 'No lo sé' → NULL: AVG ignora NULLs en PostgreSQL
|
||||
SELECT AVG(CASE
|
||||
WHEN a.value #>> '{}' = 'Sí, definitivamente' THEN 100.0
|
||||
WHEN a.value #>> '{}' = 'Tal vez, dependería del caso' THEN 66.0
|
||||
WHEN a.value #>> '{}' = 'No lo considero necesario' THEN 33.0
|
||||
WHEN a.value #>> '{}' = 'No lo sé' THEN NULL
|
||||
ELSE NULL
|
||||
END) INTO v_score_6_5
|
||||
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.5';
|
||||
|
||||
v_score_final := ROUND(
|
||||
0.7 * COALESCE(v_score_6_1, 0.0) + 0.3 * COALESCE(v_score_6_5, 0.0),
|
||||
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;
|
||||
$$;
|
||||
|
||||
-- ── Heatmap: horas de cuidado × frecuencia ausentismo (2.2 × 5.2) ──
|
||||
-- Sin cambios estructurales respecto a migration 009.
|
||||
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;
|
||||
$$;
|
||||
|
||||
-- ── Pirámide demográfica (A5 × A6) ───────────────────────────
|
||||
-- Sin cambios respecto a migration 009.
|
||||
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;
|
||||
$$;
|
||||
|
||||
-- ── Apoyos más demandados (era rpc_politicas_gap) ────────────
|
||||
-- Solo 6.2 (multi-select max 3). Top 8 por frecuencia, N≥5.
|
||||
CREATE OR REPLACE FUNCTION public.rpc_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(
|
||||
'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;
|
||||
$$;
|
||||
|
||||
-- ── Intensidad de cuidado vs. Bienestar (era rpc_intensidad_vs_productividad) ──
|
||||
-- 2.2 × 4.3 — sin cambios de lógica respecto a migration 009.
|
||||
CREATE OR REPLACE FUNCTION public.rpc_intensidad_vs_bienestar(
|
||||
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;
|
||||
$$;
|
||||
|
||||
-- ── Saturación del cuidador (era rpc_riesgo_retencion) ───────
|
||||
-- Indicador compuesto: 4.3 (bienestar) + 5.1 (rendimiento) + 5.2 (ausentismo)
|
||||
-- Alta saturación: 3 criterios. Media: 2. Baja: 0–1.
|
||||
-- Columnas: Segmento · N · % del total · Apoyo más demandado (6.2)
|
||||
CREATE OR REPLACE FUNCTION public.rpc_saturacion_cuidador(
|
||||
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_alta bigint;
|
||||
v_n_media bigint;
|
||||
v_n_baja bigint;
|
||||
v_apo_alta text;
|
||||
v_apo_media text;
|
||||
v_apo_baja 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;
|
||||
|
||||
-- Contar por segmento en una sola pasada
|
||||
SELECT
|
||||
count(*) FILTER (WHERE n_crit = 3),
|
||||
count(*) FILTER (WHERE n_crit = 2),
|
||||
count(*) FILTER (WHERE n_crit <= 1)
|
||||
INTO v_n_alta, v_n_media, v_n_baja
|
||||
FROM (
|
||||
SELECT
|
||||
dr.response_id,
|
||||
(CASE WHEN 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='4.3'
|
||||
AND a.value #>> '{}' ~ '^[0-9]+$'
|
||||
AND (a.value #>> '{}')::int <= 2
|
||||
) THEN 1 ELSE 0 END
|
||||
+ CASE WHEN 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.1'
|
||||
AND a.value #>> '{}' = 'Casi todos los días'
|
||||
) THEN 1 ELSE 0 END
|
||||
+ CASE WHEN 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)'
|
||||
)
|
||||
) THEN 1 ELSE 0 END) AS n_crit
|
||||
FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr
|
||||
) sub;
|
||||
|
||||
-- Apoyo más demandado (6.2) por segmento, solo si N≥5
|
||||
IF v_n_alta >= 5 THEN
|
||||
SELECT opt INTO v_apo_alta FROM (
|
||||
SELECT jsonb_array_elements_text(a.value) AS opt,
|
||||
count(DISTINCT dr.response_id) AS cnt
|
||||
FROM (
|
||||
SELECT response_id FROM (
|
||||
SELECT dr2.response_id,
|
||||
(CASE WHEN EXISTS (SELECT 1 FROM public.answers a2 JOIN public.questions q2 ON q2.id=a2.question_id WHERE a2.response_id=dr2.response_id AND q2.code='4.3' AND a2.value#>>'{}' ~ '^[0-9]+$' AND (a2.value#>>'{}')::int <= 2) THEN 1 ELSE 0 END
|
||||
+ CASE WHEN EXISTS (SELECT 1 FROM public.answers a3 JOIN public.questions q3 ON q3.id=a3.question_id WHERE a3.response_id=dr2.response_id AND q3.code='5.1' AND a3.value#>>'{}' = 'Casi todos los días') THEN 1 ELSE 0 END
|
||||
+ CASE WHEN EXISTS (SELECT 1 FROM public.answers a4 JOIN public.questions q4 ON q4.id=a4.question_id WHERE a4.response_id=dr2.response_id AND q4.code='5.2' AND a4.value#>>'{}' IN ('Varias veces (4–10 veces al año)','Con frecuencia (más de 10 veces al año)')) THEN 1 ELSE 0 END) AS n_crit
|
||||
FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr2
|
||||
) s WHERE n_crit = 3
|
||||
) seg
|
||||
JOIN public.answers a ON a.response_id = seg.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 opt ORDER BY cnt DESC LIMIT 1
|
||||
) t;
|
||||
END IF;
|
||||
|
||||
IF v_n_media >= 5 THEN
|
||||
SELECT opt INTO v_apo_media FROM (
|
||||
SELECT jsonb_array_elements_text(a.value) AS opt,
|
||||
count(DISTINCT dr.response_id) AS cnt
|
||||
FROM (
|
||||
SELECT response_id FROM (
|
||||
SELECT dr2.response_id,
|
||||
(CASE WHEN EXISTS (SELECT 1 FROM public.answers a2 JOIN public.questions q2 ON q2.id=a2.question_id WHERE a2.response_id=dr2.response_id AND q2.code='4.3' AND a2.value#>>'{}' ~ '^[0-9]+$' AND (a2.value#>>'{}')::int <= 2) THEN 1 ELSE 0 END
|
||||
+ CASE WHEN EXISTS (SELECT 1 FROM public.answers a3 JOIN public.questions q3 ON q3.id=a3.question_id WHERE a3.response_id=dr2.response_id AND q3.code='5.1' AND a3.value#>>'{}' = 'Casi todos los días') THEN 1 ELSE 0 END
|
||||
+ CASE WHEN EXISTS (SELECT 1 FROM public.answers a4 JOIN public.questions q4 ON q4.id=a4.question_id WHERE a4.response_id=dr2.response_id AND q4.code='5.2' AND a4.value#>>'{}' IN ('Varias veces (4–10 veces al año)','Con frecuencia (más de 10 veces al año)')) THEN 1 ELSE 0 END) AS n_crit
|
||||
FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr2
|
||||
) s WHERE n_crit = 2
|
||||
) seg
|
||||
JOIN public.answers a ON a.response_id = seg.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 opt ORDER BY cnt DESC LIMIT 1
|
||||
) t;
|
||||
END IF;
|
||||
|
||||
IF v_n_baja >= 5 THEN
|
||||
SELECT opt INTO v_apo_baja FROM (
|
||||
SELECT jsonb_array_elements_text(a.value) AS opt,
|
||||
count(DISTINCT dr.response_id) AS cnt
|
||||
FROM (
|
||||
SELECT response_id FROM (
|
||||
SELECT dr2.response_id,
|
||||
(CASE WHEN EXISTS (SELECT 1 FROM public.answers a2 JOIN public.questions q2 ON q2.id=a2.question_id WHERE a2.response_id=dr2.response_id AND q2.code='4.3' AND a2.value#>>'{}' ~ '^[0-9]+$' AND (a2.value#>>'{}')::int <= 2) THEN 1 ELSE 0 END
|
||||
+ CASE WHEN EXISTS (SELECT 1 FROM public.answers a3 JOIN public.questions q3 ON q3.id=a3.question_id WHERE a3.response_id=dr2.response_id AND q3.code='5.1' AND a3.value#>>'{}' = 'Casi todos los días') THEN 1 ELSE 0 END
|
||||
+ CASE WHEN EXISTS (SELECT 1 FROM public.answers a4 JOIN public.questions q4 ON q4.id=a4.question_id WHERE a4.response_id=dr2.response_id AND q4.code='5.2' AND a4.value#>>'{}' IN ('Varias veces (4–10 veces al año)','Con frecuencia (más de 10 veces al año)')) THEN 1 ELSE 0 END) AS n_crit
|
||||
FROM public._dash_responses(p_survey_id, v_f.f_depto, v_f.f_genero, v_f.f_etario, v_f.f_modalidad) dr2
|
||||
) s WHERE n_crit <= 1
|
||||
) seg
|
||||
JOIN public.answers a ON a.response_id = seg.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 opt ORDER BY cnt DESC LIMIT 1
|
||||
) t;
|
||||
END IF;
|
||||
|
||||
RETURN jsonb_build_object(
|
||||
'status', 'ok',
|
||||
'n_total', v_total,
|
||||
'segmentos', jsonb_build_array(
|
||||
jsonb_build_object(
|
||||
'segmento', 'Alta saturación',
|
||||
'n', CASE WHEN v_n_alta >= 5 THEN v_n_alta ELSE NULL END,
|
||||
'pct', CASE WHEN v_n_alta >= 5 THEN ROUND(v_n_alta::numeric/v_total*100,1) ELSE NULL END,
|
||||
'apoyo', CASE WHEN v_n_alta >= 5 THEN v_apo_alta ELSE NULL END,
|
||||
'suppressed', (v_n_alta < 5)
|
||||
),
|
||||
jsonb_build_object(
|
||||
'segmento', 'Saturación media',
|
||||
'n', CASE WHEN v_n_media >= 5 THEN v_n_media ELSE NULL END,
|
||||
'pct', CASE WHEN v_n_media >= 5 THEN ROUND(v_n_media::numeric/v_total*100,1) ELSE NULL END,
|
||||
'apoyo', CASE WHEN v_n_media >= 5 THEN v_apo_media ELSE NULL END,
|
||||
'suppressed', (v_n_media < 5)
|
||||
),
|
||||
jsonb_build_object(
|
||||
'segmento', 'Baja saturación',
|
||||
'n', CASE WHEN v_n_baja >= 5 THEN v_n_baja ELSE NULL END,
|
||||
'pct', CASE WHEN v_n_baja >= 5 THEN ROUND(v_n_baja::numeric/v_total*100,1) ELSE NULL END,
|
||||
'apoyo', CASE WHEN v_n_baja >= 5 THEN v_apo_baja ELSE NULL END,
|
||||
'suppressed', (v_n_baja < 5)
|
||||
)
|
||||
)
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
Reference in New Issue
Block a user