-- ============================================================ -- Tests pgTAP: Dashboard v3 — RPCs de la Mirada Empresa -- -- Ejecutar con: supabase test db -- Requiere: Docker + supabase start + extensión pgtap -- -- Cubre: -- 1. Existencia de las 11 RPCs + el helper _dash_es_cuidador (firma correcta) -- 2. _dash_es_cuidador: las 4 combinaciones de la definición UNFPA -- (solo Ninguna / Ninguna+otro / solo "hijo sin discapacidad" / sin responder) -- 3. k≥5 en dos niveles: n=4 → suppressed, n=5 → ok con pct correcto (E1) -- 4. Fix de _dash_responses: el filtro de género ya encuentra '2.5' (no '"A6"') -- 5. Umbral parametrizable de la métrica estrella: mismo dataset, dos -- resultados distintos según p_umbral_senales (prueba que el parámetro -- realmente cambia el comportamiento, no es decorativo) -- 6. E9 (rpc_me_segmentacion) — bug real encontrado en VPS: por_nivel leía -- answers/2.6 en vez de responses.qi_sector. Prueba que ahora lee -- qi_sector, que por_modalidad sigue leyendo por JOIN a 2.7, y que el -- k≥5 por segmento sigue omitiendo filas chicas. -- -- NO escrito por mí pero sí ejecutable por el director: no hay Docker en el -- entorno donde se generó este archivo — no se corrió contra Postgres real. -- ============================================================ BEGIN; SELECT plan(28); -- ============================================================ -- SECCIÓN 1: las funciones existen con la firma esperada -- ============================================================ SELECT has_function('public', '_dash_es_cuidador', ARRAY['uuid'], '1.1 _dash_es_cuidador(uuid) existe'); SELECT has_function('public', 'rpc_me_prevalencia_cuidadores', ARRAY['uuid','jsonb'], '1.2 rpc_me_prevalencia_cuidadores existe'); SELECT has_function('public', 'rpc_me_riesgo_rotacion', ARRAY['uuid','jsonb'], '1.3 rpc_me_riesgo_rotacion existe'); SELECT has_function('public', 'rpc_me_ausentismo', ARRAY['uuid','jsonb'], '1.4 rpc_me_ausentismo existe'); SELECT has_function('public', 'rpc_me_presentismo', ARRAY['uuid','jsonb'], '1.5 rpc_me_presentismo existe'); SELECT has_function('public', 'rpc_me_punto_ciego', ARRAY['uuid','jsonb'], '1.6 rpc_me_punto_ciego existe'); SELECT has_function('public', 'rpc_me_apoyos_demandados', ARRAY['uuid','jsonb'], '1.7 rpc_me_apoyos_demandados existe'); SELECT has_function('public', 'rpc_me_talento_en_riesgo', ARRAY['uuid','jsonb','int4'], '1.8 rpc_me_talento_en_riesgo(uuid,jsonb,int) existe — confirma el parámetro de umbral'); SELECT has_function('public', 'rpc_me_carrera_congelada', ARRAY['uuid','jsonb'], '1.9 rpc_me_carrera_congelada existe'); SELECT has_function('public', 'rpc_me_intensidad_carga', ARRAY['uuid','jsonb'], '1.10 rpc_me_intensidad_carga existe'); SELECT has_function('public', 'rpc_me_segmentacion', ARRAY['uuid','jsonb'], '1.11 rpc_me_segmentacion existe'); SELECT has_function('public', 'rpc_me_disposicion_red', ARRAY['uuid','jsonb'], '1.12 rpc_me_disposicion_red existe'); -- ============================================================ -- SECCIÓN 2: fixture — 1 org, 1 survey v3, preguntas mínimas necesarias -- ============================================================ DO $$ DECLARE v_org_id uuid := '10000000-1111-0000-0000-000000000001'; v_cv_id uuid := '20000000-1111-0000-0000-000000000001'; v_survey_id uuid := '30000000-1111-0000-0000-000000000001'; BEGIN INSERT INTO public.organizations (id, name) VALUES (v_org_id, '_test_me_org'); INSERT INTO public.consent_versions (id, version, body, scopes) VALUES (v_cv_id, '_test-cv-me', 'body', '["operativo"]'::jsonb); INSERT INTO public.surveys (id, org_id, title, is_anonymous, data_sensitivity, security_class, consent_version_id) VALUES (v_survey_id, v_org_id, '_test_me_survey', true, 'sensible', 'confidencial', v_cv_id); INSERT INTO public.questions (id, survey_id, code, type, prompt, position) VALUES ('40000000-1111-0000-0000-000000000041', v_survey_id, '4.1', 'multiple_choice', 'vínculos', 41), ('40000000-1111-0000-0000-000000000072', v_survey_id, '7.2', 'single_choice', 'presentismo', 72), ('40000000-1111-0000-0000-000000000073', v_survey_id, '7.3', 'single_choice', 'ausentismo', 73), ('40000000-1111-0000-0000-000000000075', v_survey_id, '7.5', 'multiple_choice', 'carrera', 75), ('40000000-1111-0000-0000-000000000076', v_survey_id, '7.6', 'single_choice', 'riesgo rotación', 76), ('40000000-1111-0000-0000-000000000091', v_survey_id, '9.1', 'single_choice', 'conciencia', 91), ('40000000-1111-0000-0000-000000000025', v_survey_id, '2.5', 'single_choice', 'género', 25); END; $$; -- ============================================================ -- SECCIÓN 3: _dash_es_cuidador — las 4 combinaciones de la definición UNFPA -- ============================================================ DO $$ DECLARE v_survey_id uuid := '30000000-1111-0000-0000-000000000001'; v_q41 uuid := '40000000-1111-0000-0000-000000000041'; v_cr_id uuid := '50000000-1111-0000-0000-000000000001'; v_r_ninguna uuid := '60000000-1111-0000-0000-000000000001'; -- solo "Ninguna..." v_r_mixto uuid := '60000000-1111-0000-0000-000000000002'; -- "Ninguna..." + otra opción v_r_solonin uuid := '60000000-1111-0000-0000-000000000003'; -- solo "Hijo menor sin discapacidad" v_r_sinresp uuid := '60000000-1111-0000-0000-000000000004'; -- no respondió 4.1 BEGIN INSERT INTO public.consent_records (id, consent_version_id, granted_scopes) VALUES (v_cr_id, '20000000-1111-0000-0000-000000000001', '{"operativo":true}'::jsonb); INSERT INTO public.responses (id, survey_id, company_id, consent_record_id) VALUES (v_r_ninguna, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id), (v_r_mixto, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id), (v_r_solonin, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id), (v_r_sinresp, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id); INSERT INTO public.answers (response_id, question_id, instance_id, value) VALUES (v_r_ninguna, v_q41, 'default', '["Ninguna de las anteriores / No tengo vinculación de cuidado"]'::jsonb), (v_r_mixto, v_q41, 'default', '["Ninguna de las anteriores / No tengo vinculación de cuidado","Otro / Especificar"]'::jsonb), (v_r_solonin, v_q41, 'default', '["Hijo menor sin discapacidad"]'::jsonb); -- v_r_sinresp: sin fila en answers para 4.1 (a propósito) END; $$; SELECT is(public._dash_es_cuidador('60000000-1111-0000-0000-000000000001'), false, '3.1 solo "Ninguna..." → NO es cuidador'); SELECT is(public._dash_es_cuidador('60000000-1111-0000-0000-000000000002'), true, '3.2 "Ninguna..." + otra opción → SÍ es cuidador (la otra opción cuenta)'); SELECT is(public._dash_es_cuidador('60000000-1111-0000-0000-000000000003'), true, '3.3 solo "Hijo menor sin discapacidad" → SÍ es cuidador (criterio UNFPA)'); SELECT is(public._dash_es_cuidador('60000000-1111-0000-0000-000000000004'), false, '3.4 sin respuesta a 4.1 → NO es cuidador'); -- ============================================================ -- SECCIÓN 4: k≥5 en rpc_me_prevalencia_cuidadores (E1) -- 4 cuidadores → suppressed; al agregar 1 más (5) → ok, pct correcto -- ============================================================ SELECT is( (public.rpc_me_prevalencia_cuidadores('30000000-1111-0000-0000-000000000001'::uuid)->>'status'), 'suppressed', '4.1 con 4 responses (n<5) → status=suppressed' ); DO $$ DECLARE v_survey_id uuid := '30000000-1111-0000-0000-000000000001'; v_q41 uuid := '40000000-1111-0000-0000-000000000041'; v_cr_id uuid := '50000000-1111-0000-0000-000000000001'; v_r5 uuid := '60000000-1111-0000-0000-000000000005'; -- 5ta response, NO cuidador BEGIN INSERT INTO public.responses (id, survey_id, company_id, consent_record_id) VALUES (v_r5, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id); INSERT INTO public.answers (response_id, question_id, instance_id, value) VALUES (v_r5, v_q41, 'default', '["Ninguna de las anteriores / No tengo vinculación de cuidado"]'::jsonb); END; $$; -- Ahora hay 5 responses: 3 cuidadores (mixto, solonin) + ... v_r_sinresp NO es -- cuidador (sin 4.1), v_r_ninguna NO es cuidador, v_r5 NO es cuidador. -- Cuidadores reales = {v_r_mixto, v_r_solonin} = 2 de 5 → 40.0% SELECT is( (public.rpc_me_prevalencia_cuidadores('30000000-1111-0000-0000-000000000001'::uuid)->>'status'), 'ok', '4.2 con 5 responses (n>=5) → status=ok' ); SELECT is( ((public.rpc_me_prevalencia_cuidadores('30000000-1111-0000-0000-000000000001'::uuid)->>'pct_cuidadores')::numeric), 40.0, '4.3 pct_cuidadores = 2/5 = 40.0%' ); -- ============================================================ -- SECCIÓN 5: fix de _dash_responses — filtro de género contra 2.5 (no A6) -- ============================================================ DO $$ DECLARE v_survey_id uuid := '30000000-1111-0000-0000-000000000001'; v_q25 uuid := '40000000-1111-0000-0000-000000000025'; v_cr_id uuid := '50000000-1111-0000-0000-000000000001'; v_rg uuid := '60000000-1111-0000-0000-000000000006'; BEGIN INSERT INTO public.responses (id, survey_id, company_id, consent_record_id) VALUES (v_rg, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id); INSERT INTO public.answers (response_id, question_id, instance_id, value) VALUES (v_rg, v_q25, 'default', '"Femenino"'::jsonb); END; $$; SELECT is( (SELECT count(*)::int FROM public._dash_responses( '30000000-1111-0000-0000-000000000001'::uuid, NULL, ARRAY['Femenino'], NULL, NULL ) WHERE response_id = '60000000-1111-0000-0000-000000000006'), 1, '5.1 filtro p_genero=[Femenino] encuentra la response con 2.5=Femenino (post-fix)' ); SELECT is( (SELECT count(*)::int FROM public._dash_responses( '30000000-1111-0000-0000-000000000001'::uuid, NULL, ARRAY['Masculino'], NULL, NULL ) WHERE response_id = '60000000-1111-0000-0000-000000000006'), 0, '5.2 filtro p_genero=[Masculino] NO encuentra esa response (control negativo)' ); -- ============================================================ -- SECCIÓN 6: umbral parametrizable — métrica estrella -- 1 cuidador con exactamente 1 de 3 señales (riesgo de rotación, vía 7.6) -- ============================================================ DO $$ DECLARE v_survey_id uuid := '30000000-1111-0000-0000-000000000001'; v_q41 uuid := '40000000-1111-0000-0000-000000000041'; v_q76 uuid := '40000000-1111-0000-0000-000000000076'; v_q73 uuid := '40000000-1111-0000-0000-000000000073'; v_q75 uuid := '40000000-1111-0000-0000-000000000075'; v_cr_id uuid := '50000000-1111-0000-0000-000000000001'; v_r1 uuid := '60000000-2222-0000-0000-000000000001'; v_r2 uuid := '60000000-2222-0000-0000-000000000002'; v_r3 uuid := '60000000-2222-0000-0000-000000000003'; v_r4 uuid := '60000000-2222-0000-0000-000000000004'; v_r5 uuid := '60000000-2222-0000-0000-000000000005'; BEGIN INSERT INTO public.responses (id, survey_id, company_id, consent_record_id) VALUES (v_r1, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id), (v_r2, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id), (v_r3, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id), (v_r4, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id), (v_r5, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id); -- Las 5 son cuidadoras (4.1 con un vínculo real) y respondieron 7.6 -- (ancla de la población), pero NINGUNA cumple 2+ señales — cada una -- cumple EXACTAMENTE 1 (riesgo de rotación), 0 en ausentismo/carrera. INSERT INTO public.answers (response_id, question_id, instance_id, value) SELECT r, v_q41, 'default', '["Otro / Especificar"]'::jsonb FROM unnest(ARRAY[v_r1,v_r2,v_r3,v_r4,v_r5]) AS r; INSERT INTO public.answers (response_id, question_id, instance_id, value) SELECT r, v_q76, 'default', '"Muy probable"'::jsonb FROM unnest(ARRAY[v_r1,v_r2,v_r3,v_r4,v_r5]) AS r; INSERT INTO public.answers (response_id, question_id, instance_id, value) SELECT r, v_q73, 'default', '"Nunca"'::jsonb FROM unnest(ARRAY[v_r1,v_r2,v_r3,v_r4,v_r5]) AS r; INSERT INTO public.answers (response_id, question_id, instance_id, value) SELECT r, v_q75, 'default', '["Ninguna de las anteriores"]'::jsonb FROM unnest(ARRAY[v_r1,v_r2,v_r3,v_r4,v_r5]) AS r; END; $$; -- Con el umbral default (2 de 3): nadie llega → 0% SELECT is( ((public.rpc_me_talento_en_riesgo('30000000-1111-0000-0000-000000000001'::uuid)->>'pct_talento_riesgo')::numeric), 0.0, '6.1 umbral default (2 de 3): 0% — nadie acumula 2 señales' ); -- Bajando el umbral a 1 de 3: las 5 cumplen (todas tienen la señal de riesgo) → 100% SELECT is( ((public.rpc_me_talento_en_riesgo( '30000000-1111-0000-0000-000000000001'::uuid, '{}'::jsonb, 1 )->>'pct_talento_riesgo')::numeric), 100.0, '6.2 umbral=1 (parámetro explícito): 100% — el parámetro SÍ cambia el resultado' ); SELECT is( ((public.rpc_me_talento_en_riesgo('30000000-1111-0000-0000-000000000001'::uuid)->>'criterios_promedio')::numeric), 1.00, '6.3 criterios_promedio = 1.00 (cada cuidadora cumple exactamente 1 de 3 señales)' ); -- ============================================================ -- SECCIÓN 7: E9 (rpc_me_segmentacion) — por_nivel via qi_sector, -- por_modalidad via JOIN a 2.7, k≥5 por segmento -- ============================================================ DO $$ DECLARE v_survey_id uuid := '30000000-1111-0000-0000-000000000001'; v_q27 uuid := '40000000-1111-0000-0000-000000000027'; v_cr_id uuid := '50000000-1111-0000-0000-000000000001'; v_r uuid; i int; BEGIN INSERT INTO public.questions (id, survey_id, code, type, prompt, position) VALUES (v_q27, v_survey_id, '2.7', 'single_choice', 'modalidad', 27); -- 6 responses "Operativo"/"Presencial" (n=6 >= 5 → visible) FOR i IN 1..6 LOOP v_r := ('60000000-3333-0000-0000-' || lpad(i::text, 12, '0'))::uuid; INSERT INTO public.responses (id, survey_id, company_id, consent_record_id, qi_sector) VALUES (v_r, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id, 'Operativo'); INSERT INTO public.answers (response_id, question_id, instance_id, value) VALUES (v_r, v_q27, 'default', '"Presencial"'::jsonb); END LOOP; -- 3 responses "Gerencial"/"Remoto (home office)" (n=3 < 5 → omitido) FOR i IN 1..3 LOOP v_r := ('60000000-4444-0000-0000-' || lpad(i::text, 12, '0'))::uuid; INSERT INTO public.responses (id, survey_id, company_id, consent_record_id, qi_sector) VALUES (v_r, v_survey_id, '10000000-1111-0000-0000-000000000001', v_cr_id, 'Gerencial'); INSERT INTO public.answers (response_id, question_id, instance_id, value) VALUES (v_r, v_q27, 'default', '"Remoto (home office)"'::jsonb); END LOOP; END; $$; SELECT is( jsonb_path_query_first( public.rpc_me_segmentacion('30000000-1111-0000-0000-000000000001'::uuid), '$.por_nivel[*] ? (@.nivel == "Operativo")' )->>'n', '6', '7.1 por_nivel lee qi_sector — "Operativo" aparece con n=6 (post-fix)' ); SELECT is( jsonb_path_query_first( public.rpc_me_segmentacion('30000000-1111-0000-0000-000000000001'::uuid), '$.por_nivel[*] ? (@.nivel == "Gerencial")' ), NULL::jsonb, '7.2 "Gerencial" (n=3 < 5) está omitido de por_nivel — k-anonimato por segmento' ); SELECT is( jsonb_path_query_first( public.rpc_me_segmentacion('30000000-1111-0000-0000-000000000001'::uuid), '$.por_modalidad[*] ? (@.modalidad == "Presencial")' )->>'n', '6', '7.3 por_modalidad lee 2.7 por JOIN a answers — "Presencial" aparece con n=6' ); SELECT is( jsonb_path_query_first( public.rpc_me_segmentacion('30000000-1111-0000-0000-000000000001'::uuid), '$.por_modalidad[*] ? (@.modalidad == "Remoto (home office)")' ), NULL::jsonb, '7.4 "Remoto (home office)" (n=3 < 5) está omitido de por_modalidad' ); SELECT * FROM finish(); ROLLBACK;