978 lines
30 KiB
PL/PgSQL
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;
|
|
-- ============================================================================
|