Agregar Bono 14 Guatemala y actualizar dist

This commit is contained in:
2026-08-12 09:13:40 -04:00
parent 65ab888c00
commit 6652877319
33 changed files with 3983 additions and 4833 deletions
@@ -0,0 +1,407 @@
-- Cruce de Cuentas GLM
-- Equivalencias validadas para falsos positivos de "Banco sin BambooHR"
-- Ejecutar en el SQL Editor del Supabase empresarial.
-- Script idempotente: puede ejecutarse nuevamente sin borrar datos existentes.
begin;
create extension if not exists pgcrypto;
create or replace function public.cruce_cuentas_normalizar_nombre(p_value text)
returns text
language sql
immutable
strict
as $$
select trim(
regexp_replace(
translate(
lower(coalesce(p_value, '')),
'áàäâãåéèëêíìïîóòöôõúùüûñçÁÀÄÂÃÅÉÈËÊÍÌÏÎÓÒÖÔÕÚÙÜÛÑÇ',
'aaaaaaeeeeiiiiooooouuuuncAAAAAAEEEEIIIIOOOOOUUUUNC'
),
'[^a-z0-9]+',
' ',
'g'
)
);
$$;
create table if not exists public.cruce_cuentas_bamboo_aliases (
id uuid primary key default gen_random_uuid(),
pais text not null check (pais in ('GT', 'TT')),
nombre_origen text not null,
nombre_origen_normalizado text not null,
cuenta_bancaria text not null default '',
bamboo_employee_number text,
bamboo_employee_id text,
bamboo_nombre text not null,
motivo text,
comentario text,
activo boolean not null default true,
validado_por_email text not null,
validado_por_nombre text,
last_reported_at timestamptz not null default now(),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
-- Compatibilidad si una versión anterior del script creó la tabla sin cuenta.
alter table public.cruce_cuentas_bamboo_aliases
add column if not exists cuenta_bancaria text not null default '';
drop index if exists public.ux_cruce_cuentas_bamboo_aliases_pais_nombre;
create unique index if not exists ux_cruce_cuentas_bamboo_aliases_pais_nombre_cuenta
on public.cruce_cuentas_bamboo_aliases (pais, nombre_origen_normalizado, cuenta_bancaria);
create index if not exists ix_cruce_cuentas_bamboo_aliases_employee_number
on public.cruce_cuentas_bamboo_aliases (pais, bamboo_employee_number)
where activo = true and bamboo_employee_number is not null;
create table if not exists public.cruce_cuentas_bamboo_correcciones (
id uuid primary key default gen_random_uuid(),
pais text not null check (pais in ('GT', 'TT')),
nombre_origen_principal text not null,
nombres_origen text[] not null default array[]::text[],
cuenta_bancaria text,
monto_banco numeric(18,2),
moneda text,
bamboo_employee_number text,
bamboo_employee_id text,
bamboo_nombre text not null,
motivo text not null,
comentario text,
execution_id text,
report_url text,
period_label text,
period_start date,
period_end date,
solicitado_por_email text not null,
solicitado_por_nombre text,
destinatarios_rrhh text[] not null default array[]::text[],
alias_ids uuid[] not null default array[]::uuid[],
estado text not null default 'pendiente_notificacion'
check (estado in ('pendiente_notificacion', 'notificado_rrhh', 'corregido_bamboo', 'cancelado')),
correo_enviado boolean not null default false,
correo_enviado_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists ix_cruce_cuentas_bamboo_correcciones_pais_created
on public.cruce_cuentas_bamboo_correcciones (pais, created_at desc);
create index if not exists ix_cruce_cuentas_bamboo_correcciones_estado
on public.cruce_cuentas_bamboo_correcciones (estado, created_at desc);
create or replace function public.cruce_cuentas_bamboo_set_normalized()
returns trigger
language plpgsql
set search_path = public
as $$
begin
new.pais := upper(trim(new.pais));
new.nombre_origen := trim(new.nombre_origen);
new.nombre_origen_normalizado := public.cruce_cuentas_normalizar_nombre(new.nombre_origen);
new.cuenta_bancaria := regexp_replace(coalesce(new.cuenta_bancaria, ''), '[^0-9A-Za-z]+', '', 'g');
new.validado_por_email := lower(trim(new.validado_por_email));
new.updated_at := now();
return new;
end;
$$;
drop trigger if exists trg_cruce_cuentas_bamboo_alias_normalized
on public.cruce_cuentas_bamboo_aliases;
create trigger trg_cruce_cuentas_bamboo_alias_normalized
before insert or update on public.cruce_cuentas_bamboo_aliases
for each row execute function public.cruce_cuentas_bamboo_set_normalized();
create or replace function public.cruce_cuentas_bamboo_touch_updated_at()
returns trigger
language plpgsql
set search_path = public
as $$
begin
new.updated_at := now();
return new;
end;
$$;
drop trigger if exists trg_cruce_cuentas_bamboo_correcciones_updated_at
on public.cruce_cuentas_bamboo_correcciones;
create trigger trg_cruce_cuentas_bamboo_correcciones_updated_at
before update on public.cruce_cuentas_bamboo_correcciones
for each row execute function public.cruce_cuentas_bamboo_touch_updated_at();
-- RPC consumida únicamente por los workflows generadores de reportes.
-- Siempre devuelve un objeto con aliases=[], incluso cuando no existen registros.
create or replace function public.cruce_cuentas_bamboo_aliases_activos(p_pais text)
returns jsonb
language sql
stable
security definer
set search_path = public
as $$
select jsonb_build_object(
'ok', true,
'pais', upper(trim(coalesce(p_pais, ''))),
'aliases', coalesce(
jsonb_agg(
jsonb_build_object(
'id', a.id,
'pais', a.pais,
'nombre_origen', a.nombre_origen,
'nombre_origen_normalizado', a.nombre_origen_normalizado,
'cuenta_bancaria', a.cuenta_bancaria,
'bamboo_employee_number', a.bamboo_employee_number,
'bamboo_employee_id', a.bamboo_employee_id,
'bamboo_nombre', a.bamboo_nombre,
'motivo', a.motivo,
'activo', a.activo
)
order by a.updated_at desc
) filter (where a.id is not null),
'[]'::jsonb
)
)
from public.cruce_cuentas_bamboo_aliases a
where a.activo = true
and a.pais = upper(trim(coalesce(p_pais, '')))
and upper(trim(coalesce(p_pais, ''))) in ('GT', 'TT');
$$;
alter table public.cruce_cuentas_bamboo_aliases enable row level security;
alter table public.cruce_cuentas_bamboo_correcciones enable row level security;
-- El navegador nunca escribe ni lee estas tablas directamente.
-- Toda operación pasa por n8n con service_role.
revoke all on table public.cruce_cuentas_bamboo_aliases from anon, authenticated;
revoke all on table public.cruce_cuentas_bamboo_correcciones from anon, authenticated;
grant all on table public.cruce_cuentas_bamboo_aliases to service_role;
grant all on table public.cruce_cuentas_bamboo_correcciones to service_role;
revoke all on function public.cruce_cuentas_bamboo_aliases_activos(text) from public, anon, authenticated;
grant execute on function public.cruce_cuentas_bamboo_aliases_activos(text) to service_role;
-- ============================================================
-- HISTÓRICOS: conservar y recuperar Banco sin Bamboo por reporte
-- ============================================================
-- Los workflows ya envían el detalle de cada caso. Esta columna hace que el
-- detalle quede persistido y pueda abrirse días después desde Históricos.
alter table public.cruces_cuentas_gt_reportes
add column if not exists detalle_banco_sin_bamboo jsonb not null default '[]'::jsonb;
-- Identificador generado por el navegador para trazabilidad de una ejecución.
-- No se usa como llave única porque no se reintenta automáticamente un POST
-- de conciliación (evita duplicar reportes, correos o Sheets).
alter table public.cruces_cuentas_gt_reportes
add column if not exists client_request_id text;
update public.cruces_cuentas_gt_reportes
set detalle_banco_sin_bamboo = '[]'::jsonb
where detalle_banco_sin_bamboo is null;
create index if not exists ix_cruces_cuentas_reportes_client_request
on public.cruces_cuentas_gt_reportes (client_request_id)
where client_request_id is not null;
create index if not exists ix_cruce_cuentas_bamboo_correcciones_execution
on public.cruce_cuentas_bamboo_correcciones (execution_id, created_at desc)
where execution_id is not null;
-- Reemplaza la RPC histórica manteniendo exactamente la misma firma y
-- estructura pública, pero excluye detalle_banco_sin_bamboo de la lista para
-- no descargar todos los casos cada vez que se abre Históricos. El detalle se
-- solicita únicamente cuando el usuario pulsa Banco sin Bamboo.
create or replace function public.cruce_cuentas_get_historicos(
p_country text,
p_page integer default 1,
p_page_size integer default 10,
p_search text default null,
p_status text default null
)
returns jsonb
language plpgsql
security definer
set search_path = public
as $$
declare
v_country text := upper(trim(coalesce(p_country, ''));
v_page integer := greatest(coalesce(p_page, 1), 1);
v_page_size integer := least(greatest(coalesce(p_page_size, 10), 1), 100);
v_offset integer;
v_search text := nullif(trim(coalesce(p_search, '')), '');
v_status text := nullif(trim(coalesce(p_status, '')), '');
v_total bigint;
v_filtered_total bigint;
v_reports jsonb;
begin
if v_country not in ('GT', 'TT') then
raise exception 'País no permitido: %', v_country;
end if;
v_offset := (v_page - 1) * v_page_size;
select count(*)
into v_total
from public.cruces_cuentas_gt_reportes r
where upper(coalesce(r.country, 'GT')) = v_country;
select count(*)
into v_filtered_total
from public.cruces_cuentas_gt_reportes r
where upper(coalesce(r.country, 'GT')) = v_country
and (v_status is null or r.estado = v_status)
and (
v_search is null
or coalesce(r.period_label, '') ilike '%' || v_search || '%'
or coalesce(r.payroll_file_name, '') ilike '%' || v_search || '%'
or coalesce(r.ejecutado_por_nombre, '') ilike '%' || v_search || '%'
or coalesce(r.resuelto_por_nombre, '') ilike '%' || v_search || '%'
or r.id::text ilike '%' || v_search || '%'
);
select coalesce(
jsonb_agg(
(to_jsonb(q) - 'detalle_banco_sin_bamboo')
order by q.created_at desc
),
'[]'::jsonb
)
into v_reports
from (
select r.*
from public.cruces_cuentas_gt_reportes r
where upper(coalesce(r.country, 'GT')) = v_country
and (v_status is null or r.estado = v_status)
and (
v_search is null
or coalesce(r.period_label, '') ilike '%' || v_search || '%'
or coalesce(r.payroll_file_name, '') ilike '%' || v_search || '%'
or coalesce(r.ejecutado_por_nombre, '') ilike '%' || v_search || '%'
or coalesce(r.resuelto_por_nombre, '') ilike '%' || v_search || '%'
or r.id::text ilike '%' || v_search || '%'
)
order by r.created_at desc
limit v_page_size
offset v_offset
) q;
return jsonb_build_object(
'ok', true,
'country', v_country,
'page', v_page,
'page_size', v_page_size,
'total', v_total,
'filtered_total', v_filtered_total,
'total_pages', greatest(ceil(v_filtered_total::numeric / v_page_size)::integer, 1),
'reports', v_reports
);
end;
$$;
revoke all on function public.cruce_cuentas_get_historicos(text, integer, integer, text, text) from public, anon;
grant execute on function public.cruce_cuentas_get_historicos(text, integer, integer, text, text) to authenticated;
-- RPC de detalle utilizada por Históricos al abrir un reporte específico.
-- No otorga SELECT directo sobre las tablas de aliases/correcciones.
create or replace function public.cruce_cuentas_get_banco_sin_bamboo_reporte(
p_report_id text,
p_country text
)
returns jsonb
language plpgsql
stable
security definer
set search_path = public
as $$
declare
v_country text := upper(trim(coalesce(p_country, ''));
v_report public.cruces_cuentas_gt_reportes%rowtype;
v_corrections jsonb := '[]'::jsonb;
begin
if auth.uid() is null then
raise exception 'No autenticado.';
end if;
if v_country not in ('GT', 'TT') then
raise exception 'País no permitido: %', v_country;
end if;
select r.*
into v_report
from public.cruces_cuentas_gt_reportes r
where r.id::text = trim(coalesce(p_report_id, ''))
and upper(coalesce(r.country, 'GT')) = v_country
limit 1;
if not found then
return jsonb_build_object(
'ok', false,
'message', 'No se encontró el reporte solicitado.'
);
end if;
select coalesce(
jsonb_agg(
jsonb_build_object(
'id', c.id,
'pais', c.pais,
'nombre_origen_principal', c.nombre_origen_principal,
'nombres_origen', c.nombres_origen,
'cuenta_bancaria', c.cuenta_bancaria,
'bamboo_employee_number', c.bamboo_employee_number,
'bamboo_nombre', c.bamboo_nombre,
'estado', c.estado,
'correo_enviado', c.correo_enviado,
'correo_enviado_at', c.correo_enviado_at,
'created_at', c.created_at
)
order by c.created_at desc
),
'[]'::jsonb
)
into v_corrections
from public.cruce_cuentas_bamboo_correcciones c
where c.pais = v_country
and (
c.execution_id = v_report.id::text
or (
nullif(trim(coalesce(c.execution_id, '')), '') is null
and nullif(trim(coalesce(c.report_url, '')), '') is not null
and c.report_url = v_report.report_url
)
);
return jsonb_build_object(
'ok', true,
'report', jsonb_build_object(
'id', v_report.id,
'country', v_report.country,
'period_label', v_report.period_label,
'period_start', v_report.period_start,
'period_end', v_report.period_end,
'report_url', v_report.report_url,
'banco_sin_bamboo', v_report.banco_sin_bamboo
),
'cases', coalesce(v_report.detalle_banco_sin_bamboo, '[]'::jsonb),
'corrections', v_corrections
);
end;
$$;
revoke all on function public.cruce_cuentas_get_banco_sin_bamboo_reporte(text, text) from public, anon;
grant execute on function public.cruce_cuentas_get_banco_sin_bamboo_reporte(text, text) to authenticated;
commit;
-- Validaciones opcionales después de ejecutar:
-- select public.cruce_cuentas_bamboo_aliases_activos('GT');
-- select public.cruce_cuentas_bamboo_aliases_activos('TT');
-- select column_name, data_type from information_schema.columns
-- where table_schema='public' and table_name='cruces_cuentas_gt_reportes'
-- and column_name in ('detalle_banco_sin_bamboo', 'client_request_id');