-- ============================================================================ -- TABLERO CDC — PERMISO INDIVIDUAL PARA "HORAS DEL EQUIPO" -- Migración aditiva y segura. -- -- Objetivo: -- 1) Agregar tablero_cdc_allowed_users.can_view_team_hours. -- 2) Mantener el acceso actual de los usuarios activos existentes la primera vez. -- 3) Dejar nuevos usuarios SIN este permiso por defecto. -- 4) Proteger los RPC usados por el módulo Horas del equipo. -- -- No modifica proyectos, horas, histórico, métricas, tarifarios, listas ni briefs. -- ============================================================================ begin; -- Se ejecuta de forma idempotente. Solo en la PRIMERA instalación del permiso -- se conserva el comportamiento actual dando acceso a los usuarios activos existentes. do $$ declare permission_was_missing boolean; begin select not exists ( select 1 from information_schema.columns where table_schema = 'public' and table_name = 'tablero_cdc_allowed_users' and column_name = 'can_view_team_hours' ) into permission_was_missing; if permission_was_missing then alter table public.tablero_cdc_allowed_users add column can_view_team_hours boolean not null default false; update public.tablero_cdc_allowed_users set can_view_team_hours = true, updated_at = now() where is_active = true; end if; end $$; comment on column public.tablero_cdc_allowed_users.can_view_team_hours is 'Controla si el usuario puede ver y consultar el módulo Horas del equipo. Los usuarios nuevos quedan sin acceso hasta que se les habilite explícitamente.'; -- Función central de permiso. Se usa desde el frontend y desde los RPC de datos. create or replace function public.tablero_cdc_current_user_can_view_team_hours() 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_view_team_hours = true ); $$; revoke all on function public.tablero_cdc_current_user_can_view_team_hours() from public; grant execute on function public.tablero_cdc_current_user_can_view_team_hours() to authenticated; -- Roster de equipo: solo usuarios con el permiso pueden consultarlo. create or replace function public.tablero_cdc_get_team_hours_roster() returns table ( email text, full_name text ) language plpgsql stable security definer set search_path = public as $$ begin if not public.tablero_cdc_current_user_can_view_team_hours() then raise exception 'No autorizado'; end if; return query select lower(trim(u.email)) as email, coalesce(nullif(trim(u.full_name), ''), lower(trim(u.email))) as full_name from public.tablero_cdc_allowed_users u where u.is_active = true order by coalesce(nullif(trim(u.full_name), ''), lower(trim(u.email))); end; $$; revoke all on function public.tablero_cdc_get_team_hours_roster() from public; grant execute on function public.tablero_cdc_get_team_hours_roster() to authenticated; -- Lectura paginada de horas actuales + histórico. El acceso se valida en servidor, -- de modo que ocultar el botón no sea el único control del módulo. create or replace function public.tablero_cdc_get_team_hours_records( p_from date default null, p_to date default null, p_limit integer default 1000, p_offset integer default 0 ) returns table ( id uuid, record_source text, source_key text, work_date date, project_name text, client text, country text, worked_by_name text, worked_by_email text, task_name text, duration_minutes integer, notes text ) language plpgsql stable security definer set search_path = public as $$ begin if not public.tablero_cdc_current_user_can_view_team_hours() then raise exception 'No autorizado'; end if; return query select c.id, c.record_source, c.source_key, c.work_date, c.project_name, c.client, c.country, c.worked_by_name, c.worked_by_email, c.task_name, c.duration_minutes, c.notes from public.tablero_cdc_time_combined c where (p_from is null or c.work_date >= p_from) and (p_to is null or c.work_date <= p_to) order by c.work_date desc, c.id asc limit least(greatest(coalesce(p_limit, 1000), 1), 1000) offset greatest(coalesce(p_offset, 0), 0); end; $$; revoke all on function public.tablero_cdc_get_team_hours_records(date, date, integer, integer) from public; grant execute on function public.tablero_cdc_get_team_hours_records(date, date, integer, integer) to authenticated; commit; -- ============================================================================ -- ADMINISTRACIÓN DEL PERMISO -- ============================================================================ -- Dar acceso: -- update public.tablero_cdc_allowed_users -- set can_view_team_hours = true, updated_at = now() -- where lower(email) = lower('correo@gomezleemarketing.com'); -- -- Quitar acceso: -- update public.tablero_cdc_allowed_users -- set can_view_team_hours = false, updated_at = now() -- where lower(email) = lower('correo@gomezleemarketing.com'); -- -- Ver estado actual: select email, full_name, role, is_active, can_view_team_hours from public.tablero_cdc_allowed_users order by full_name nulls last, email;