304 lines
8.6 KiB
PL/PgSQL
304 lines
8.6 KiB
PL/PgSQL
-- ============================================================
|
|
-- 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-<AÑO>-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;
|