-- ========================================================= -- TABLERO CDC - TIEMPO DEDICADO POR PROYECTO -- Ejecutar una sola vez en Supabase SQL Editor antes de desplegar -- la versión que incluye el botón "Tiempo". -- -- Objetivo: -- - Registrar tiempo dedicado a un proyecto. -- - Desglosar ese tiempo por país y por tarea. -- - Conservar fecha, notas y quién registró cada entrada. -- - No modifica tablero_cdc_projects ni altera tarifarios, montos, -- Google Sheets, n8n o datos históricos existentes. -- ========================================================= begin; create table if not exists public.tablero_cdc_project_time_entries ( id uuid primary key default gen_random_uuid(), project_id uuid not null references public.tablero_cdc_projects(id) on delete cascade, country text not null, task_name text not null, duration_minutes integer not null check (duration_minutes > 0), work_date date not null default current_date, notes text not null default '', created_by uuid references auth.users(id), created_by_email text, created_by_name text, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint tablero_cdc_project_time_entries_country_not_blank check (length(trim(country)) > 0), constraint tablero_cdc_project_time_entries_task_not_blank check (length(trim(task_name)) > 0) ); comment on table public.tablero_cdc_project_time_entries is 'Registros de tiempo dedicado a proyectos CDC, desglosados por país y tarea.'; comment on column public.tablero_cdc_project_time_entries.duration_minutes is 'Duración exacta almacenada en minutos para evitar errores de redondeo.'; create index if not exists tablero_cdc_project_time_entries_project_date_idx on public.tablero_cdc_project_time_entries(project_id, work_date desc, created_at desc); create index if not exists tablero_cdc_project_time_entries_project_country_idx on public.tablero_cdc_project_time_entries(project_id, country); create index if not exists tablero_cdc_project_time_entries_project_task_idx on public.tablero_cdc_project_time_entries(project_id, task_name); create or replace function public.set_tablero_cdc_project_time_entries_audit() returns trigger language plpgsql security invoker set search_path = public as $$ begin new.country = trim(new.country); new.task_name = trim(new.task_name); new.notes = coalesce(trim(new.notes), ''); new.updated_at = now(); if tg_op = 'INSERT' then -- La identidad se toma de la sesión autenticada, no de valores enviados por el cliente. new.created_by = auth.uid(); new.created_by_email = coalesce( nullif(lower(trim(coalesce(auth.jwt() ->> 'email', ''))), ''), nullif(lower(trim(coalesce(new.created_by_email, ''))), '') ); new.created_by_name = coalesce( nullif(trim(coalesce(auth.jwt() -> 'user_metadata' ->> 'full_name', '')), ''), nullif(trim(coalesce(auth.jwt() -> 'user_metadata' ->> 'name', '')), ''), nullif(trim(coalesce(new.created_by_name, '')), ''), new.created_by_email, 'Usuario CDC' ); end if; return new; end; $$; drop trigger if exists trg_tablero_cdc_project_time_entries_audit on public.tablero_cdc_project_time_entries; create trigger trg_tablero_cdc_project_time_entries_audit before insert or update on public.tablero_cdc_project_time_entries for each row execute function public.set_tablero_cdc_project_time_entries_audit(); alter table public.tablero_cdc_project_time_entries enable row level security; drop policy if exists "tablero_cdc_project_time_entries_select_allowed" on public.tablero_cdc_project_time_entries; drop policy if exists "tablero_cdc_project_time_entries_insert_allowed" on public.tablero_cdc_project_time_entries; drop policy if exists "tablero_cdc_project_time_entries_update_allowed" on public.tablero_cdc_project_time_entries; drop policy if exists "tablero_cdc_project_time_entries_delete_allowed" on public.tablero_cdc_project_time_entries; -- Se valida contra la misma tabla central de acceso del Tablero CDC. -- Así, una sesión Google autenticada pero NO autorizada para el Tablero -- tampoco puede leer o escribir tiempos mediante la API de Supabase. create policy "tablero_cdc_project_time_entries_select_allowed" on public.tablero_cdc_project_time_entries for select to authenticated using ( 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 ) ); create policy "tablero_cdc_project_time_entries_insert_allowed" on public.tablero_cdc_project_time_entries for insert to authenticated with check ( 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 ) ); create policy "tablero_cdc_project_time_entries_update_allowed" on public.tablero_cdc_project_time_entries for update to authenticated using ( 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 ) ) with check ( 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 ) ); create policy "tablero_cdc_project_time_entries_delete_allowed" on public.tablero_cdc_project_time_entries for delete to authenticated using ( 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 ) ); grant select, insert, update, delete on public.tablero_cdc_project_time_entries to authenticated; -- Realtime no es obligatorio para el funcionamiento, pero se habilita -- para que el módulo quede preparado para sincronización multiusuario. do $$ begin if not exists ( select 1 from pg_publication_tables where pubname = 'supabase_realtime' and schemaname = 'public' and tablename = 'tablero_cdc_project_time_entries' ) then alter publication supabase_realtime add table public.tablero_cdc_project_time_entries; end if; end $$; commit; -- ========================================================= -- VERIFICACIÓN -- Debe devolver la tabla y 0 registros si todavía no se ha agregado tiempo. -- ========================================================= select count(*) as registros_de_tiempo, coalesce(sum(duration_minutes), 0) as minutos_totales from public.tablero_cdc_project_time_entries;