Files
labre-web/supabase/migrations/0006_docente_maestros_v16.sql
T

175 lines
5.8 KiB
PL/PgSQL

-- 0006_docente_maestros_v16.sql
-- v1.6:
-- A) profiles.rol acepta 'docente' (alumno|docente|admin)
-- B) maestros.es_tutor bool (subset de maestros que son elegibles como tutor)
-- C) RPC crear_solicitud reescrita: recibe p_maestro_id int; resuelve nombre
-- y lo guarda en maestro_responsable (snapshot). Docente no requiere
-- maestro (se guarda su propio nombre como responsable). Fallback al
-- tutor guardado si p_maestro_id es null y el user es alumno.
begin;
-- A) Rol docente ----------------------------------------------------------
alter table prestamos.profiles drop constraint if exists profiles_rol_check;
alter table prestamos.profiles
add constraint profiles_rol_check check (rol in ('alumno','docente','admin'));
-- B) Flag es_tutor --------------------------------------------------------
alter table prestamos.maestros
add column if not exists es_tutor boolean not null default false;
-- 'Sin especificar' queda como fallback de tutor para no romper flows
-- previos (no es un maestro real; se puede desmarcar despues desde /admin/maestros).
update prestamos.maestros set es_tutor = true where nombre = 'Sin especificar';
-- Self-heal policy insert amplia el check a los 3 roles no-admin.
-- Sigue prohibiendo auto-promocion a admin.
drop policy if exists profiles_insert_self on prestamos.profiles;
create policy profiles_insert_self on prestamos.profiles
for insert to authenticated
with check (id = auth.uid() and rol in ('alumno','docente'));
-- C) RPC crear_solicitud v2: recibe maestro_id ----------------------------
-- Cambio de firma: antes (text, text, jsonb) -> ahora (int, text, jsonb).
-- Drop de la anterior para no dejar overload muerto.
drop function if exists prestamos.crear_solicitud(text, text, jsonb);
create or replace function prestamos.crear_solicitud(
p_maestro_id int,
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;
v_rol text;
v_nombre_propio text;
v_tutor_id int;
begin
if v_alumno is null then
raise exception 'no_autenticado';
end if;
select rol, nombre, tutor_id
into v_rol, v_nombre_propio, v_tutor_id
from prestamos.profiles where id = v_alumno;
if v_rol is null then
raise exception 'perfil_no_existe';
end if;
if v_rol = 'docente' then
-- Docente: no elige maestro, se registra su propio nombre como responsable.
v_maestro := coalesce(nullif(trim(v_nombre_propio), ''), '(Docente)');
elsif p_maestro_id is not null then
select nombre into v_maestro
from prestamos.maestros
where id = p_maestro_id and activo;
if v_maestro is null then
raise exception 'maestro_no_valido';
end if;
else
-- Alumno sin selección: cae al tutor guardado.
if v_tutor_id is null then
raise exception 'maestro_responsable_requerido';
end if;
select nombre into v_maestro
from prestamos.maestros where id = v_tutor_id;
if v_maestro is null then
raise exception 'tutor_no_valido';
end if;
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;
-- Lock por orden ascendente de material_id, evita deadlocks entre alumnos
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(int, text, jsonb) to authenticated;
commit;