Actualiza frontend, integraciones y dist de producción

This commit is contained in:
2026-07-31 10:53:28 -04:00
parent 364b313240
commit f65a56fc3c
25 changed files with 3876 additions and 436 deletions
+448
View File
@@ -930,3 +930,451 @@ commit;
-- Prueba de recordatorios de una fecha:
-- select * from public.tax_due_reminders('2026-07-24'::date);
-- ===========================================================================
-- ACTUALIZACIÓN JULIO 2026: BambooHR, Google Calendar y fechas futuras
-- ===========================================================================
-- Seguimiento de Impuestos GLM
-- Actualización: contactos BambooHR, edición ampliada, Google Calendar y bloqueo de fechas pasadas.
-- Ejecutar una sola vez en Supabase SQL Editor antes de desplegar el frontend y activar los workflows.
begin;
-- ---------------------------------------------------------------------------
-- Contactos: metadatos de BambooHR y tercer canal de notificación
-- ---------------------------------------------------------------------------
alter table public.tax_contacts
add column if not exists department text,
add column if not exists division text,
add column if not exists location text,
add column if not exists source text not null default 'manual',
add column if not exists bamboo_employee_id text,
add column if not exists bamboo_employee_number text,
add column if not exists bamboo_status text,
add column if not exists bamboo_synced_at timestamptz,
add column if not exists calendar_enabled boolean not null default true;
alter table public.tax_contacts
drop constraint if exists tax_contacts_area_check;
alter table public.tax_contacts
add constraint tax_contacts_area_check
check (area in ('admin', 'nomina', 'regional', 'other'));
update public.tax_contacts
set calendar_enabled = false
where email is null or length(trim(email)) <= 3;
alter table public.tax_contacts
drop constraint if exists tax_contacts_calendar_email_when_enabled;
alter table public.tax_contacts
add constraint tax_contacts_calendar_email_when_enabled
check (not calendar_enabled or length(trim(coalesce(email, ''))) > 3);
create unique index if not exists tax_contacts_unique_active_bamboo_employee
on public.tax_contacts (bamboo_employee_id)
where active = true and bamboo_employee_id is not null;
create index if not exists tax_contacts_bamboo_employee_number_idx
on public.tax_contacts (bamboo_employee_number)
where bamboo_employee_number is not null;
-- Amplía la normalización existente para los nuevos campos.
create or replace function public.tax_normalize_contact()
returns trigger
language plpgsql
set search_path = ''
as $$
begin
new.full_name := trim(new.full_name);
new.email := nullif(lower(trim(coalesce(new.email, ''))), '');
new.whatsapp_number := nullif(regexp_replace(coalesce(new.whatsapp_number, ''), '[^0-9]', '', 'g'), '');
new.job_title := nullif(trim(coalesce(new.job_title, '')), '');
new.department := nullif(trim(coalesce(new.department, '')), '');
new.division := nullif(trim(coalesce(new.division, '')), '');
new.location := nullif(trim(coalesce(new.location, '')), '');
new.source := coalesce(nullif(lower(trim(coalesce(new.source, ''))), ''), 'manual');
new.bamboo_employee_id := nullif(trim(coalesce(new.bamboo_employee_id, '')), '');
new.bamboo_employee_number := nullif(trim(coalesce(new.bamboo_employee_number, '')), '');
new.bamboo_status := nullif(trim(coalesce(new.bamboo_status, '')), '');
if new.source = 'bamboohr' then
if tg_op = 'INSERT' then
new.bamboo_synced_at := now();
elsif new.bamboo_employee_id is distinct from old.bamboo_employee_id
or new.full_name is distinct from old.full_name
or new.email is distinct from old.email
or new.whatsapp_number is distinct from old.whatsapp_number then
new.bamboo_synced_at := now();
end if;
end if;
return new;
end;
$$;
-- ---------------------------------------------------------------------------
-- No permitir obligaciones nuevas o movidas a fechas pasadas
-- ---------------------------------------------------------------------------
create or replace function public.tax_validate_future_obligation()
returns trigger
language plpgsql
set search_path = ''
as $$
declare
local_today date := (now() at time zone 'America/Santo_Domingo')::date;
begin
if new.active = true
and new.is_historical = false
and new.due_date < local_today then
if tg_op = 'INSERT' then
raise exception 'No se puede crear una obligación en una fecha que ya pasó.'
using errcode = '22007';
elsif new.due_date is distinct from old.due_date then
raise exception 'No se puede mover una obligación a una fecha que ya pasó.'
using errcode = '22007';
end if;
end if;
return new;
end;
$$;
drop trigger if exists tax_validate_future_obligation_trigger on public.tax_obligations;
create trigger tax_validate_future_obligation_trigger
before insert or update on public.tax_obligations
for each row execute function public.tax_validate_future_obligation();
-- ---------------------------------------------------------------------------
-- Sincronización con Google Calendar
-- Un evento de Google Calendar por obligación, con todos los destinatarios
-- aplicables como invitados.
-- ---------------------------------------------------------------------------
create table if not exists public.tax_google_calendar_events (
obligation_id uuid primary key references public.tax_obligations(id) on delete cascade,
google_event_id text,
calendar_id text not null default 'primary',
attendees jsonb not null default '[]'::jsonb,
status text not null default 'active'
check (status in ('active', 'deleted', 'failed')),
last_action text,
provider_response jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
synced_at timestamptz
);
alter table public.tax_google_calendar_events enable row level security;
revoke all on table public.tax_google_calendar_events from anon, authenticated;
grant select, insert, update, delete on table public.tax_google_calendar_events to service_role;
create or replace function public.tax_google_calendar_payload(p_obligation_id uuid)
returns table (
obligation_id uuid,
active boolean,
due_date date,
description text,
category text,
country_id uuid,
country_name text,
country_code text,
attendees jsonb,
google_event_id text,
calendar_id text,
sync_status text
)
language sql
stable
security definer
set search_path = ''
as $$
select
obligation.id,
obligation.active,
obligation.due_date,
obligation.description,
obligation.category,
obligation.country_id,
country.name,
country.code,
coalesce(
(
select jsonb_agg(recipient.email order by recipient.email)
from (
select distinct lower(contact.email) as email
from public.tax_contacts contact
join public.tax_countries contact_country
on contact_country.id = contact.country_id
where contact.active = true
and contact.calendar_enabled = true
and contact.email is not null
and length(trim(contact.email)) > 3
and (
contact.area = 'regional'
or (
contact_country.is_regional = true
and contact.area = obligation.category
)
or (
contact.country_id = obligation.country_id
and contact.area = obligation.category
)
)
) recipient
),
'[]'::jsonb
) as attendees,
sync.google_event_id,
coalesce(sync.calendar_id, 'primary') as calendar_id,
sync.status
from public.tax_obligations obligation
join public.tax_countries country on country.id = obligation.country_id
left join public.tax_google_calendar_events sync
on sync.obligation_id = obligation.id
where obligation.id = p_obligation_id;
$$;
revoke all on function public.tax_google_calendar_payload(uuid)
from public, anon, authenticated;
grant execute on function public.tax_google_calendar_payload(uuid)
to service_role;
create or replace function public.tax_record_google_calendar_sync(
p_obligation_id uuid,
p_google_event_id text,
p_calendar_id text default 'primary',
p_attendees jsonb default '[]'::jsonb,
p_status text default 'active',
p_last_action text default null,
p_provider_response jsonb default null
)
returns void
language plpgsql
security definer
set search_path = ''
as $$
begin
insert into public.tax_google_calendar_events (
obligation_id,
google_event_id,
calendar_id,
attendees,
status,
last_action,
provider_response,
synced_at,
updated_at
)
values (
p_obligation_id,
nullif(trim(coalesce(p_google_event_id, '')), ''),
coalesce(nullif(trim(coalesce(p_calendar_id, '')), ''), 'primary'),
coalesce(p_attendees, '[]'::jsonb),
p_status,
p_last_action,
p_provider_response,
now(),
now()
)
on conflict (obligation_id)
do update set
google_event_id = coalesce(excluded.google_event_id, public.tax_google_calendar_events.google_event_id),
calendar_id = excluded.calendar_id,
attendees = excluded.attendees,
status = excluded.status,
last_action = excluded.last_action,
provider_response = excluded.provider_response,
synced_at = now(),
updated_at = now();
end;
$$;
revoke all on function public.tax_record_google_calendar_sync(uuid, text, text, jsonb, text, text, jsonb)
from public, anon, authenticated;
grant execute on function public.tax_record_google_calendar_sync(uuid, text, text, jsonb, text, text, jsonb)
to service_role;
commit;
-- Verificaciones opcionales:
-- select * from public.tax_google_calendar_payload('UUID_DE_OBLIGACION');
-- select * from public.tax_google_calendar_events order by updated_at desc;
-- Seguimiento de Impuestos GLM
-- Actualización incremental: retirar WhatsApp y habilitar Google Chat.
-- Ejecutar después de actualizacion_bamboohr_google_calendar.sql.
begin;
alter table public.tax_contacts
add column if not exists google_chat_enabled boolean not null default true;
alter table public.tax_contacts
alter column whatsapp_enabled set default false;
-- La nueva operación deja de usar WhatsApp. Se conserva la información histórica
-- para no destruir datos ni bitácoras anteriores.
update public.tax_contacts
set
whatsapp_enabled = false,
google_chat_enabled = case
when email_enabled = true and email is not null and length(trim(email)) > 3 then true
else false
end;
alter table public.tax_contacts
drop constraint if exists tax_contacts_google_chat_email_when_enabled;
alter table public.tax_contacts
add constraint tax_contacts_google_chat_email_when_enabled
check (not google_chat_enabled or length(trim(coalesce(email, ''))) > 3);
alter table public.tax_notification_log
drop constraint if exists tax_notification_log_channel_check;
alter table public.tax_notification_log
add constraint tax_notification_log_channel_check
check (channel in ('email', 'google_chat', 'whatsapp'));
-- CREATE OR REPLACE no permite cambiar las columnas retornadas, por eso se elimina
-- y se recrea la RPC usada por n8n.
drop function if exists public.tax_due_reminders(date);
create function public.tax_due_reminders(p_run_date date default current_date)
returns table (
obligation_id uuid,
contact_id uuid,
alert_date date,
alert_type text,
due_date date,
description text,
category text,
country_name text,
country_code text,
contact_name text,
contact_email text,
email_enabled boolean,
google_chat_enabled boolean
)
language sql
stable
security definer
set search_path = ''
as $$
with due as (
select
obligation.id,
obligation.due_date,
obligation.description,
obligation.category,
obligation.country_id,
country.name as country_name,
country.code as country_code,
case
when p_run_date = obligation.due_date then 'same_day'
else 'previous_wednesday'
end as alert_type
from public.tax_obligations obligation
join public.tax_countries country on country.id = obligation.country_id
where obligation.active = true
and (
p_run_date = obligation.due_date
or p_run_date = public.tax_previous_wednesday(obligation.due_date)
)
)
select
due.id as obligation_id,
contact.id as contact_id,
p_run_date as alert_date,
due.alert_type,
due.due_date,
due.description,
due.category,
due.country_name,
due.country_code,
contact.full_name as contact_name,
contact.email as contact_email,
(
contact.email_enabled
and contact.email is not null
and not exists (
select 1
from public.tax_notification_log log
where log.obligation_id = due.id
and log.contact_id = contact.id
and log.alert_date = p_run_date
and log.channel = 'email'
and log.status = 'sent'
)
) as email_enabled,
(
contact.google_chat_enabled
and contact.email is not null
and not exists (
select 1
from public.tax_notification_log log
where log.obligation_id = due.id
and log.contact_id = contact.id
and log.alert_date = p_run_date
and log.channel = 'google_chat'
and log.status = 'sent'
)
) as google_chat_enabled
from due
join public.tax_contacts contact on contact.active = true
join public.tax_countries contact_country
on contact_country.id = contact.country_id
and (
contact.area = 'regional'
or (
contact_country.is_regional = true
and contact.area = due.category
)
or (
contact.country_id = due.country_id
and contact.area = due.category
)
)
where
(
contact.email_enabled
and contact.email is not null
and not exists (
select 1
from public.tax_notification_log log
where log.obligation_id = due.id
and log.contact_id = contact.id
and log.alert_date = p_run_date
and log.channel = 'email'
and log.status = 'sent'
)
)
or
(
contact.google_chat_enabled
and contact.email is not null
and not exists (
select 1
from public.tax_notification_log log
where log.obligation_id = due.id
and log.contact_id = contact.id
and log.alert_date = p_run_date
and log.channel = 'google_chat'
and log.status = 'sent'
)
)
order by due.due_date, due.country_name, contact.full_name;
$$;
revoke all on function public.tax_due_reminders(date) from public, anon, authenticated;
grant execute on function public.tax_due_reminders(date) to service_role;
commit;
-- Verificación opcional:
-- select full_name, email_enabled, google_chat_enabled, calendar_enabled, whatsapp_enabled
-- from public.tax_contacts where active = true order by full_name;