Agency validates first then client; Meta copy is 5+5+1 with on-image vs feed layers, emojis on feed, and partial v2 by image/hook/support/copy. Co-authored-by: Cursor <cursoragent@cursor.com>
105 lines
3.8 KiB
SQL
105 lines
3.8 KiB
SQL
-- Creative pipeline: producción RRSS separada de `creatives` (Ads Meta).
|
|
-- Flujo unificado (ver sql/approval_status_unify.sql):
|
|
-- borrador → revision_agencia → pendiente_aprobacion → aprobada|rechazada → publicada
|
|
-- Cliente solo ve desde pendiente_aprobacion. Idempotente.
|
|
|
|
create table if not exists public.creative_pipeline (
|
|
id uuid primary key default gen_random_uuid(),
|
|
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
|
|
|
-- referencia externa estable usada por los scripts de generación (ej. "oct-04")
|
|
external_ref text,
|
|
|
|
title text not null,
|
|
format text not null default 'post'
|
|
check (format in ('post', 'story', 'reel', 'carrusel')),
|
|
|
|
-- mes/calendario al que pertenece la pieza (agrupación en el board)
|
|
month text, -- 'YYYY-MM'
|
|
|
|
status text not null default 'borrador'
|
|
check (status in (
|
|
'borrador', 'revision_agencia', 'pendiente_aprobacion',
|
|
'aprobada', 'rechazada', 'publicada'
|
|
)),
|
|
|
|
-- portada (todas las piezas tienen una)
|
|
cover_path text,
|
|
cover_mime text,
|
|
|
|
-- piezas carrusel: array ordenado de storage paths de los slides interiores + CTA
|
|
-- ej. ["tenant/.../slide-2.jpg", "tenant/.../slide-3.jpg", ...]
|
|
slide_paths jsonb not null default '[]'::jsonb,
|
|
|
|
caption text,
|
|
notes text,
|
|
|
|
created_by uuid references auth.users(id),
|
|
approved_by uuid references auth.users(id),
|
|
approved_at timestamptz,
|
|
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create unique index if not exists creative_pipeline_tenant_ref_idx
|
|
on public.creative_pipeline (tenant_id, external_ref)
|
|
where external_ref is not null;
|
|
|
|
do $$ begin
|
|
if not exists (
|
|
select 1 from pg_constraint where conname = 'creative_pipeline_tenant_ref_uniq'
|
|
) then
|
|
alter table public.creative_pipeline
|
|
add constraint creative_pipeline_tenant_ref_uniq unique (tenant_id, external_ref);
|
|
end if;
|
|
end $$;
|
|
|
|
create index if not exists creative_pipeline_tenant_month_idx
|
|
on public.creative_pipeline (tenant_id, month, status);
|
|
|
|
alter table public.creative_pipeline enable row level security;
|
|
|
|
-- Lectura: cliente ve las piezas de su propio tenant (incluye revisión interna,
|
|
-- ya que este panel es también para el cliente, no solo para el equipo).
|
|
create policy creative_pipeline_tenant_select on public.creative_pipeline
|
|
for select using (
|
|
tenant_id::text = coalesce(
|
|
auth.jwt() -> 'app_metadata' ->> 'tenant_id',
|
|
auth.jwt() -> 'user_metadata' ->> 'tenant_id'
|
|
)
|
|
);
|
|
|
|
-- Alta/edición de contenido vía API del equipo (service role) — bloqueado para
|
|
-- clientes directos por RLS; el endpoint PATCH permite al cliente mover el
|
|
-- estado a 'aprobado'/'rechazado' únicamente mediante el mismo filtro de tenant.
|
|
create policy creative_pipeline_tenant_insert on public.creative_pipeline
|
|
for insert with check (
|
|
tenant_id::text = coalesce(
|
|
auth.jwt() -> 'app_metadata' ->> 'tenant_id',
|
|
auth.jwt() -> 'user_metadata' ->> 'tenant_id'
|
|
)
|
|
);
|
|
|
|
create policy creative_pipeline_tenant_update on public.creative_pipeline
|
|
for update using (
|
|
tenant_id::text = coalesce(
|
|
auth.jwt() -> 'app_metadata' ->> 'tenant_id',
|
|
auth.jwt() -> 'user_metadata' ->> 'tenant_id'
|
|
)
|
|
);
|
|
|
|
create policy creative_pipeline_tenant_delete on public.creative_pipeline
|
|
for delete using (
|
|
tenant_id::text = coalesce(
|
|
auth.jwt() -> 'app_metadata' ->> 'tenant_id',
|
|
auth.jwt() -> 'user_metadata' ->> 'tenant_id'
|
|
)
|
|
);
|
|
|
|
comment on table public.creative_pipeline is
|
|
'Pipeline interno de producción de creatividades (RRSS), separado de creatives (ads). Mismo bucket tenant-assets, prefijo pipeline/.';
|
|
|
|
-- Storage: reutiliza el bucket privado `tenant-assets` (ver src/lib/assets.ts),
|
|
-- path = {tenant_id}/pipeline/{id}/cover.jpg y {tenant_id}/pipeline/{id}/slide-N.jpg
|