Rediseño: vale de préstamo multi-ítem, fiel al formulario de papel
El vale físico del LSC permite pedir varios materiales en un solo trámite; el sistema modelaba 1 solicitud = 1 material. Migra prestamos.prestamos -> solicitudes (cabecera) + solicitud_items (renglones), vía RENAME + backfill (preserva ids/historial real). - RPC prestamos.crear_solicitud: transaccional, lockea materiales por fila, arregla la race condition del insert directo anterior. El alumno ya no inserta directo (RLS lo bloquea). - sync_stock/log_estado reescritos para iterar renglones por vale. - maestro_responsable (por vale) y profiles.semestre (perfil, nullable, sin UI todavía — onboarding queda para después). - Alumno: carrito (SolicitudCart/AgregarMaterial) reemplaza el modal de solicitud único por material. - Admin: 3 vistas de solicitudes, reportes y CSV export listan renglones por vale; api/admin/prestamos -> api/admin/solicitudes. De paso corrige un bug preexistente en detalles.ts (audit_log nunca se ordenaba por la columna correcta). Verificado: build limpio, ciclo RPC+triggers probado en una transacción revertida en prod (sin residuo), 9 vistas SSR smoke-testeadas contra el schema real. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,22 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
type Item = {
|
||||
cantidad: number;
|
||||
descripcion: string | null;
|
||||
material: { nombre: string; numero_inventario: string | null } | null;
|
||||
};
|
||||
type Detalles = {
|
||||
prestamo: {
|
||||
id: number;
|
||||
cantidad: number;
|
||||
estado: string;
|
||||
fecha_solicitud: string;
|
||||
fecha_aprobacion: string | null;
|
||||
fecha_devolucion_estimada: string | null;
|
||||
fecha_devolucion_real: string | null;
|
||||
notas: string | null;
|
||||
maestro_responsable: string;
|
||||
alumno: { nombre: string | null; email: string; matricula: string | null } | null;
|
||||
material: { nombre: string; numero_inventario: string | null } | null;
|
||||
items: Item[];
|
||||
};
|
||||
audit_log: Array<Record<string, any>>;
|
||||
};
|
||||
@@ -38,7 +43,7 @@ export default function VerDetalles({ prestamoId }: { prestamoId: number }) {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await fetch(`/api/admin/prestamos/${prestamoId}/detalles`);
|
||||
const res = await fetch(`/api/admin/solicitudes/${prestamoId}/detalles`);
|
||||
if (!res.ok) throw new Error(`Error ${res.status}`);
|
||||
setData(await res.json());
|
||||
} catch (err: any) {
|
||||
@@ -74,10 +79,20 @@ export default function VerDetalles({ prestamoId }: { prestamoId: number }) {
|
||||
<dl className="mt-4 grid grid-cols-3 gap-y-2 text-sm">
|
||||
<dt className="opacity-70">Alumno</dt>
|
||||
<dd className="col-span-2">{data.prestamo.alumno?.nombre ?? data.prestamo.alumno?.email}</dd>
|
||||
<dt className="opacity-70">Material</dt>
|
||||
<dd className="col-span-2">{data.prestamo.material?.nombre} <span className="opacity-70">· Inv. {data.prestamo.material?.numero_inventario ?? '—'}</span></dd>
|
||||
<dt className="opacity-70">Cantidad</dt>
|
||||
<dd className="col-span-2" style={{ fontVariantNumeric: 'tabular-nums' }}>{data.prestamo.cantidad}</dd>
|
||||
<dt className="opacity-70">Maestro</dt>
|
||||
<dd className="col-span-2">{data.prestamo.maestro_responsable}</dd>
|
||||
<dt className="opacity-70">Materiales</dt>
|
||||
<dd className="col-span-2">
|
||||
<ul className="list-disc pl-4">
|
||||
{data.prestamo.items.map((i, idx) => (
|
||||
<li key={idx}>
|
||||
<strong style={{ fontVariantNumeric: 'tabular-nums' }}>{i.cantidad}</strong>× {i.material?.nombre ?? '—'}{' '}
|
||||
<span className="opacity-70">· Inv. {i.material?.numero_inventario ?? '—'}</span>
|
||||
{i.descripcion && <div className="text-xs opacity-70">{i.descripcion}</div>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</dd>
|
||||
<dt className="opacity-70">Solicitado</dt>
|
||||
<dd className="col-span-2">{fmt.format(new Date(data.prestamo.fecha_solicitud))}</dd>
|
||||
{data.prestamo.fecha_aprobacion && (<><dt className="opacity-70">Aprobado</dt><dd className="col-span-2">{fmt.format(new Date(data.prestamo.fecha_aprobacion))}</dd></>)}
|
||||
@@ -91,7 +106,7 @@ export default function VerDetalles({ prestamoId }: { prestamoId: number }) {
|
||||
) : (
|
||||
<ul className="text-sm divide-y" style={{ borderColor: 'color-mix(in oklab, var(--color-ink) 10%, transparent)' }}>
|
||||
{data.audit_log.map((e, i) => {
|
||||
const fecha = e.created_at ?? e.fecha ?? e.timestamp;
|
||||
const fecha = e.at ?? e.created_at ?? e.fecha ?? e.timestamp;
|
||||
return (
|
||||
<li key={i} className="py-2 flex items-baseline gap-2 flex-wrap">
|
||||
<span className="font-medium">{e.accion ?? 'cambio'}</span>
|
||||
|
||||
Reference in New Issue
Block a user