-- ============================================================ -- Cruce de Cuentas GLM - Bono 14 Guatemala -- Script incremental / idempotente -- ============================================================ -- Objetivo: -- Habilitar y verificar que Bono 14 use la MISMA infraestructura -- de reporte único ya instalada, pero con una identidad independiente: -- -- GT--07-bono14 -- -- Ejemplo: -- GT-2026-07-bono14 -- -- No crea otra tabla de históricos y no elimina datos existentes. -- Puede ejecutarse nuevamente sin borrar reportes. -- ============================================================ begin; -- ------------------------------------------------------------ -- 1. Validación de prerrequisitos de la versión "reporte único" -- ------------------------------------------------------------ do $$ declare v_missing text[] := array[]::text[]; begin if to_regclass('public.cruces_cuentas_gt_reportes') is null then raise exception 'No existe public.cruces_cuentas_gt_reportes. Instala primero la estructura base de Cruce de Cuentas.'; end if; if not exists ( select 1 from information_schema.columns where table_schema = 'public' and table_name = 'cruces_cuentas_gt_reportes' and column_name = 'report_identity' ) then v_missing := array_append(v_missing, 'report_identity'); end if; if not exists ( select 1 from information_schema.columns where table_schema = 'public' and table_name = 'cruces_cuentas_gt_reportes' and column_name = 'updated_at' ) then v_missing := array_append(v_missing, 'updated_at'); end if; if not exists ( select 1 from information_schema.columns where table_schema = 'public' and table_name = 'cruces_cuentas_gt_reportes' and column_name = 'country' ) then v_missing := array_append(v_missing, 'country'); end if; if not exists ( select 1 from information_schema.columns where table_schema = 'public' and table_name = 'cruces_cuentas_gt_reportes' and column_name = 'year' ) then v_missing := array_append(v_missing, 'year'); end if; if not exists ( select 1 from information_schema.columns where table_schema = 'public' and table_name = 'cruces_cuentas_gt_reportes' and column_name = 'month' ) then v_missing := array_append(v_missing, 'month'); end if; if not exists ( select 1 from information_schema.columns where table_schema = 'public' and table_name = 'cruces_cuentas_gt_reportes' and column_name = 'period_type' ) then v_missing := array_append(v_missing, 'period_type'); end if; if cardinality(v_missing) > 0 then raise exception 'Faltan columnas requeridas en public.cruces_cuentas_gt_reportes: %. Ejecuta primero el script de REPORTE ÚNICO POR PERÍODO.', array_to_string(v_missing, ', '); end if; end; $$; -- ------------------------------------------------------------ -- 2. Constructor canónico de identidad -- ------------------------------------------------------------ -- Se mantiene genérico para nómina GT, nómina TT y Bono 14. create or replace function public.cruce_cuentas_build_report_identity( p_country text, p_year integer, p_month integer, p_period_type text ) returns text language sql immutable as $$ select upper(trim(coalesce(p_country, 'GT'))) || '-' || coalesce(p_year, 0)::text || '-' || lpad(coalesce(p_month, 0)::text, 2, '0') || '-' || lower(trim(coalesce(p_period_type, 'periodo'))); $$; -- ------------------------------------------------------------ -- 3. Trigger de identidad / updated_at -- ------------------------------------------------------------ -- Reinstalado de forma idempotente para garantizar que un UPSERT de -- Bono 14 mantenga la misma identidad y actualice updated_at. create or replace function public.cruce_cuentas_set_report_identity() returns trigger language plpgsql set search_path = public as $$ declare v_canonical text; begin v_canonical := public.cruce_cuentas_build_report_identity( coalesce(new.country, 'GT'), new.year, new.month, new.period_type ); if tg_op = 'INSERT' then if nullif(trim(coalesce(new.report_identity, '')), '') is null then new.report_identity := v_canonical; end if; else -- Los duplicados históricos antiguos conservan su sufijo :legacy:. if coalesce(old.report_identity, '') like '%:legacy:%' then new.report_identity := old.report_identity; else new.report_identity := v_canonical; end if; end if; new.updated_at := now(); return new; end; $$; drop trigger if exists trg_cruce_cuentas_report_identity on public.cruces_cuentas_gt_reportes; create trigger trg_cruce_cuentas_report_identity before insert or update on public.cruces_cuentas_gt_reportes for each row execute function public.cruce_cuentas_set_report_identity(); -- ------------------------------------------------------------ -- 4. Garantizar unicidad por identidad -- ------------------------------------------------------------ -- La versión de reporte único ya debe tener este índice. Se valida antes -- de recrearlo para no alterar ni eliminar históricos existentes. do $$ begin if not exists ( select 1 from pg_indexes where schemaname = 'public' and tablename = 'cruces_cuentas_gt_reportes' and indexname = 'ux_cruces_cuentas_reportes_report_identity' ) then if exists ( select report_identity from public.cruces_cuentas_gt_reportes where report_identity is not null group by report_identity having count(*) > 1 ) then raise exception 'Existen report_identity duplicados. Ejecuta primero el script de REPORTE ÚNICO POR PERÍODO para aplicar su backfill seguro antes de Bono 14.'; end if; create unique index ux_cruces_cuentas_reportes_report_identity on public.cruces_cuentas_gt_reportes (report_identity); end if; end; $$; -- Índice auxiliar específico para consultas/soporte de Bono 14. create index if not exists ix_cruces_cuentas_gt_reportes_bono14_updated on public.cruces_cuentas_gt_reportes (year, updated_at desc) where upper(coalesce(country, 'GT')) = 'GT' and lower(coalesce(period_type, '')) = 'bono14'; -- ------------------------------------------------------------ -- 5. RPC que n8n consulta ANTES de crear/reutilizar el Google Sheet -- ------------------------------------------------------------ create or replace function public.cruce_cuentas_get_reporte_periodo( p_country text, p_year integer, p_month integer, p_period_type text ) returns jsonb language plpgsql stable security definer set search_path = public as $$ declare v_identity text; v_report public.cruces_cuentas_gt_reportes%rowtype; begin v_identity := public.cruce_cuentas_build_report_identity( p_country, p_year, p_month, p_period_type ); select r.* into v_report from public.cruces_cuentas_gt_reportes r where r.report_identity = v_identity order by r.updated_at desc nulls last, r.created_at desc nulls last limit 1; if not found then return jsonb_build_object( 'ok', true, 'found', false, 'report_identity', v_identity, 'report', null ); end if; return jsonb_build_object( 'ok', true, 'found', true, 'report_identity', v_identity, 'report', jsonb_build_object( 'id', v_report.id, 'report_identity', v_report.report_identity, 'country', v_report.country, 'year', v_report.year, 'month', v_report.month, 'period_type', v_report.period_type, 'period_label', v_report.period_label, 'spreadsheet_id', v_report.spreadsheet_id, 'report_url', v_report.report_url, 'estado', v_report.estado, 'created_at', v_report.created_at, 'updated_at', v_report.updated_at ) ); end; $$; revoke all on function public.cruce_cuentas_get_reporte_periodo(text, integer, integer, text) from public, anon, authenticated; grant execute on function public.cruce_cuentas_get_reporte_periodo(text, integer, integer, text) to service_role; commit; -- ============================================================ -- VALIDACIÓN FINAL (solo lectura) -- Debe devolver: GT-2026-07-bono14 -- ============================================================ select public.cruce_cuentas_build_report_identity( 'GT', 2026, 7, 'bono14' ) as identidad_bono14_esperada; -- Si ya existe un Bono 14 2026, esta consulta devuelve found=true. -- Si todavía no se ha generado, devuelve found=false (lo esperado antes -- de la primera ejecución). select public.cruce_cuentas_get_reporte_periodo( 'GT', 2026, 7, 'bono14' ) as estado_bono14_2026;