Initial commit without README
This commit is contained in:
@@ -0,0 +1,977 @@
|
||||
-- ============================================================================
|
||||
-- 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;
|
||||
-- ============================================================================
|
||||
Reference in New Issue
Block a user