-- 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;