Migración 0004: maestros, perfil (tutor_id, foto_path), bucket avatares, fallback tutor en crear_solicitud

This commit is contained in:
2026-08-24 08:46:39 -07:00
parent 1ce1343882
commit ef40241ade
@@ -0,0 +1,203 @@
-- 0004_perfil_maestros_avatares.sql
-- 3 bloques:
-- A) tabla prestamos.maestros (catalogo de tutores) + RLS
-- B) profiles.tutor_id + profiles.foto_path
-- C) bucket 'avatares' publico con RLS por carpeta = uid
-- + update de crear_solicitud: fallback a nombre del tutor si el
-- alumno deja p_maestro_responsable vacio.
begin;
-- ============================================================
-- A) Tabla de maestros (catalogo admin-managed)
-- ============================================================
create table if not exists prestamos.maestros (
id serial primary key,
nombre text not null unique,
activo boolean not null default true,
created_at timestamptz not null default now()
);
alter table prestamos.maestros enable row level security;
drop policy if exists maestros_read_all on prestamos.maestros;
drop policy if exists maestros_admin_write on prestamos.maestros;
create policy maestros_read_all on prestamos.maestros
for select to authenticated using (true);
create policy maestros_admin_write on prestamos.maestros
for all to authenticated
using (prestamos.is_admin())
with check (prestamos.is_admin());
grant select, insert, update, delete on prestamos.maestros to authenticated, service_role;
grant usage, select on prestamos.maestros_id_seq to authenticated, service_role;
insert into prestamos.maestros (nombre) values
('Sin especificar')
on conflict (nombre) do nothing;
-- ============================================================
-- B) profiles: tutor_id + foto_path
-- ============================================================
alter table prestamos.profiles
add column if not exists tutor_id int references prestamos.maestros(id) on delete set null,
add column if not exists foto_path text;
-- policy profiles_update_self (definida en 0001) usa:
-- with check (id = auth.uid() and rol = (select rol from ...))
-- solo bloquea cambios al rol, permite editar matricula/semestre/tutor_id/foto_path.
-- No requiere cambios.
-- ============================================================
-- C) Bucket 'avatares' publico + policies por carpeta = uid
-- Path pattern esperado: '<uid>/<timestamp>.<ext>'
-- ============================================================
insert into storage.buckets (id, name, public)
values ('avatares', 'avatares', true)
on conflict (id) do update set public = true;
drop policy if exists "avatares_read_all" on storage.objects;
drop policy if exists "avatares_owner_write" on storage.objects;
create policy "avatares_read_all" on storage.objects
for select to anon, authenticated
using (bucket_id = 'avatares');
create policy "avatares_owner_write" on storage.objects
for all to authenticated
using (
bucket_id = 'avatares'
and (storage.foldername(name))[1] = auth.uid()::text
)
with check (
bucket_id = 'avatares'
and (storage.foldername(name))[1] = auth.uid()::text
);
-- ============================================================
-- D) crear_solicitud: si p_maestro_responsable llega vacio,
-- autocompletar con el nombre del tutor guardado en profiles.
-- Si el usuario tampoco tiene tutor, sigue el raise
-- 'maestro_responsable_requerido' original -> el checkout ya
-- guarda contra ese caso (perfil incompleto).
-- ============================================================
create or replace function prestamos.crear_solicitud(
p_maestro_responsable text,
p_notas text,
p_items jsonb
) returns bigint
language plpgsql
security definer
set search_path = prestamos, pg_temp
as $$
declare
v_alumno uuid := auth.uid();
v_solicitud_id bigint;
v_item jsonb;
v_material_id int;
v_cantidad int;
v_descripcion text;
v_disponible int;
v_estado_mat text;
v_trackeado boolean;
v_unidad_id bigint;
v_asignadas int;
v_maestro text;
begin
if v_alumno is null then
raise exception 'no_autenticado';
end if;
v_maestro := nullif(trim(coalesce(p_maestro_responsable, '')), '');
-- fallback al tutor del perfil si el alumno no escribio nada
if v_maestro is null then
select m.nombre into v_maestro
from prestamos.profiles p
join prestamos.maestros m on m.id = p.tutor_id
where p.id = v_alumno;
end if;
if v_maestro is null then
raise exception 'maestro_responsable_requerido';
end if;
if p_items is null or jsonb_typeof(p_items) <> 'array' or jsonb_array_length(p_items) = 0 then
raise exception 'items_requeridos';
end if;
for v_material_id in
select distinct (elem->>'material_id')::int
from jsonb_array_elements(p_items) elem
order by 1
loop
perform 1 from prestamos.materiales where id = v_material_id for update;
end loop;
insert into prestamos.solicitudes (alumno_id, estado, maestro_responsable, notas)
values (v_alumno, 'pendiente', v_maestro, nullif(trim(coalesce(p_notas, '')), ''))
returning id into v_solicitud_id;
for v_item in select * from jsonb_array_elements(p_items)
loop
v_material_id := (v_item->>'material_id')::int;
v_cantidad := (v_item->>'cantidad')::int;
v_descripcion := nullif(trim(coalesce(v_item->>'descripcion', '')), '');
if v_material_id is null or v_cantidad is null or v_cantidad <= 0 then
raise exception 'item_invalido';
end if;
select cantidad_disponible, estado, trackeado_por_unidad
into v_disponible, v_estado_mat, v_trackeado
from prestamos.materiales where id = v_material_id;
if v_estado_mat is null then
raise exception 'material_no_existe: %', v_material_id;
end if;
if v_estado_mat <> 'disponible' then
raise exception 'material_no_disponible: %', v_material_id;
end if;
if v_disponible < v_cantidad then
raise exception 'stock_insuficiente: %', v_material_id;
end if;
if v_trackeado then
v_asignadas := 0;
while v_asignadas < v_cantidad loop
select id into v_unidad_id
from prestamos.material_unidades
where material_id = v_material_id and estado = 'disponible'
order by id
for update skip locked
limit 1;
if v_unidad_id is null then
raise exception 'sin_unidad_disponible: %', v_material_id;
end if;
insert into prestamos.solicitud_items
(solicitud_id, material_id, cantidad, descripcion, material_unidad_id)
values (v_solicitud_id, v_material_id, 1, v_descripcion, v_unidad_id);
update prestamos.material_unidades
set estado = 'prestado'
where id = v_unidad_id;
v_asignadas := v_asignadas + 1;
end loop;
else
insert into prestamos.solicitud_items (solicitud_id, material_id, cantidad, descripcion)
values (v_solicitud_id, v_material_id, v_cantidad, v_descripcion);
end if;
end loop;
return v_solicitud_id;
end;
$$;
grant execute on function prestamos.crear_solicitud(text, text, jsonb) to authenticated;
commit;