From 44fe7527beb8fac8bb9d9ac68183291deddd00e7 Mon Sep 17 00:00:00 2001 From: Lak-G Date: Tue, 25 Aug 2026 11:37:26 -0700 Subject: [PATCH] =?UTF-8?q?Migraci=C3=B3n=200006=20v1.6:=20rol=20docente,?= =?UTF-8?q?=20maestros.es=5Ftutor,=20RPC=20crear=5Fsolicitud=20con=20maest?= =?UTF-8?q?ro=5Fid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0006_docente_maestros_v16.sql | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 supabase/migrations/0006_docente_maestros_v16.sql diff --git a/supabase/migrations/0006_docente_maestros_v16.sql b/supabase/migrations/0006_docente_maestros_v16.sql new file mode 100644 index 0000000..e79ca8e --- /dev/null +++ b/supabase/migrations/0006_docente_maestros_v16.sql @@ -0,0 +1,174 @@ +-- 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;