Files
Cruces-GLM/supabase/Cruces_GLM_Supabase_Completo.sql
T

978 lines
30 KiB
PL/PgSQL

-- ============================================================================
-- CRUCES GLM — SUPABASE COMPLETO
-- GomezLee Marketing
-- ============================================================================
-- Ejecutar una vez en SQL Editor.
--
-- Supabase queda como fuente de verdad para:
-- 1) Usuarios autorizados para iniciar sesión en Cruces GLM.
-- 2) Permiso para Agregar / Editar / Eliminar formularios.
-- 3) Datos de todos los formularios/cruces del Hub.
-- 4) Sumatorias globales: formularios creados, aperturas y logueos.
-- 5) Métricas por formulario y por usuario.
-- 6) Historial de eventos.
-- 7) Consultas y mutaciones mediante RPC; el frontend no necesita acceso
-- directo a las tablas.
--
-- IMPORTANTE:
-- Google/Supabase puede completar el OAuth, pero Cruces GLM consulta
-- cruces_glm_get_my_access() inmediatamente. Si is_allowed = false o el correo
-- no existe en cruces_glm_permissions, la app cierra esa sesión y no permite
-- entrar. Todas las RPC de datos vuelven a validar el acceso en servidor.
-- ============================================================================
begin;
-- --------------------------------------------------------------------------
-- 1. ACCESOS / PERMISOS
-- --------------------------------------------------------------------------
create table if not exists public.cruces_glm_permissions (
email text primary key,
display_name text,
is_allowed boolean not null default true,
can_manage_forms boolean not null default false,
updated_at timestamptz not null default now(),
constraint cruces_glm_permissions_email_lowercase check (email = lower(email))
);
-- Compatibilidad con versiones anteriores del script.
alter table public.cruces_glm_permissions
add column if not exists display_name text;
alter table public.cruces_glm_permissions
add column if not exists is_allowed boolean not null default true;
alter table public.cruces_glm_permissions
add column if not exists can_manage_forms boolean not null default false;
alter table public.cruces_glm_permissions
add column if not exists updated_at timestamptz not null default now();
alter table public.cruces_glm_permissions enable row level security;
create or replace function public.cruces_glm_set_updated_at()
returns trigger
language plpgsql
as $$
begin
new.updated_at = now();
return new;
end;
$$;
drop trigger if exists cruces_glm_permissions_set_updated_at
on public.cruces_glm_permissions;
create trigger cruces_glm_permissions_set_updated_at
before update on public.cruces_glm_permissions
for each row execute function public.cruces_glm_set_updated_at();
-- Lista inicial autorizada. Solo Isaac administra formularios inicialmente.
-- ON CONFLICT preserva is_allowed/can_manage_forms para no deshacer cambios
-- manuales posteriores si actualizas solamente nombres.
insert into public.cruces_glm_permissions (
email,
display_name,
is_allowed,
can_manage_forms
)
values
('mgomez@gomezleemarketing.com', 'Máximo Gómez', true, false),
('iaracena@gomezleemarketing.com', 'Isaac Aracena', true, true),
('jgomez@gomezleemarketing.com', 'José Leopoldo Gómez', true, false),
('lmatos@gomezleemarketing.com', 'Luis Matos', true, false),
('ymadera@gomezleemarketing.com', 'Yanelly Madera', true, false),
('iherrera@gomezleemarketing.com', 'Iveth Herrera', true, false),
('msoto@gomezleemarketing.com', 'Mati Soto', true, false),
('vparamo@gomezleemarketing.com', 'Viviana Páramo', true, false),
('asrodriguez@gomezleemarketing.com', 'Ada Rodríguez', true, false),
('administrativonicaragua@gomezleemarketing.com', 'Estefani Dayana Orozco Dávila', true, false),
('administrativo@gomezleemarketing.com', 'Seyling Zambrana', true, false),
('adminregional@gomezleemarketing.com', 'Liliana Benítez', true, false)
on conflict (email) do update
set display_name = excluded.display_name;
-- --------------------------------------------------------------------------
-- 2. FORMULARIOS / CRUCES — FUENTE DE VERDAD
-- --------------------------------------------------------------------------
create table if not exists public.cruces_glm_forms (
id text primary key,
nombre text not null,
descripcion text not null,
categoria text not null,
url text not null,
activo boolean not null default true,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
created_by text,
updated_by text,
constraint cruces_glm_forms_categoria_check
check (categoria in ('Seguridad Social', 'IR', 'Reportes')),
constraint cruces_glm_forms_nombre_not_blank check (length(trim(nombre)) > 0),
constraint cruces_glm_forms_descripcion_not_blank check (length(trim(descripcion)) > 0),
constraint cruces_glm_forms_url_not_blank check (length(trim(url)) > 0)
);
create index if not exists cruces_glm_forms_categoria_idx
on public.cruces_glm_forms (categoria, nombre);
create index if not exists cruces_glm_forms_activo_idx
on public.cruces_glm_forms (activo, categoria);
alter table public.cruces_glm_forms enable row level security;
drop trigger if exists cruces_glm_forms_set_updated_at
on public.cruces_glm_forms;
create trigger cruces_glm_forms_set_updated_at
before update on public.cruces_glm_forms
for each row execute function public.cruces_glm_set_updated_at();
-- --------------------------------------------------------------------------
-- 3. MÉTRICAS GLOBALES
-- --------------------------------------------------------------------------
create table if not exists public.cruces_glm_metrics_summary (
id smallint primary key default 1,
total_formularios_colocados bigint not null default 0 check (total_formularios_colocados >= 0),
total_formularios_abiertos bigint not null default 0 check (total_formularios_abiertos >= 0),
total_logueos bigint not null default 0 check (total_logueos >= 0),
updated_at timestamptz not null default now(),
constraint cruces_glm_metrics_summary_single_row check (id = 1)
);
insert into public.cruces_glm_metrics_summary (id)
values (1)
on conflict (id) do nothing;
alter table public.cruces_glm_metrics_summary enable row level security;
-- --------------------------------------------------------------------------
-- 4. MÉTRICAS POR FORMULARIO
-- --------------------------------------------------------------------------
create table if not exists public.cruces_glm_form_metrics (
form_id text primary key,
form_name text not null,
category text,
total_publicaciones bigint not null default 1 check (total_publicaciones >= 0),
total_aperturas bigint not null default 0 check (total_aperturas >= 0),
first_published_at timestamptz not null default now(),
last_published_at timestamptz not null default now(),
last_opened_at timestamptz
);
alter table public.cruces_glm_form_metrics enable row level security;
-- --------------------------------------------------------------------------
-- 5. MÉTRICAS POR USUARIO
-- --------------------------------------------------------------------------
create table if not exists public.cruces_glm_user_metrics (
email text primary key,
total_logueos bigint not null default 0 check (total_logueos >= 0),
total_aperturas bigint not null default 0 check (total_aperturas >= 0),
last_login_at timestamptz,
last_opened_at timestamptz,
constraint cruces_glm_user_metrics_email_lowercase check (email = lower(email))
);
alter table public.cruces_glm_user_metrics enable row level security;
-- --------------------------------------------------------------------------
-- 6. HISTORIAL DE EVENTOS
-- --------------------------------------------------------------------------
create table if not exists public.cruces_glm_metric_events (
id bigint generated by default as identity primary key,
event_type text not null check (event_type in ('login', 'form_created', 'form_opened')),
user_email text not null,
form_id text,
form_name text,
category text,
created_at timestamptz not null default now()
);
create index if not exists cruces_glm_metric_events_type_idx
on public.cruces_glm_metric_events (event_type, created_at desc);
create index if not exists cruces_glm_metric_events_user_idx
on public.cruces_glm_metric_events (user_email, created_at desc);
create index if not exists cruces_glm_metric_events_form_idx
on public.cruces_glm_metric_events (form_id, created_at desc);
alter table public.cruces_glm_metric_events enable row level security;
-- --------------------------------------------------------------------------
-- 7. SEGURIDAD BASE — SIN ACCESO DIRECTO DESDE EL FRONTEND
-- --------------------------------------------------------------------------
-- El navegador usa exclusivamente RPCs SECURITY DEFINER. Esto evita depender
-- de filtros construidos por el cliente para permisos o CRUD.
revoke all on table public.cruces_glm_permissions from anon, authenticated;
revoke all on table public.cruces_glm_forms from anon, authenticated;
revoke all on table public.cruces_glm_metrics_summary from anon, authenticated;
revoke all on table public.cruces_glm_form_metrics from anon, authenticated;
revoke all on table public.cruces_glm_user_metrics from anon, authenticated;
revoke all on table public.cruces_glm_metric_events from anon, authenticated;
-- Quitar policies antiguas conocidas; el acceso normal será solo vía RPC.
drop policy if exists "Cruces GLM - leer permiso propio"
on public.cruces_glm_permissions;
drop policy if exists "Cruces GLM - administradores leen resumen"
on public.cruces_glm_metrics_summary;
drop policy if exists "Cruces GLM - administradores leen metricas formularios"
on public.cruces_glm_form_metrics;
drop policy if exists "Cruces GLM - administradores leen metricas usuarios"
on public.cruces_glm_user_metrics;
drop policy if exists "Cruces GLM - administradores leen eventos"
on public.cruces_glm_metric_events;
-- --------------------------------------------------------------------------
-- 8. HELPERS DE AUTORIZACIÓN
-- --------------------------------------------------------------------------
create or replace function public.cruces_glm_current_email()
returns text
language sql
stable
as $$
select lower(coalesce(auth.jwt() ->> 'email', ''));
$$;
create or replace function public.cruces_glm_is_allowed()
returns boolean
language sql
stable
security definer
set search_path = public
as $$
select exists (
select 1
from public.cruces_glm_permissions p
where p.email = public.cruces_glm_current_email()
and p.is_allowed = true
);
$$;
create or replace function public.cruces_glm_can_manage_forms()
returns boolean
language sql
stable
security definer
set search_path = public
as $$
select exists (
select 1
from public.cruces_glm_permissions p
where p.email = public.cruces_glm_current_email()
and p.is_allowed = true
and p.can_manage_forms = true
);
$$;
revoke all on function public.cruces_glm_current_email() from public;
revoke all on function public.cruces_glm_is_allowed() from public;
revoke all on function public.cruces_glm_can_manage_forms() from public;
-- --------------------------------------------------------------------------
-- 9. RPC — ACCESO DEL USUARIO ACTUAL
-- --------------------------------------------------------------------------
-- Esta es la fuente de verdad del login de Cruces GLM. No existe allowlist
-- hardcodeada en el frontend.
create or replace function public.cruces_glm_get_my_access()
returns table (
allowed boolean,
can_manage_forms boolean,
display_name text
)
language plpgsql
stable
security definer
set search_path = public
as $$
declare
v_email text := public.cruces_glm_current_email();
begin
return query
select
coalesce(p.is_allowed, false) as allowed,
(coalesce(p.is_allowed, false) and coalesce(p.can_manage_forms, false)) as can_manage_forms,
p.display_name
from (select v_email as email) current_user_email
left join public.cruces_glm_permissions p
on p.email = current_user_email.email;
end;
$$;
revoke all on function public.cruces_glm_get_my_access() from public;
grant execute on function public.cruces_glm_get_my_access() to authenticated;
-- --------------------------------------------------------------------------
-- 10. RPC — LISTAR FORMULARIOS
-- --------------------------------------------------------------------------
create or replace function public.cruces_glm_get_forms()
returns table (
id text,
nombre text,
descripcion text,
categoria text,
url text,
activo boolean,
created_at timestamptz,
updated_at timestamptz
)
language plpgsql
stable
security definer
set search_path = public
as $$
begin
if not public.cruces_glm_is_allowed() then
raise exception 'Usuario no autorizado para Cruces GLM';
end if;
return query
select
f.id,
f.nombre,
f.descripcion,
f.categoria,
f.url,
f.activo,
f.created_at,
f.updated_at
from public.cruces_glm_forms f
order by f.nombre asc;
end;
$$;
revoke all on function public.cruces_glm_get_forms() from public;
grant execute on function public.cruces_glm_get_forms() to authenticated;
-- --------------------------------------------------------------------------
-- 11. RPC — CONSULTAR UN FORMULARIO
-- --------------------------------------------------------------------------
create or replace function public.cruces_glm_get_form(p_form_id text)
returns table (
id text,
nombre text,
descripcion text,
categoria text,
url text,
activo boolean,
created_at timestamptz,
updated_at timestamptz
)
language plpgsql
stable
security definer
set search_path = public
as $$
begin
if not public.cruces_glm_is_allowed() then
raise exception 'Usuario no autorizado para Cruces GLM';
end if;
return query
select
f.id,
f.nombre,
f.descripcion,
f.categoria,
f.url,
f.activo,
f.created_at,
f.updated_at
from public.cruces_glm_forms f
where f.id = trim(coalesce(p_form_id, ''))
limit 1;
end;
$$;
revoke all on function public.cruces_glm_get_form(text) from public;
grant execute on function public.cruces_glm_get_form(text) to authenticated;
-- --------------------------------------------------------------------------
-- 12. RPC — CREAR / EDITAR FORMULARIO
-- --------------------------------------------------------------------------
-- Registra total_formularios_colocados únicamente cuando el ID no existía.
-- Editar un formulario no incrementa esa sumatoria.
drop function if exists public.cruces_glm_record_form_created(text, text, text);
create or replace function public.cruces_glm_save_form(
p_form_id text,
p_nombre text,
p_descripcion text,
p_categoria text,
p_url text,
p_activo boolean
)
returns table (
id text,
nombre text,
descripcion text,
categoria text,
url text,
activo boolean,
created_at timestamptz,
updated_at timestamptz
)
language plpgsql
security definer
set search_path = public
as $$
declare
v_email text := public.cruces_glm_current_email();
v_id text := trim(coalesce(p_form_id, ''));
v_nombre text := trim(coalesce(p_nombre, ''));
v_descripcion text := trim(coalesce(p_descripcion, ''));
v_categoria text := trim(coalesce(p_categoria, ''));
v_url text := trim(coalesce(p_url, ''));
v_activo boolean := coalesce(p_activo, false);
v_existed boolean;
begin
if not public.cruces_glm_can_manage_forms() then
raise exception 'Usuario sin permiso para administrar formularios';
end if;
if v_id = '' or v_nombre = '' or v_descripcion = '' or v_categoria = '' or v_url = '' then
raise exception 'Todos los campos del formulario son obligatorios';
end if;
if v_categoria not in ('Seguridad Social', 'IR', 'Reportes') then
raise exception 'Categoría no válida';
end if;
if v_url !~* '^https?://' then
raise exception 'La URL debe comenzar con http:// o https://';
end if;
select exists (
select 1 from public.cruces_glm_forms f where f.id = v_id
) into v_existed;
insert into public.cruces_glm_forms (
id,
nombre,
descripcion,
categoria,
url,
activo,
created_by,
updated_by
)
values (
v_id,
v_nombre,
v_descripcion,
v_categoria,
v_url,
v_activo,
v_email,
v_email
)
on conflict (id) do update
set nombre = excluded.nombre,
descripcion = excluded.descripcion,
categoria = excluded.categoria,
url = excluded.url,
activo = excluded.activo,
updated_by = excluded.updated_by;
if not v_existed then
insert into public.cruces_glm_metrics_summary (
id,
total_formularios_colocados,
total_formularios_abiertos,
total_logueos,
updated_at
)
values (1, 1, 0, 0, now())
on conflict (id) do update
set total_formularios_colocados = public.cruces_glm_metrics_summary.total_formularios_colocados + 1,
updated_at = now();
insert into public.cruces_glm_form_metrics (
form_id,
form_name,
category,
total_publicaciones,
total_aperturas,
first_published_at,
last_published_at
)
values (v_id, v_nombre, v_categoria, 1, 0, now(), now())
on conflict (form_id) do update
set form_name = excluded.form_name,
category = excluded.category,
total_publicaciones = public.cruces_glm_form_metrics.total_publicaciones + 1,
last_published_at = now();
insert into public.cruces_glm_metric_events (
event_type,
user_email,
form_id,
form_name,
category
)
values ('form_created', v_email, v_id, v_nombre, v_categoria);
else
update public.cruces_glm_form_metrics
set form_name = v_nombre,
category = v_categoria
where form_id = v_id;
end if;
return query
select
f.id,
f.nombre,
f.descripcion,
f.categoria,
f.url,
f.activo,
f.created_at,
f.updated_at
from public.cruces_glm_forms f
where f.id = v_id;
end;
$$;
revoke all on function public.cruces_glm_save_form(text, text, text, text, text, boolean) from public;
grant execute on function public.cruces_glm_save_form(text, text, text, text, text, boolean) to authenticated;
-- --------------------------------------------------------------------------
-- 13. RPC — ELIMINAR FORMULARIO
-- --------------------------------------------------------------------------
create or replace function public.cruces_glm_delete_form(p_form_id text)
returns boolean
language plpgsql
security definer
set search_path = public
as $$
declare
v_id text := trim(coalesce(p_form_id, ''));
v_deleted_count integer;
begin
if not public.cruces_glm_can_manage_forms() then
raise exception 'Usuario sin permiso para administrar formularios';
end if;
delete from public.cruces_glm_forms f
where f.id = v_id;
get diagnostics v_deleted_count = row_count;
return v_deleted_count > 0;
end;
$$;
revoke all on function public.cruces_glm_delete_form(text) from public;
grant execute on function public.cruces_glm_delete_form(text) to authenticated;
-- --------------------------------------------------------------------------
-- 14. RPC — REGISTRAR LOGIN
-- --------------------------------------------------------------------------
create or replace function public.cruces_glm_record_login()
returns void
language plpgsql
security definer
set search_path = public
as $$
declare
v_email text := public.cruces_glm_current_email();
begin
if not public.cruces_glm_is_allowed() then
raise exception 'Usuario no autorizado para Cruces GLM';
end if;
insert into public.cruces_glm_metrics_summary (
id,
total_formularios_colocados,
total_formularios_abiertos,
total_logueos,
updated_at
)
values (1, 0, 0, 1, now())
on conflict (id) do update
set total_logueos = public.cruces_glm_metrics_summary.total_logueos + 1,
updated_at = now();
insert into public.cruces_glm_user_metrics (
email,
total_logueos,
total_aperturas,
last_login_at
)
values (v_email, 1, 0, now())
on conflict (email) do update
set total_logueos = public.cruces_glm_user_metrics.total_logueos + 1,
last_login_at = now();
insert into public.cruces_glm_metric_events (event_type, user_email)
values ('login', v_email);
end;
$$;
revoke all on function public.cruces_glm_record_login() from public;
grant execute on function public.cruces_glm_record_login() to authenticated;
-- --------------------------------------------------------------------------
-- 15. RPC — REGISTRAR APERTURA
-- --------------------------------------------------------------------------
-- Solo recibe el ID. Nombre/categoría/estado salen de la tabla real para evitar
-- manipulación desde el navegador. Un formulario inactivo no se contabiliza.
drop function if exists public.cruces_glm_record_form_open(text, text, text);
create or replace function public.cruces_glm_record_form_open(p_form_id text)
returns void
language plpgsql
security definer
set search_path = public
as $$
declare
v_email text := public.cruces_glm_current_email();
v_form_id text := trim(coalesce(p_form_id, ''));
v_form_name text;
v_category text;
v_active boolean;
begin
if not public.cruces_glm_is_allowed() then
raise exception 'Usuario no autorizado para Cruces GLM';
end if;
select f.nombre, f.categoria, f.activo
into v_form_name, v_category, v_active
from public.cruces_glm_forms f
where f.id = v_form_id;
if not found then
raise exception 'Formulario no encontrado';
end if;
if not v_active then
raise exception 'Formulario no disponible';
end if;
insert into public.cruces_glm_metrics_summary (
id,
total_formularios_colocados,
total_formularios_abiertos,
total_logueos,
updated_at
)
values (1, 0, 1, 0, now())
on conflict (id) do update
set total_formularios_abiertos = public.cruces_glm_metrics_summary.total_formularios_abiertos + 1,
updated_at = now();
insert into public.cruces_glm_user_metrics (
email,
total_logueos,
total_aperturas,
last_opened_at
)
values (v_email, 0, 1, now())
on conflict (email) do update
set total_aperturas = public.cruces_glm_user_metrics.total_aperturas + 1,
last_opened_at = now();
insert into public.cruces_glm_form_metrics (
form_id,
form_name,
category,
total_publicaciones,
total_aperturas,
first_published_at,
last_published_at,
last_opened_at
)
values (v_form_id, v_form_name, v_category, 0, 1, now(), now(), now())
on conflict (form_id) do update
set form_name = excluded.form_name,
category = excluded.category,
total_aperturas = public.cruces_glm_form_metrics.total_aperturas + 1,
last_opened_at = now();
insert into public.cruces_glm_metric_events (
event_type,
user_email,
form_id,
form_name,
category
)
values ('form_opened', v_email, v_form_id, v_form_name, v_category);
end;
$$;
revoke all on function public.cruces_glm_record_form_open(text) from public;
grant execute on function public.cruces_glm_record_form_open(text) to authenticated;
-- --------------------------------------------------------------------------
-- 16. RPC — CONSULTAS DE MÉTRICAS PARA FUTURO DASHBOARD
-- --------------------------------------------------------------------------
create or replace function public.cruces_glm_get_metrics_summary()
returns table (
total_formularios_colocados bigint,
total_formularios_abiertos bigint,
total_logueos bigint,
updated_at timestamptz
)
language plpgsql
stable
security definer
set search_path = public
as $$
begin
if not public.cruces_glm_can_manage_forms() then
raise exception 'Usuario sin permiso para consultar métricas';
end if;
return query
select
m.total_formularios_colocados,
m.total_formularios_abiertos,
m.total_logueos,
m.updated_at
from public.cruces_glm_metrics_summary m
where m.id = 1;
end;
$$;
create or replace function public.cruces_glm_get_form_metrics()
returns table (
form_id text,
form_name text,
category text,
total_publicaciones bigint,
total_aperturas bigint,
first_published_at timestamptz,
last_published_at timestamptz,
last_opened_at timestamptz
)
language plpgsql
stable
security definer
set search_path = public
as $$
begin
if not public.cruces_glm_can_manage_forms() then
raise exception 'Usuario sin permiso para consultar métricas';
end if;
return query
select
m.form_id,
m.form_name,
m.category,
m.total_publicaciones,
m.total_aperturas,
m.first_published_at,
m.last_published_at,
m.last_opened_at
from public.cruces_glm_form_metrics m
order by m.total_aperturas desc, m.form_name asc;
end;
$$;
create or replace function public.cruces_glm_get_user_metrics()
returns table (
email text,
total_logueos bigint,
total_aperturas bigint,
last_login_at timestamptz,
last_opened_at timestamptz
)
language plpgsql
stable
security definer
set search_path = public
as $$
begin
if not public.cruces_glm_can_manage_forms() then
raise exception 'Usuario sin permiso para consultar métricas';
end if;
return query
select
m.email,
m.total_logueos,
m.total_aperturas,
m.last_login_at,
m.last_opened_at
from public.cruces_glm_user_metrics m
order by m.total_logueos desc, m.email asc;
end;
$$;
create or replace function public.cruces_glm_get_recent_events(p_limit integer default 100)
returns table (
id bigint,
event_type text,
user_email text,
form_id text,
form_name text,
category text,
created_at timestamptz
)
language plpgsql
stable
security definer
set search_path = public
as $$
begin
if not public.cruces_glm_can_manage_forms() then
raise exception 'Usuario sin permiso para consultar métricas';
end if;
return query
select
e.id,
e.event_type,
e.user_email,
e.form_id,
e.form_name,
e.category,
e.created_at
from public.cruces_glm_metric_events e
order by e.created_at desc
limit least(greatest(coalesce(p_limit, 100), 1), 500);
end;
$$;
revoke all on function public.cruces_glm_get_metrics_summary() from public;
revoke all on function public.cruces_glm_get_form_metrics() from public;
revoke all on function public.cruces_glm_get_user_metrics() from public;
revoke all on function public.cruces_glm_get_recent_events(integer) from public;
grant execute on function public.cruces_glm_get_metrics_summary() to authenticated;
grant execute on function public.cruces_glm_get_form_metrics() to authenticated;
grant execute on function public.cruces_glm_get_user_metrics() to authenticated;
grant execute on function public.cruces_glm_get_recent_events(integer) to authenticated;
-- --------------------------------------------------------------------------
-- 17. RPC — ADMINISTRACIÓN DE ACCESOS
-- --------------------------------------------------------------------------
-- No hay UI para esto todavía, pero queda listo para una futura pantalla.
-- Hoy puedes administrar cruces_glm_permissions directamente en Table Editor.
create or replace function public.cruces_glm_admin_get_access_list()
returns table (
email text,
display_name text,
is_allowed boolean,
can_manage_forms boolean,
updated_at timestamptz
)
language plpgsql
stable
security definer
set search_path = public
as $$
begin
if not public.cruces_glm_can_manage_forms() then
raise exception 'Usuario sin permiso para administrar accesos';
end if;
return query
select
p.email,
p.display_name,
p.is_allowed,
p.can_manage_forms,
p.updated_at
from public.cruces_glm_permissions p
order by p.display_name nulls last, p.email;
end;
$$;
create or replace function public.cruces_glm_admin_save_access(
p_email text,
p_display_name text,
p_is_allowed boolean,
p_can_manage_forms boolean
)
returns void
language plpgsql
security definer
set search_path = public
as $$
declare
v_email text := lower(trim(coalesce(p_email, '')));
v_name text := nullif(trim(coalesce(p_display_name, '')), '');
begin
if not public.cruces_glm_can_manage_forms() then
raise exception 'Usuario sin permiso para administrar accesos';
end if;
if v_email = '' or position('@' in v_email) <= 1 then
raise exception 'Correo no válido';
end if;
insert into public.cruces_glm_permissions (
email,
display_name,
is_allowed,
can_manage_forms
)
values (
v_email,
v_name,
coalesce(p_is_allowed, false),
coalesce(p_can_manage_forms, false)
)
on conflict (email) do update
set display_name = excluded.display_name,
is_allowed = excluded.is_allowed,
can_manage_forms = excluded.can_manage_forms;
end;
$$;
revoke all on function public.cruces_glm_admin_get_access_list() from public;
revoke all on function public.cruces_glm_admin_save_access(text, text, boolean, boolean) from public;
grant execute on function public.cruces_glm_admin_get_access_list() to authenticated;
grant execute on function public.cruces_glm_admin_save_access(text, text, boolean, boolean) to authenticated;
commit;
-- ============================================================================
-- ADMINISTRACIÓN RÁPIDA DESDE SQL EDITOR / TABLE EDITOR
-- ============================================================================
-- Dar acceso a Cruces GLM:
-- update public.cruces_glm_permissions
-- set is_allowed = true
-- where email = 'correo@gomezleemarketing.com';
--
-- Quitar acceso a Cruces GLM sin borrar historial:
-- update public.cruces_glm_permissions
-- set is_allowed = false
-- where email = 'correo@gomezleemarketing.com';
--
-- Dar permiso de Agregar / Editar / Eliminar:
-- update public.cruces_glm_permissions
-- set can_manage_forms = true
-- where email = 'correo@gomezleemarketing.com';
--
-- Quitar permiso de administración:
-- update public.cruces_glm_permissions
-- set can_manage_forms = false
-- where email = 'correo@gomezleemarketing.com';
--
-- Formularios actualmente guardados:
-- select * from public.cruces_glm_forms order by nombre;
--
-- Métricas globales:
-- select * from public.cruces_glm_metrics_summary;
--
-- Métricas por formulario:
-- select * from public.cruces_glm_form_metrics order by total_aperturas desc;
--
-- Métricas por usuario:
-- select * from public.cruces_glm_user_metrics order by total_logueos desc;
--
-- Eventos recientes:
-- select * from public.cruces_glm_metric_events order by created_at desc limit 100;
-- ============================================================================