830 lines
29 KiB
PL/PgSQL
830 lines
29 KiB
PL/PgSQL
-- =========================================================
|
||
-- 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;
|