-- ============================================================================
-- GLM HUB · VERSIÓN FINAL
-- LED DE DISPONIBILIDAD + REGISTRO CENTRAL DE ACCESOS + JEFE INMEDIATO
-- GomezLee Marketing
-- Fecha: 2026-08-14
--
-- OBJETIVO
-- 1) Mantener a Isaac + José Leopoldo + Máximo como aprobadores base.
-- 2) Agregar al jefe inmediato desde empleados_glm.supervisor_email.
-- 3) Crear UNA tabla central que el Hub usa únicamente para saber si debe mostrar
--    "Disponible" o "Sin acceso".
-- 4) NO modifica el login ni los permisos reales de ninguna aplicación.
-- 5) NO modifica claves, credenciales ni configuración de EasyPanel/n8n.
--
-- TABLA CENTRAL
-- public.glm_hub_app_access
--   app_name = nombre visible de la aplicación en el Hub
--   email    = correo autorizado
--   email='*' significa que TODOS los usuarios @gomezleemarketing.com tienen acceso
--   is_active=false permite desactivar el registro sin borrarlo
--
-- OPERACIÓN FUTURA
-- - App abierta a todo GLM: agregar una fila con app_name + email='*'.
-- - App restringida: agregar una fila por usuario con app_name + su correo.
-- - Revocar: borrar la fila o cambiar is_active a false.
-- ============================================================================

begin;

-- --------------------------------------------------------------------------
-- 1. APROBADORES DINÁMICOS: 3 BASE + JEFE INMEDIATO
-- --------------------------------------------------------------------------

alter table public.glm_hub_access_request_approvers
  drop constraint if exists glm_hub_access_request_approvers_email_check;

alter table public.glm_hub_access_request_approvers
  drop constraint if exists glm_hub_access_request_approvers_email_normalized_check;

alter table public.glm_hub_access_request_approvers
  add constraint glm_hub_access_request_approvers_email_normalized_check
  check (
    approver_email = lower(btrim(approver_email))
    and approver_email ~ '^[a-z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-z0-9.-]+\.[a-z]{2,}$'
  );

create or replace function public.glm_hub_issue_access_request_tokens(p_request_id uuid)
returns jsonb
language plpgsql
volatile
security definer
set search_path = ''
as $$
declare
  v_request public.glm_hub_access_requests%rowtype;
begin
  select request.*
    into v_request
  from public.glm_hub_access_requests as request
  where request.id = p_request_id
    and request.status = 'pending'
  for update;

  if not found then
    return jsonb_build_object('ok', false, 'error', 'La solicitud no existe o ya fue atendida.');
  end if;

  with supervisor_candidate as (
    select
      coalesce(
        nullif(btrim(employee.supervisor), ''),
        nullif(btrim(supervisor_employee.name), ''),
        split_part(lower(btrim(employee.supervisor_email)), '@', 1)
      ) as approver_name,
      lower(btrim(employee.supervisor_email)) as approver_email,
      40 as priority
    from public.empleados_glm as employee
    left join public.empleados_glm as supervisor_employee
      on lower(btrim(supervisor_employee.work_email)) = lower(btrim(employee.supervisor_email))
    where lower(btrim(employee.work_email)) = v_request.requester_email
      and nullif(btrim(employee.supervisor_email), '') is not null
    order by
      case when employee.status::text = 'Active' then 0 else 1 end,
      employee.id
    limit 1
  ),
  candidate_approvers as (
    select 'Isaac Aracena'::text as approver_name,
           'iaracena@gomezleemarketing.com'::text as approver_email,
           10 as priority
    union all
    select 'José Leopoldo Gómez', 'jgomez@gomezleemarketing.com', 20
    union all
    select 'Máximo Gómez', 'mgomez@gomezleemarketing.com', 30
    union all
    select approver_name, approver_email, priority
    from supervisor_candidate
  ),
  deduplicated as (
    select distinct on (approver_email)
      approver_name,
      approver_email,
      priority
    from candidate_approvers
    where approver_email is not null
      and approver_email <> ''
      and approver_email <> v_request.requester_email
      and approver_email ~ '^[a-z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-z0-9.-]+\.[a-z]{2,}$'
    order by approver_email, priority
  ),
  tokenized as (
    select
      approver_name,
      approver_email,
      priority,
      encode(extensions.gen_random_bytes(32), 'hex') as decision_token
    from deduplicated
  )
  insert into public.glm_hub_access_request_approvers (
    request_id,
    approver_name,
    approver_email,
    decision_token,
    decision_token_hash
  )
  select
    p_request_id,
    tokenized.approver_name,
    tokenized.approver_email,
    tokenized.decision_token,
    encode(extensions.digest(tokenized.decision_token, 'sha256'), 'hex')
  from tokenized
  on conflict (request_id, approver_email) do nothing;

  return jsonb_build_object(
    'ok', true,
    'requestId', v_request.id,
    'requesterName', v_request.requester_name,
    'requesterEmail', v_request.requester_email,
    'appName', v_request.app_name,
    'appCategory', v_request.app_category,
    'approvers', (
      select coalesce(
        jsonb_agg(
          jsonb_build_object(
            'name', approver.approver_name,
            'email', approver.approver_email,
            'token', approver.decision_token
          )
          order by
            case approver.approver_email
              when 'iaracena@gomezleemarketing.com' then 1
              when 'jgomez@gomezleemarketing.com' then 2
              when 'mgomez@gomezleemarketing.com' then 3
              else 4
            end,
            approver.approver_email
        ),
        '[]'::jsonb
      )
      from public.glm_hub_access_request_approvers as approver
      where approver.request_id = p_request_id
    )
  );
end;
$$;

alter function public.glm_hub_issue_access_request_tokens(uuid) owner to postgres;
revoke all on function public.glm_hub_issue_access_request_tokens(uuid) from public, anon, authenticated;
grant execute on function public.glm_hub_issue_access_request_tokens(uuid) to service_role;

-- --------------------------------------------------------------------------
-- 2. TABLA CENTRAL DE ACCESOS DEL HUB
-- --------------------------------------------------------------------------
-- Esta tabla es INFORMATIVA para el Hub. No sustituye el login/seguridad real
-- de cada aplicación.
--
-- app_id es rellenado automáticamente a partir de app_name, de forma que desde
-- el Table Editor basta con escribir el nombre de la app y el correo.
-- --------------------------------------------------------------------------

create table if not exists public.glm_hub_app_access (
  id uuid primary key default extensions.gen_random_uuid(),
  app_name text not null,
  email text not null,
  is_active boolean not null default true,
  app_id uuid references public.glm_hub_apps(id) on delete cascade,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  constraint glm_hub_app_access_email_check
    check (
      email = '*'
      or email ~ '^[a-z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-z0-9.-]+\.[a-z]{2,}$'
    )
);

comment on table public.glm_hub_app_access is
  'Registro central informativo de accesos mostrado por GLM Hub. No reemplaza los permisos reales de cada aplicación.';
comment on column public.glm_hub_app_access.app_name is
  'Nombre de la aplicación tal como aparece en glm_hub_apps.';
comment on column public.glm_hub_app_access.email is
  'Correo autorizado. El valor * significa todos los usuarios GLM autenticados.';
comment on column public.glm_hub_app_access.is_active is
  'TRUE = acceso mostrado como Disponible; FALSE = registro desactivado.';
comment on column public.glm_hub_app_access.app_id is
  'Se completa automáticamente usando app_name.';

create unique index if not exists glm_hub_app_access_app_email_uidx
  on public.glm_hub_app_access (app_id, email);

create index if not exists glm_hub_app_access_email_idx
  on public.glm_hub_app_access (email)
  where is_active = true;

-- Normaliza correos y permite trabajar por nombre de aplicación desde Table Editor.
create or replace function public.glm_hub_app_access_prepare_row()
returns trigger
language plpgsql
security definer
set search_path = ''
as $$
declare
  v_app_id uuid;
  v_app_name text;
begin
  new.app_name := btrim(coalesce(new.app_name, ''));
  new.email := lower(btrim(coalesce(new.email, '')));

  if new.app_name = '' then
    raise exception 'Debes indicar app_name.';
  end if;

  if new.email = '' then
    raise exception 'Debes indicar email. Usa * si la aplicación está disponible para todo GLM.';
  end if;

  -- Si el usuario cambió app_name manualmente, ese nombre manda y se resuelve
  -- nuevamente el UUID. En INSERT también permite dejar app_id vacío.
  if tg_op = 'INSERT' then
    select app.id, app.name
      into v_app_id, v_app_name
    from public.glm_hub_apps as app
    where lower(btrim(app.name)) = lower(new.app_name)
    order by app.created_at
    limit 1;

    if v_app_id is null then
      raise exception 'No existe una aplicación en GLM Hub llamada "%".', new.app_name;
    end if;

    new.app_id := v_app_id;
    new.app_name := v_app_name;
  elsif new.app_id is null or new.app_name is distinct from old.app_name then
    select app.id, app.name
      into v_app_id, v_app_name
    from public.glm_hub_apps as app
    where lower(btrim(app.name)) = lower(new.app_name)
    order by app.created_at
    limit 1;

    if v_app_id is null then
      raise exception 'No existe una aplicación en GLM Hub llamada "%".', new.app_name;
    end if;

    new.app_id := v_app_id;
    new.app_name := v_app_name;
  else
    select app.name
      into v_app_name
    from public.glm_hub_apps as app
    where app.id = new.app_id;

    if v_app_name is null then
      raise exception 'El app_id indicado no existe en glm_hub_apps.';
    end if;

    new.app_name := v_app_name;
  end if;

  new.updated_at := now();
  return new;
end;
$$;

drop trigger if exists glm_hub_app_access_prepare_row_trigger
  on public.glm_hub_app_access;

create trigger glm_hub_app_access_prepare_row_trigger
before insert or update on public.glm_hub_app_access
for each row
execute function public.glm_hub_app_access_prepare_row();

-- Si un administrador cambia el nombre de una app en el Hub, el nombre visible
-- de los registros centrales se mantiene sincronizado por app_id.
create or replace function public.glm_hub_sync_access_app_name()
returns trigger
language plpgsql
security definer
set search_path = ''
as $$
begin
  if new.name is distinct from old.name then
    update public.glm_hub_app_access
       set app_name = new.name,
           updated_at = now()
     where app_id = new.id;
  end if;
  return new;
end;
$$;

drop trigger if exists glm_hub_sync_access_app_name_trigger
  on public.glm_hub_apps;

create trigger glm_hub_sync_access_app_name_trigger
after update of name on public.glm_hub_apps
for each row
execute function public.glm_hub_sync_access_app_name();

-- Nadie necesita leer esta tabla directamente desde el navegador. El frontend
-- consulta únicamente el RPC seguro glm_hub_get_my_app_access().
alter table public.glm_hub_app_access enable row level security;
revoke all on table public.glm_hub_app_access from anon, authenticated;
grant all on table public.glm_hub_app_access to service_role;

-- --------------------------------------------------------------------------
-- 3. CARGA INICIAL DE LOS ACCESOS ACTUALES
-- --------------------------------------------------------------------------
-- A) Apps abiertas para cualquier usuario GLM -> una sola fila con email='*'.
-- --------------------------------------------------------------------------

insert into public.glm_hub_app_access (app_id, app_name, email, is_active)
select app.id, app.name, '*', true
from public.glm_hub_apps as app
where lower(btrim(app.name)) in (
  'bamboohr',
  'cdc brief',
  'cruce de seguridad social - honduras',
  'cruce de seguridad social - guatemala',
  'cruce de seguridad social - costa rica',
  'cruce de seguridad social - república dominicana',
  'cruce de seguridad social - el salvador',
  'validación de ir - nicaragua',
  'glm id card generator'
)
on conflict (app_id, email)
do update set
  app_name = excluded.app_name,
  is_active = true,
  updated_at = now();

-- B) CDC Project Management -> snapshot inicial desde tablero_cdc_allowed_users.
insert into public.glm_hub_app_access (app_id, app_name, email, is_active)
select
  app.id,
  app.name,
  lower(btrim(allowed_user.email)),
  true
from public.glm_hub_apps as app
cross join public.tablero_cdc_allowed_users as allowed_user
where lower(btrim(app.name)) = 'cdc project management'
  and nullif(btrim(allowed_user.email), '') is not null
  and coalesce(allowed_user.is_active, false) = true
on conflict (app_id, email)
do update set
  app_name = excluded.app_name,
  is_active = true,
  updated_at = now();

-- C) Portal de Verificación de Nóminas -> snapshot inicial desde la tabla actual.
insert into public.glm_hub_app_access (app_id, app_name, email, is_active)
select
  app.id,
  app.name,
  lower(btrim(allowed_user.email)),
  true
from public.glm_hub_apps as app
cross join public.cruce_cuentas_usuarios_autorizados as allowed_user
where lower(btrim(app.name)) = 'portal de verificación de nóminas'
  and nullif(btrim(allowed_user.email), '') is not null
on conflict (app_id, email)
do update set
  app_name = excluded.app_name,
  is_active = true,
  updated_at = now();

-- D) Seguimiento de Impuestos GLM -> snapshot inicial desde tax_calendar_access.
insert into public.glm_hub_app_access (app_id, app_name, email, is_active)
select
  app.id,
  app.name,
  lower(btrim(allowed_user.email)),
  true
from public.glm_hub_apps as app
cross join public.tax_calendar_access as allowed_user
where lower(btrim(app.name)) = 'seguimiento de impuestos glm'
  and nullif(btrim(allowed_user.email), '') is not null
  and coalesce(allowed_user.active, false) = true
on conflict (app_id, email)
do update set
  app_name = excluded.app_name,
  is_active = true,
  updated_at = now();

-- --------------------------------------------------------------------------
-- 4. RPC FINAL PARA EL LED DEL HUB
-- --------------------------------------------------------------------------
-- El Hub ya NO consulta las tablas particulares de cada aplicación.
-- Solo lee este registro central.
-- --------------------------------------------------------------------------

create or replace function public.glm_hub_get_my_app_access()
returns table (
  app_id uuid,
  app_name text,
  has_access boolean
)
language plpgsql
stable
security definer
set search_path = ''
as $$
declare
  v_email text;
begin
  v_email := lower(btrim(coalesce(auth.jwt() ->> 'email', '')));

  if auth.uid() is null or v_email = '' then
    return;
  end if;

  -- El GLM Hub continúa reservado al dominio corporativo principal.
  if v_email !~ '^[a-z0-9.!#$%&''*+/=?^_`{|}~-]+@gomezleemarketing\.com$' then
    return;
  end if;

  return query
  select
    app.id,
    app.name,
    exists (
      select 1
      from public.glm_hub_app_access as access_row
      where access_row.app_id = app.id
        and access_row.is_active = true
        and (
          access_row.email = '*'
          or access_row.email = v_email
        )
    ) as has_access
  from public.glm_hub_apps as app
  where app.visibility = 'published'
  order by app.name;
end;
$$;

alter function public.glm_hub_get_my_app_access() owner to postgres;
revoke all on function public.glm_hub_get_my_app_access() from public, anon;
grant execute on function public.glm_hub_get_my_app_access() to authenticated;
grant execute on function public.glm_hub_get_my_app_access() to service_role;

commit;

-- ============================================================================
-- VERIFICACIONES OPCIONALES DESPUÉS DE EJECUTAR
-- ============================================================================

-- Ver toda la matriz central:
-- select app_name, email, is_active
-- from public.glm_hub_app_access
-- order by app_name, email;

-- Agregar acceso individual a una futura app restringida DESDE SQL:
-- insert into public.glm_hub_app_access (app_name, email)
-- values ('Nombre exacto de la aplicación', 'usuario@gomezleemarketing.com');
--
-- En el Table Editor puedes hacer exactamente lo mismo dejando app_id vacío.

-- Marcar una futura app como abierta para todo GLM:
-- insert into public.glm_hub_app_access (app_name, email)
-- values ('Nombre exacto de la aplicación', '*');

-- Revocar sin borrar historial:
-- update public.glm_hub_app_access
-- set is_active = false
-- where lower(app_name) = lower('Nombre exacto de la aplicación')
--   and email = 'usuario@gomezleemarketing.com';
