Files
cdc-project-management/supabase_tariff_admin_module.sql
T

830 lines
29 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =========================================================
-- TABLERO CDC - TARIFARIO WALMART CONNECT + MÓDULO DE ADMINISTRACIÓN
-- Versión segura e idempotente para producción.
--
-- Qué hace:
-- 1) Habilita la sección "wmc" en el catálogo dinámico existente.
-- 2) Inserta/actualiza las 12 piezas y montos del Excel Tarifario_XCDC_WMC.
-- 3) Mantiene intactos proyectos, costos históricos, links, listas, n8n y tarifas CDC existentes.
-- 4) Añade secciones generales dinámicas y tarifarios especiales por cliente.
-- 5) Controla el módulo con can_manage_tariff_catalog desde Supabase.
-- 6) Protege las tarifas administradas en la app frente al sync del Sheet.
--
-- Puede ejecutarse más de una vez: usa CREATE IF NOT EXISTS y UPSERT.
-- =========================================================
begin;
-- 1) Asegurar la tabla del catálogo dinámico.
create table if not exists public.tablero_cdc_tariff_catalog (
id text primary key,
catalog_item_id text not null,
section text not null,
category text not null default '',
service text not null default '',
notes text not null default '',
work_type_id text not null default 'reference',
work_type_label text not null default 'Referencia',
work_type_short_label text not null default 'Ref.',
hour_reference text not null default '',
level_id text not null default 'project',
level_label text not null default 'Precio único',
reference text not null default '',
reference_min numeric,
reference_max numeric,
level_hint text not null default '',
sort_order integer not null default 9999,
is_active boolean not null default true,
created_by uuid references auth.users(id),
updated_by uuid references auth.users(id),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
-- Compatibilidad con instalaciones donde alguna columna todavía no exista.
alter table public.tablero_cdc_tariff_catalog
add column if not exists catalog_item_id text,
add column if not exists section text,
add column if not exists category text not null default '',
add column if not exists service text not null default '',
add column if not exists notes text not null default '',
add column if not exists work_type_id text not null default 'reference',
add column if not exists work_type_label text not null default 'Referencia',
add column if not exists work_type_short_label text not null default 'Ref.',
add column if not exists hour_reference text not null default '',
add column if not exists level_id text not null default 'project',
add column if not exists level_label text not null default 'Precio único',
add column if not exists reference text not null default '',
add column if not exists reference_min numeric,
add column if not exists reference_max numeric,
add column if not exists level_hint text not null default '',
add column if not exists sort_order integer not null default 9999,
add column if not exists is_active boolean not null default true,
add column if not exists created_by uuid references auth.users(id),
add column if not exists updated_by uuid references auth.users(id),
add column if not exists created_at timestamptz not null default now(),
add column if not exists updated_at timestamptz not null default now();
-- Completar únicamente valores vacíos si la tabla venía de una versión antigua.
-- No se normalizan IDs de sección porque el módulo admite secciones nuevas y dinámicas.
update public.tablero_cdc_tariff_catalog
set
catalog_item_id = coalesce(nullif(trim(catalog_item_id), ''), id),
section = coalesce(nullif(trim(section), ''), 'grafico')
where catalog_item_id is null
or nullif(trim(catalog_item_id), '') is null
or section is null
or nullif(trim(section), '') is null;
alter table public.tablero_cdc_tariff_catalog
alter column catalog_item_id set not null,
alter column section set not null;
-- Retirar cualquier restricción antigua que limite las secciones a IDs fijos.
alter table public.tablero_cdc_tariff_catalog
drop constraint if exists tablero_cdc_tariff_catalog_section_check;
create index if not exists tablero_cdc_tariff_catalog_section_idx
on public.tablero_cdc_tariff_catalog(section, is_active, sort_order);
create index if not exists tablero_cdc_tariff_catalog_item_idx
on public.tablero_cdc_tariff_catalog(catalog_item_id);
-- 2) RLS: lectura para usuarios autenticados y escritura para quienes pueden tarifar.
alter table public.tablero_cdc_tariff_catalog enable row level security;
create or replace function public.tablero_cdc_current_user_can_manage_internal_pricing()
returns boolean
language sql
stable
security definer
set search_path = public
as $$
select exists (
select 1
from public.tablero_cdc_allowed_users u
where lower(trim(u.email)) = lower(trim(coalesce(auth.jwt() ->> 'email', '')))
and u.is_active = true
and u.can_manage_internal_pricing = true
);
$$;
grant execute on function public.tablero_cdc_current_user_can_manage_internal_pricing()
to authenticated;
drop policy if exists "tablero_cdc_tariff_catalog_select_authenticated"
on public.tablero_cdc_tariff_catalog;
drop policy if exists "tablero_cdc_tariff_catalog_insert_pricing_admins"
on public.tablero_cdc_tariff_catalog;
drop policy if exists "tablero_cdc_tariff_catalog_update_pricing_admins"
on public.tablero_cdc_tariff_catalog;
drop policy if exists "tablero_cdc_tariff_catalog_delete_pricing_admins"
on public.tablero_cdc_tariff_catalog;
create policy "tablero_cdc_tariff_catalog_select_authenticated"
on public.tablero_cdc_tariff_catalog
for select
to authenticated
using (true);
create policy "tablero_cdc_tariff_catalog_insert_pricing_admins"
on public.tablero_cdc_tariff_catalog
for insert
to authenticated
with check (public.tablero_cdc_current_user_can_manage_internal_pricing());
create policy "tablero_cdc_tariff_catalog_update_pricing_admins"
on public.tablero_cdc_tariff_catalog
for update
to authenticated
using (public.tablero_cdc_current_user_can_manage_internal_pricing())
with check (public.tablero_cdc_current_user_can_manage_internal_pricing());
-- No se recomienda borrar tarifas usadas históricamente; se deben desactivar.
create policy "tablero_cdc_tariff_catalog_delete_pricing_admins"
on public.tablero_cdc_tariff_catalog
for delete
to authenticated
using (public.tablero_cdc_current_user_can_manage_internal_pricing());
grant select, insert, update, delete
on public.tablero_cdc_tariff_catalog
to authenticated;
create or replace function public.set_tablero_cdc_tariff_catalog_updated_at()
returns trigger
language plpgsql
as $$
begin
new.updated_at = now();
new.updated_by = auth.uid();
if tg_op = 'INSERT' then
new.created_by = coalesce(new.created_by, auth.uid());
end if;
return new;
end;
$$;
drop trigger if exists trg_tablero_cdc_tariff_catalog_updated_at
on public.tablero_cdc_tariff_catalog;
create trigger trg_tablero_cdc_tariff_catalog_updated_at
before insert or update on public.tablero_cdc_tariff_catalog
for each row
execute function public.set_tablero_cdc_tariff_catalog_updated_at();
-- 3) Cargar las 12 piezas del Excel de Walmart Connect.
insert into public.tablero_cdc_tariff_catalog (
id,
catalog_item_id,
section,
category,
service,
notes,
work_type_id,
work_type_label,
work_type_short_label,
hour_reference,
level_id,
level_label,
reference,
reference_min,
reference_max,
level_hint,
sort_order,
is_active
)
values
(
'wmc-uniformes__wmc-fixed__fixed',
'wmc-uniformes',
'wmc',
'Walmart Connect',
'Uniformes',
'Uniforme completo para personal de impulso en piso (playera/top, jogger, falda, chaleco), con 4 variantes de diseño exploradas. Equivalencia CDC: Materiales de PDV estándar y otros materiales básicos > Uniformes. Incluye diseño en alta resolución y 3 rondas de cambios.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '80 USD', 80, 80,
'Incluye diseño en alta resolución y 3 rondas de cambios.',
20001, true
),
(
'wmc-uniformes-supervisor__wmc-fixed__fixed',
'wmc-uniformes-supervisor',
'wmc',
'Walmart Connect',
'Uniformes Supervisor',
'Uniforme para supervisor de piso (polo, chumpa/track jacket, camisa formal), con 3 variantes de diseño exploradas. Equivalencia CDC: Materiales de PDV estándar y otros materiales básicos > Uniformes. Incluye diseño en alta resolución y 3 rondas de cambios.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '100 USD', 100, 100,
'Incluye diseño en alta resolución y 3 rondas de cambios.',
20002, true
),
(
'wmc-glorificador-mano__wmc-fixed__fixed',
'wmc-glorificador-mano',
'wmc',
'Walmart Connect',
'Glorificador de mano',
'Exhibidor portátil pequeño para presentar el producto: bandeja iluminada, aro LED, estuche o base acrílica. Equivalencia CDC: Stands, Muebles y Exhibidores > Estándar (Displays básicos). Incluye diseño 3D, troqueles y 3 rondas de cambios.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '150 USD', 150, 150,
'Incluye diseño 3D, troqueles y 3 rondas de cambios.',
20003, true
),
(
'wmc-carritos-intervenidos__wmc-fixed__fixed',
'wmc-carritos-intervenidos',
'wmc',
'Walmart Connect',
'Carritos intervenidos',
'Vinil o calcomanía de marca aplicada a la canasta y al mango del carrito de compras. Equivalencia CDC: Materiales de PDV estándar y otros materiales básicos (análogo a Backings/Rompetráficos). Pieza análoga a material gráfico estándar aplicado sobre una superficie existente.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '200 USD', 200, 200,
'Pieza análoga a material gráfico estándar aplicado sobre una superficie existente.',
20004, true
),
(
'wmc-dummies-exhibicion__wmc-fixed__fixed',
'wmc-dummies-exhibicion',
'wmc',
'Walmart Connect',
'Dummies de exhibición',
'Réplica escultórica a gran escala de pasta y cepillo dental como punto focal decorativo o táctil. Equivalencia CDC: Proyectos especiales > Diseño de muebles / proyectos con planos y troqueles desde cero. Requiere diseño estructural y visualización 3D; no es solo arte de impresión.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '200 USD', 200, 200,
'Requiere diseño estructural y visualización 3D; no es solo arte de impresión.',
20005, true
),
(
'wmc-photobooth__wmc-fixed__fixed',
'wmc-photobooth',
'wmc',
'Walmart Connect',
'Photobooth',
'Estructura tipo arco con iluminación LED y espacio fotográfico de marca para activaciones. Equivalencia CDC: Proyectos especiales > Diseño de Stands Creativos para ferias o eventos. Estructura autoportante con iluminación, ambientación y 4 rondas de cambios.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '200 USD', 200, 200,
'Estructura autoportante con iluminación, ambientación y 4 rondas de cambios.',
20006, true
),
(
'wmc-play-station__wmc-fixed__fixed',
'wmc-play-station',
'wmc',
'Walmart Connect',
'Play Station',
'Kiosco interactivo con pantalla táctil y mecánica de juego. Equivalencia CDC: Proyectos especiales > Diseño de muebles / proyectos especiales. Incluye diseño de mueble e interfaz gráfica; el desarrollo funcional del juego se cotiza aparte.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '200 USD', 200, 200,
'Incluye diseño de mueble e interfaz gráfica; el desarrollo funcional del juego se cotiza aparte.',
20007, true
),
(
'wmc-isla-wm__wmc-fixed__fixed',
'wmc-isla-wm',
'wmc',
'Walmart Connect',
'Isla WM',
'Mueble isla completo multinivel con iluminación, gráficos y espacio de exhibición de la línea completa de producto. Equivalencia CDC: Proyectos especiales > Diseño de Stands Creativos para ferias o eventos. Mayor escala y complejidad estructural dentro del set de piezas.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '150 USD', 150, 150,
'Mayor escala y complejidad estructural dentro del set de piezas.',
20008, true
),
(
'wmc-exhibicion-especial__wmc-fixed__fixed',
'wmc-exhibicion-especial',
'wmc',
'Walmart Connect',
'Exhibición Especial',
'Propuesta de muebles de exhibición. Equivalencia CDC: Proyectos especiales > Diseño de muebles con planos y troqueles desde cero. Costo de referencia por concepto; las variantes adicionales se cobran como adicionales.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '250 USD', 250, 250,
'Costo de referencia por concepto; las variantes adicionales se cobran como adicionales.',
20009, true
),
(
'wmc-punta-gondola__wmc-fixed__fixed',
'wmc-punta-gondola',
'wmc',
'Walmart Connect',
'Punta de góndola',
'Cabecera de góndola personalizada con iluminación, gráficos y espacio para producto. Equivalencia CDC: Proyectos especiales > Diseño de muebles con planos y troqueles desde cero. Costo de referencia por concepto; las variantes adicionales se cobran como adicionales.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '250 USD', 250, 250,
'Costo de referencia por concepto; las variantes adicionales se cobran como adicionales.',
20010, true
),
(
'wmc-estacion-prueba-producto__wmc-fixed__fixed',
'wmc-estacion-prueba-producto',
'wmc',
'Walmart Connect',
'Estación para prueba de producto',
'Carrito móvil con lavamanos funcional, grifo y desagüe para pruebas de producto en piso. Equivalencia CDC: Proyectos especiales > Diseño de Stands Creativos para ferias o eventos. La complejidad funcional puede requerir una cotización especial fuera del tarifario estándar.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '200 USD', 200, 200,
'La complejidad funcional puede requerir una cotización especial fuera del tarifario estándar.',
20011, true
),
(
'wmc-arco-entrada__wmc-fixed__fixed',
'wmc-arco-entrada',
'wmc',
'Walmart Connect',
'Arco de entrada',
'Estructura de entrada de tienda a gran formato con iluminación, efecto de niebla y gráficos de marca. Equivalencia CDC: Proyectos especiales > Diseño de Stands Creativos para ferias o eventos. La instalación en sitio se cotiza aparte.',
'wmc_fixed', 'Tarifa fija WMC', 'WMC', '35 - 80',
'fixed', 'Precio fijo', '200 USD', 200, 200,
'La instalación en sitio se cotiza aparte.',
20012, true
)
on conflict (id) do update
set
catalog_item_id = excluded.catalog_item_id,
section = excluded.section,
category = excluded.category,
service = excluded.service,
notes = excluded.notes,
work_type_id = excluded.work_type_id,
work_type_label = excluded.work_type_label,
work_type_short_label = excluded.work_type_short_label,
hour_reference = excluded.hour_reference,
level_id = excluded.level_id,
level_label = excluded.level_label,
reference = excluded.reference,
reference_min = excluded.reference_min,
reference_max = excluded.reference_max,
level_hint = excluded.level_hint,
sort_order = excluded.sort_order,
is_active = true,
updated_at = now();
-- 4) Si el flujo estándar usa esta RPC para desactivar tarifas ausentes,
-- solo afectará grafico/estrategia y nunca apagará la sección WMC.
create or replace function public.tablero_cdc_deactivate_missing_tariffs(p_active_ids text[])
returns integer
language plpgsql
security definer
set search_path = public
as $$
declare
v_count integer;
begin
if p_active_ids is null or array_length(p_active_ids, 1) is null then
raise exception 'No se recibieron IDs activos. Se cancela la desactivación.';
end if;
if array_length(p_active_ids, 1) < 3 then
raise exception 'Se recibieron menos de 3 IDs activos. Se cancela la desactivación por seguridad.';
end if;
update public.tablero_cdc_tariff_catalog
set
is_active = false,
updated_at = now()
where section in ('grafico', 'estrategia')
and is_active = true
and not (id = any(p_active_ids));
get diagnostics v_count = row_count;
return v_count;
end;
$$;
grant execute on function public.tablero_cdc_deactivate_missing_tariffs(text[])
to authenticated, service_role;
commit;
-- Verificación final: deben aparecer 12 filas activas y un total de referencia de 2,180 USD.
select
count(*) as piezas_wmc_activas,
coalesce(sum(reference_min), 0)::numeric(12, 2) as suma_de_todas_las_piezas
from public.tablero_cdc_tariff_catalog
where section = 'wmc'
and is_active = true;
select
service as pieza,
reference_min::numeric(12, 2) as monto_fijo,
is_active
from public.tablero_cdc_tariff_catalog
where section = 'wmc'
order by sort_order, service;
-- =========================================================
-- MÓDULO DE ADMINISTRACIÓN DEL TARIFARIO
-- Añade permisos dinámicos, secciones generales y tarifarios por cliente.
-- Seguro para ejecutar después del bloque anterior y repetir posteriormente.
-- =========================================================
begin;
-- 1) Permiso específico del módulo, independiente del permiso para tarifar proyectos.
alter table public.tablero_cdc_allowed_users
add column if not exists can_manage_tariff_catalog boolean not null default false;
insert into public.tablero_cdc_allowed_users (
email,
full_name,
role,
is_active,
can_delete_projects,
can_manage_internal_pricing,
can_control_pricing_summary,
can_manage_tariff_catalog
)
values
(
'iaracena@gomezleemarketing.com',
'Isaac Daniel Aracena Toribio',
'admin_it',
true,
true,
true,
true,
true
),
(
'gmarrero@gomezleemarketing.com',
'Gerardo Marrero',
'director_creativo',
true,
true,
true,
true,
true
),
(
'areyes@gomezleemarketing.com',
'Alicia Thaía Reyes',
'user',
true,
true,
true,
false,
true
)
on conflict (email) do update
set
can_manage_tariff_catalog = true,
updated_at = now();
create or replace function public.tablero_cdc_current_user_can_manage_tariff_catalog()
returns boolean
language sql
stable
security definer
set search_path = public
as $$
select exists (
select 1
from public.tablero_cdc_allowed_users u
where lower(trim(u.email)) = lower(trim(coalesce(auth.jwt() ->> 'email', '')))
and u.is_active = true
and u.can_manage_tariff_catalog = true
);
$$;
grant execute on function public.tablero_cdc_current_user_can_manage_tariff_catalog()
to authenticated;
-- 2) Tabla de secciones: generales o asociadas a un cliente.
create table if not exists public.tablero_cdc_tariff_sections (
id text primary key,
name text not null,
scope text not null default 'general'
check (scope in ('general', 'client')),
client_name text not null default '',
pricing_mode text not null default 'guided'
check (pricing_mode in ('guided', 'fixed_multi')),
allow_manual boolean not null default true,
description text not null default '',
sort_order integer not null default 9999,
is_active boolean not null default true,
created_by uuid references auth.users(id),
updated_by uuid references auth.users(id),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint tablero_cdc_tariff_sections_client_check check (
(scope = 'general' and client_name = '' and pricing_mode = 'guided')
or
(scope = 'client' and nullif(trim(client_name), '') is not null and pricing_mode = 'fixed_multi')
)
);
create index if not exists tablero_cdc_tariff_sections_scope_idx
on public.tablero_cdc_tariff_sections(scope, is_active, sort_order);
create index if not exists tablero_cdc_tariff_sections_client_idx
on public.tablero_cdc_tariff_sections(lower(client_name), is_active);
insert into public.tablero_cdc_tariff_sections (
id, name, scope, client_name, pricing_mode, allow_manual,
description, sort_order, is_active
)
values
(
'grafico',
'Tarifario Gráfico CDC',
'general',
'',
'guided',
true,
'Diseño gráfico, artes finales, adaptaciones y materiales de punto de venta.',
10,
true
),
(
'estrategia',
'Estrategia y Creatividad',
'general',
'',
'guided',
true,
'Servicios de estrategia, conceptualización y creatividad.',
20,
true
),
(
'wmc',
'Tarifario Walmart Connect',
'client',
'Walmart Connect WMC',
'fixed_multi',
true,
'Piezas de precio fijo que aparecen únicamente al seleccionar Walmart Connect WMC.',
100,
true
)
on conflict (id) do update
set
name = excluded.name,
scope = excluded.scope,
client_name = excluded.client_name,
pricing_mode = excluded.pricing_mode,
allow_manual = excluded.allow_manual,
description = excluded.description,
sort_order = excluded.sort_order,
is_active = excluded.is_active,
updated_at = now();
alter table public.tablero_cdc_tariff_sections enable row level security;
drop policy if exists "tablero_cdc_tariff_sections_select_authenticated"
on public.tablero_cdc_tariff_sections;
drop policy if exists "tablero_cdc_tariff_sections_insert_admins"
on public.tablero_cdc_tariff_sections;
drop policy if exists "tablero_cdc_tariff_sections_update_admins"
on public.tablero_cdc_tariff_sections;
drop policy if exists "tablero_cdc_tariff_sections_delete_admins"
on public.tablero_cdc_tariff_sections;
create policy "tablero_cdc_tariff_sections_select_authenticated"
on public.tablero_cdc_tariff_sections
for select
to authenticated
using (true);
create policy "tablero_cdc_tariff_sections_insert_admins"
on public.tablero_cdc_tariff_sections
for insert
to authenticated
with check (public.tablero_cdc_current_user_can_manage_tariff_catalog());
create policy "tablero_cdc_tariff_sections_update_admins"
on public.tablero_cdc_tariff_sections
for update
to authenticated
using (public.tablero_cdc_current_user_can_manage_tariff_catalog())
with check (public.tablero_cdc_current_user_can_manage_tariff_catalog());
create policy "tablero_cdc_tariff_sections_delete_admins"
on public.tablero_cdc_tariff_sections
for delete
to authenticated
using (public.tablero_cdc_current_user_can_manage_tariff_catalog());
grant select, insert, update, delete
on public.tablero_cdc_tariff_sections
to authenticated;
create or replace function public.set_tablero_cdc_tariff_sections_updated_at()
returns trigger
language plpgsql
as $$
begin
new.updated_at = now();
new.updated_by = auth.uid();
if tg_op = 'INSERT' then
new.created_by = coalesce(new.created_by, auth.uid());
end if;
return new;
end;
$$;
drop trigger if exists trg_tablero_cdc_tariff_sections_updated_at
on public.tablero_cdc_tariff_sections;
create trigger trg_tablero_cdc_tariff_sections_updated_at
before insert or update on public.tablero_cdc_tariff_sections
for each row
execute function public.set_tablero_cdc_tariff_sections_updated_at();
-- 3) El catálogo acepta cualquier sección registrada, no solo tres IDs fijos.
alter table public.tablero_cdc_tariff_catalog
drop constraint if exists tablero_cdc_tariff_catalog_section_check;
alter table public.tablero_cdc_tariff_catalog
add column if not exists managed_by text not null default 'sheet';
alter table public.tablero_cdc_tariff_catalog
drop constraint if exists tablero_cdc_tariff_catalog_managed_by_check;
alter table public.tablero_cdc_tariff_catalog
add constraint tablero_cdc_tariff_catalog_managed_by_check
check (managed_by in ('sheet', 'app', 'system'));
update public.tablero_cdc_tariff_catalog
set managed_by = 'system'
where section = 'wmc'
and managed_by = 'sheet';
-- Las escrituras desde el módulo requieren el permiso específico.
drop policy if exists "tablero_cdc_tariff_catalog_select_authenticated"
on public.tablero_cdc_tariff_catalog;
drop policy if exists "tablero_cdc_tariff_catalog_insert_pricing_admins"
on public.tablero_cdc_tariff_catalog;
drop policy if exists "tablero_cdc_tariff_catalog_update_pricing_admins"
on public.tablero_cdc_tariff_catalog;
drop policy if exists "tablero_cdc_tariff_catalog_delete_pricing_admins"
on public.tablero_cdc_tariff_catalog;
drop policy if exists "tablero_cdc_tariff_catalog_insert_catalog_admins"
on public.tablero_cdc_tariff_catalog;
drop policy if exists "tablero_cdc_tariff_catalog_update_catalog_admins"
on public.tablero_cdc_tariff_catalog;
drop policy if exists "tablero_cdc_tariff_catalog_delete_catalog_admins"
on public.tablero_cdc_tariff_catalog;
create policy "tablero_cdc_tariff_catalog_select_authenticated"
on public.tablero_cdc_tariff_catalog
for select
to authenticated
using (true);
create policy "tablero_cdc_tariff_catalog_insert_catalog_admins"
on public.tablero_cdc_tariff_catalog
for insert
to authenticated
with check (public.tablero_cdc_current_user_can_manage_tariff_catalog());
create policy "tablero_cdc_tariff_catalog_update_catalog_admins"
on public.tablero_cdc_tariff_catalog
for update
to authenticated
using (public.tablero_cdc_current_user_can_manage_tariff_catalog())
with check (public.tablero_cdc_current_user_can_manage_tariff_catalog());
create policy "tablero_cdc_tariff_catalog_delete_catalog_admins"
on public.tablero_cdc_tariff_catalog
for delete
to authenticated
using (public.tablero_cdc_current_user_can_manage_tariff_catalog());
grant select, insert, update, delete
on public.tablero_cdc_tariff_catalog
to authenticated;
-- Protege los registros administrados desde la app para que el sync del Sheet no los sobrescriba.
create or replace function public.set_tablero_cdc_tariff_catalog_updated_at()
returns trigger
language plpgsql
as $$
declare
v_request_role text := coalesce(
nullif(auth.role(), ''),
nullif(current_setting('request.jwt.claim.role', true), ''),
''
);
begin
if tg_op = 'UPDATE'
and old.managed_by = 'app'
and v_request_role = 'service_role' then
-- El sync de n8n no puede sobrescribir una tarifa que ya fue administrada en la app.
return old;
end if;
new.updated_at = now();
new.updated_by = auth.uid();
if v_request_role = 'authenticated' then
new.managed_by = 'app';
end if;
if tg_op = 'INSERT' then
new.created_by = coalesce(new.created_by, auth.uid());
end if;
return new;
end;
$$;
drop trigger if exists trg_tablero_cdc_tariff_catalog_updated_at
on public.tablero_cdc_tariff_catalog;
create trigger trg_tablero_cdc_tariff_catalog_updated_at
before insert or update on public.tablero_cdc_tariff_catalog
for each row
execute function public.set_tablero_cdc_tariff_catalog_updated_at();
-- El sync estándar solo desactiva registros gestionados por el Sheet.
create or replace function public.tablero_cdc_deactivate_missing_tariffs(p_active_ids text[])
returns integer
language plpgsql
security definer
set search_path = public
as $$
declare
v_count integer;
begin
if p_active_ids is null or array_length(p_active_ids, 1) is null then
raise exception 'No se recibieron IDs activos. Se cancela la desactivación.';
end if;
if array_length(p_active_ids, 1) < 3 then
raise exception 'Se recibieron menos de 3 IDs activos. Se cancela la desactivación por seguridad.';
end if;
update public.tablero_cdc_tariff_catalog
set
is_active = false,
updated_at = now()
where section in ('grafico', 'estrategia')
and managed_by <> 'app'
and is_active = true
and not (id = any(p_active_ids));
get diagnostics v_count = row_count;
return v_count;
end;
$$;
grant execute on function public.tablero_cdc_deactivate_missing_tariffs(text[])
to authenticated, service_role;
-- 4) Trazabilidad opcional de la tarifa usada en cada proyecto.
alter table public.tablero_cdc_project_pricing_items
add column if not exists tariff_section_id text,
add column if not exists tariff_catalog_id text;
create index if not exists tablero_cdc_project_pricing_items_tariff_section_idx
on public.tablero_cdc_project_pricing_items(tariff_section_id);
create index if not exists tablero_cdc_project_pricing_items_tariff_catalog_idx
on public.tablero_cdc_project_pricing_items(tariff_catalog_id);
commit;
-- =========================================================
-- VERIFICACIÓN
-- =========================================================
select
email,
full_name,
is_active,
can_manage_tariff_catalog
from public.tablero_cdc_allowed_users
where lower(trim(email)) in (
'iaracena@gomezleemarketing.com',
'gmarrero@gomezleemarketing.com',
'areyes@gomezleemarketing.com'
)
order by email;
select
id,
name,
scope,
client_name,
pricing_mode,
allow_manual,
is_active,
sort_order
from public.tablero_cdc_tariff_sections
order by sort_order, name;
select
section,
managed_by,
count(*) filter (where is_active) as tarifas_activas,
count(*) filter (where not is_active) as tarifas_inactivas
from public.tablero_cdc_tariff_catalog
group by section, managed_by
order by section, managed_by;