Solicitud enviadaMultímetro digital · pendiente de aprobación
",
+ "css": ".ds-toast { display: flex; align-items: flex-start; gap: 12px; border-radius: 16px; padding: 12px 16px; background: rgba(255,255,255,0.85); backdrop-filter: blur(20px); box-shadow: 0 16px 40px -12px rgba(0,0,0,0.25); border: 1px solid color-mix(in oklab, #2D3748 10%, transparent); color: #2D3748; max-width: 26rem; } .ds-toast-icon { flex-shrink: 0; display: grid; place-items: center; width: 36px; height: 36px; border-radius: 9999px; background: #00723F; color: #ffffff; } .ds-toast-title { display: block; font-weight: 600; font-size: 0.875rem; } .ds-toast-desc { display: block; font-size: 0.75rem; opacity: 0.7; margin-top: 2px; }"
+ }
+ ],
+ "narrative": {
+ "northStar": "La Bitácora Digital",
+ "overview": "LabPréstamos is the printed loan sheet and the Excel logbook it used to feed, collapsed into one live surface. Every visual decision defends that lineage: nothing decorative, nothing that would feel out of place next to an official UABC form, but nothing that feels like a cold spreadsheet either. The system is cálido pero confiable — warm but reliable — and it earns the \"warm\" half narrowly: a gold accent, soft rounded corners, and gentle motion sit on top of an otherwise flat, administrative base.\n\nThe page itself stays flat and quiet — white cards on a pale green-grey surface, hairline borders instead of shadows, tabular numbers lined up like a real ledger. Depth is reserved for the two things that are genuinely floating above the page: the toast notifications and the mobile navigation dock. That asymmetry is deliberate — it is how the system tells the eye \"this is the document\" from \"this is the chrome.\"\n\nTwo audiences read this bitácora differently. The alumno's default device is the phone — the catalog, the request modal, the pill-shaped floating dock are all tuned for thumbs. The admin's default device is the desktop — dense tables, a persistent sidebar, KPI tiles. Neither view degrades on the other's device; every screen ships both a table and a card-list rendering.",
+ "keyCharacteristics": [
+ "Flat administrative base (white cards, hairline ink-tinted borders, no ambient shadow)",
+ "Shadow appears only on floating overlay chrome — the mobile dock and toasts — never on the page plane",
+ "Every status is a tinted pill: 12–15% color wash background, full-strength text",
+ "Tabular numbers everywhere a quantity, date, or stock figure appears",
+ "One typeface (Inter Variable) carrying every role through weight and size alone",
+ "Motion is a courtesy, not a performance: motion-safe:-gated, specific-property transitions, no transition-all"
+ ],
+ "rules": [
+ { "name": "The Ink-Mix Border Rule", "body": "Every hairline border and divider is color-mix(in oklab, var(--color-tinta) N%, transparent), never a literal gray hex. A border is always \"ink, diluted,\" so it stays coherent with body text at any opacity.", "section": "colors" },
+ { "name": "The Vencido Exception", "body": "Every status pill uses the tinted-fill pattern except an overdue loan (\"Vencido\"), which gets a solid peligro fill with white text — on both the table badge and, on mobile, a light danger wash across the entire card background. This is the system's one permitted alarm.", "section": "colors" },
+ { "name": "The Tabular Numbers Rule", "body": "Any figure that represents a count — cantidad, stock (disponible/total), KPI values, dates — is rendered with font-variant-numeric: tabular-nums. Numbers must align like a ledger column.", "section": "typography" },
+ { "name": "The Table/Card Swap Rule", "body": "Every data-dense admin view ships two complete renderings of the same data: a table for md:block and a card list for md:hidden. This is a structural swap, not a responsive squeeze.", "section": "layout" },
+ { "name": "The Flat Page, Floating Chrome Rule", "body": "If an element is part of the document (a card, a table, a form), it stays flat with a hairline border. If an element floats above the document regardless of scroll (the dock, a toast), it gets a soft ambient shadow plus backdrop-blur.", "section": "elevation" }
+ ],
+ "dos": [
+ "Do use color-mix(in oklab, var(--color-tinta) N%, transparent) for any new border or divider — never a literal gray.",
+ "Do keep shadow exclusive to floating overlay chrome (toasts, the mobile dock).",
+ "Do use the tinted-pill pattern (12–15% background, full-strength text) for any new status or state indicator.",
+ "Do apply font-variant-numeric: tabular-nums to any new numeric quantity, stock figure, or date.",
+ "Do ship both a md:block table and a md:hidden card-list rendering for any new data-dense admin view.",
+ "Do gate all motion with motion-safe: and animate specific properties, never transition-all.",
+ "Do give every interactive control a 44px minimum tap target and touch-action: manipulation."
+ ],
+ "donts": [
+ "Don't add a shadow to .card, .btn, or .input — the system's flatness on the page plane is deliberate.",
+ "Don't introduce a second typeface. Inter Variable carries every role through weight and size.",
+ "Don't give a status pill a solid saturated fill — the tinted wash is the rule, and Vencido is the one confirmed exception, not a precedent to extend.",
+ "Don't reach for rounded-2xl outside the login mark and toasts; it is reserved for \"first impression / interruption\" moments.",
+ "Don't replace the mobile bottom dock with a hamburger drawer, or the desktop sidebar with a top bar — the device-appropriate nav pattern per breakpoint is deliberate."
+ ]
+ }
+}
diff --git a/AGENTS.md b/AGENTS.md
index 6b33a3e..885b383 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -154,3 +154,17 @@ Fases 3, 4/5 y 6 tocan carpetas de rutas distintas (`src/pages/alumno/*`, `src/p
- Confirmado en prod por el usuario: "Marcar devuelto" ahora funciona. El mismo fix cubre aprobar/rechazar y cualquier otro POST admin que hubiera fallado con el mismo síntoma.
- Limpieza: removido `/api/whoami` (endpoint diagnóstico) y el bloque `debug` que devolvía email+rol en el 403 del endpoint devolver. Ya no aplican.
- Aprendizaje: cuando un endpoint responde 403 con **texto plano** ("Cross-site POST..."), es Astro; cuando responde 403 con **JSON** (`{"error":"no autorizado"}`), es lógica del endpoint. Ese matiz habría acelerado el diagnóstico.
+
+- **2026-08-16 — Auditoría `/impeccable`: "quita el sloop de IA y reforma el diseño"**.
+ - Se generaron `PRODUCT.md` y `DESIGN.md` (skill impeccable) documentando el sistema de diseño ya implementado ("La Bitácora Digital"). Veredicto tras leer todo el código y capturar screenshots reales (login, catálogo, panel admin, solicitudes, inventario, reportes, mobile) vía un bypass temporal de middleware solo-DEV (`?preview=alumno|admin`, revertido al terminar): el sistema **no** tenía slop visual genérico típico de IA (sin gradientes, sin glassmorphism, sin morado/azul SaaS, sin emojis) — el trabajo previo ya era disciplinado. El slop real encontrado era puntual:
+ 1. El favicon era el cohete default de Astro (boilerplate del scaffold, nunca reemplazado).
+ 2. El "logo" era un cuadrado con la letra "L" — el placeholder más genérico posible, repetido en sidebar, header mobile y login sin ningún glifo propio detrás.
+ 3. `/admin` para no-admins devolvía texto plano sin estilo (`new Response('Acceso denegado', {status:403})`), rompiendo el sistema de diseño.
+ 4. No existía página 404 propia (caía al default de Astro).
+ 5. Íconos "fake": los botones de cerrar modal usaban el carácter `×`, el estado vacío de solicitudes usaba `✓`, el chevron de historial usaba `›` — inconsistente con el resto del sistema que sí dibuja íconos SVG reales (heroicons-outline).
+ 6. Código muerto/inconsistente: `EliminarMaterial` y `RechazarDialog` cambiaban color en hover vía `onMouseEnter/onMouseLeave` en JS (con una custom property `--h` sin usar) en vez de CSS, y el dropdown de `Autocomplete` usaba `shadow-lg` de Tailwind en vez de respetar la regla "sin sombra fuera de dock/toast" del sistema.
+ - Fix aplicado: nuevo glifo de marca propio (`src/components/BrandMark.astro`) — una ficha/etiqueta con perforación, evoca el ticket de préstamo físico — reemplaza la "L" en sidebar/header/login y es ahora también el favicon (`public/favicon.svg` + `.ico` regenerado). Es un mark de producto, no un intento de escudo institucional UABC (ese sigue pendiente y sin fabricar, por instrucción explícita). Se unificó el color del chip del mark (antes dorado en nav vs. verde en login) a wash blanco/verde consistente. Se agregaron `src/pages/403.astro` y `src/pages/404.astro` con el mismo lenguaje visual del login; el middleware ahora usa `context.rewrite('/403')` en vez de devolver texto plano. Los `×`/`✓`/`›` se reemplazaron por SVG inline consistentes con el stroke-style existente. Se limpió el hover JS→CSS y el `shadow-lg` del autocomplete.
+ - Documentación actualizada: `PRODUCT.md` (Brand Commitments ya no dice "100% tipográfica sin logo"; aclara que el BrandMark es mark de producto, no escudo oficial) y `DESIGN.md` (nueva subsección "Brand Mark" en Components, nueva regla Do/Don't: "todo ícono es SVG dibujado, nunca un carácter unicode").
+ - Verificación: `npm run build` limpio, detector mecánico de impeccable (`detect.mjs`) sin hallazgos sobre los 15 archivos tocados, screenshots antes/después confirmando visualmente cada fix (incluyendo hover de "Eliminar" y el toggle del historial).
+ - No se tocó IA de información (paneles Panel vs. Estadísticas se solapan en KPIs pero no se reestructuraron — fuera de alcance de "quitar slop"), ni se generó contenido de relleno para las páginas con poco dato real (1 material de seed) — eso es estado temprano de datos, no slop de diseño.
+ - Estado: cambios visuales completos y verificados. Servidor de dev quedó corriendo en background (`astro dev status`/`astro dev logs`/`astro dev stop` para gestionarlo). Pendiente: deploy a producción cuando el usuario lo confirme.
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..d8026fd
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,226 @@
+---
+name: LabPréstamos
+description: Sistema institucional UABC para préstamo de material de laboratorio — la bitácora de papel y Excel, digitalizada.
+colors:
+ verde-institucional: "#00723F"
+ verde-institucional-hover: "#005f34"
+ dorado-uabc: "#DD971A"
+ dorado-uabc-hover: "#c78614"
+ superficie: "#F4F7F5"
+ tinta: "#2D3748"
+ peligro: "#C53030"
+typography:
+ headline:
+ fontFamily: "Inter Variable, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
+ fontSize: "1.5rem (2xl) → 1.875rem (3xl) md+"
+ fontWeight: 600
+ lineHeight: 1.25
+ title:
+ fontFamily: "Inter Variable, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
+ fontSize: "1.125rem"
+ fontWeight: 600
+ lineHeight: 1.4
+ body:
+ fontFamily: "Inter Variable, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
+ fontSize: "0.875rem"
+ fontWeight: 400
+ lineHeight: 1.5
+ label:
+ fontFamily: "Inter Variable, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
+ fontSize: "0.875rem"
+ fontWeight: 500
+ lineHeight: 1.3
+ caption:
+ fontFamily: "Inter Variable, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
+ fontSize: "0.75rem"
+ fontWeight: 400
+ lineHeight: 1.4
+rounded:
+ control: "0.5rem"
+ emphasis: "1rem"
+ pill: "9999px"
+spacing:
+ tight: "0.5rem"
+ base: "1rem"
+ loose: "1.5rem"
+ section: "2rem"
+components:
+ button-primary:
+ backgroundColor: "{colors.verde-institucional}"
+ textColor: "#ffffff"
+ rounded: "{rounded.control}"
+ padding: "10px 16px"
+ button-secondary:
+ backgroundColor: "{colors.dorado-uabc}"
+ textColor: "#ffffff"
+ rounded: "{rounded.control}"
+ padding: "10px 16px"
+ button-ghost:
+ backgroundColor: "transparent"
+ textColor: "{colors.tinta}"
+ rounded: "{rounded.control}"
+ padding: "10px 16px"
+ card:
+ backgroundColor: "#ffffff"
+ textColor: "{colors.tinta}"
+ rounded: "{rounded.control}"
+ padding: "24px"
+ input:
+ backgroundColor: "#ffffff"
+ textColor: "{colors.tinta}"
+ rounded: "{rounded.control}"
+ padding: "10px 12px"
+---
+
+# Design System: LabPréstamos
+
+## Overview
+
+**Creative North Star: "La Bitácora Digital"**
+
+LabPréstamos is the printed loan sheet and the Excel logbook it used to feed, collapsed into one live surface. Every visual decision defends that lineage: nothing decorative, nothing that would feel out of place next to an official UABC form, but nothing that feels like a cold spreadsheet either. The system is **cálido pero confiable** — warm but reliable — and it earns the "warm" half narrowly: a gold accent, soft rounded corners, and gentle motion sit on top of an otherwise flat, administrative base.
+
+The page itself stays flat and quiet — white cards on a pale green-grey surface, hairline borders instead of shadows, tabular numbers lined up like a real ledger. Depth is reserved for the two things that are genuinely *floating above* the page: the toast notifications and the mobile navigation dock. That asymmetry is deliberate — it is how the system tells the eye "this is the document" from "this is the chrome."
+
+Two audiences read this bitácora differently. The alumno's default device is the phone — the catalog, the request modal, the pill-shaped floating dock are all tuned for thumbs. The admin's default device is the desktop — dense tables, a persistent sidebar, KPI tiles. Neither view degrades on the other's device; every screen ships both a table and a card-list rendering.
+
+**Key Characteristics:**
+- Flat administrative base (white cards, hairline ink-tinted borders, no ambient shadow)
+- Shadow appears only on floating overlay chrome — the mobile dock and toasts — never on the page plane
+- Every status is a tinted pill: 12–15% color wash background, full-strength text
+- Tabular numbers everywhere a quantity, date, or stock figure appears
+- One typeface (Inter Variable) carrying every role through weight and size alone
+- Motion is a courtesy, not a performance: `motion-safe:`-gated, specific-property transitions, no `transition-all`
+
+## Colors
+
+Two institutional hues carry the whole palette under a strict 60/30/10 split — green dominant, gold as a deliberately rare accent, everything else neutral ink and white.
+
+### Primary
+- **Verde Institucional** (`#00723F`): the UABC green. Primary buttons, the desktop sidebar's entire background, active nav states, focus rings, "healthy/positive" status pills (disponible, aprobado, devuelto), the login mark. This is the color of the system's authority — it appears on anything that confirms, approves, or orients.
+
+### Secondary
+- **Dorado UABC** (`#DD971A`): the institutional gold. Used sparingly and only as an accent — secondary buttons, the "en préstamo" KPI tile, "mantenimiento" status pills, the logo-mark square on the sidebar/mobile header. Never the dominant color of a screen.
+
+### Neutral
+- **Superficie** (`#F4F7F5`): the page background — a near-white green-grey, not pure white. Everything else (cards, dialogs, the desktop sidebar's text, dock) sits on top of it.
+- **Tinta** (`#2D3748`): body text, and — critically — the **source color for every border in the system**. Borders are never a fixed gray; they're `color-mix(in oklab, var(--color-ink) N%, transparent)` at 8–20% depending on prominence. This ties every hairline to the same ink token instead of a separate gray scale.
+- **Peligro** (`#C53030`): errors, destructive actions, and the one status that is allowed to break the tinted-pill convention (see Named Rules).
+
+### Named Rules
+**The Ink-Mix Border Rule.** Every hairline border and divider is `color-mix(in oklab, var(--color-tinta) N%, transparent)`, never a literal gray hex. A border is always "ink, diluted," so it stays coherent with body text at any opacity.
+
+**The Vencido Exception.** Every status pill uses the tinted-fill pattern (see Components → Badges) *except* an overdue loan ("Vencido"), which gets a solid `peligro` fill with white text — on both the table badge and, on mobile, a light danger wash across the entire card background. This is the system's one permitted alarm: reserve a solid semantic fill for the single state that genuinely needs to interrupt a scan, and only that state.
+
+## Typography
+
+**Body & Display Font:** Inter Variable (with `ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif` fallback)
+
+**Character:** One typeface for the entire system — hierarchy comes from weight and size, never a second family. It reads as a single, consistent institutional voice rather than a "brand font + UI font" split.
+
+### Hierarchy
+- **Headline** (600, 1.5rem → 1.875rem at `md:`, 1.25 line-height): page-level `
`s — "Mis préstamos", "Estadísticas", the login screen's title.
+- **Title** (600, 1.125rem, 1.4 line-height): section and card headers — "Inicia sesión", "Inventario", empty-state headings.
+- **Body** (400, 0.875rem, 1.5 line-height): the default text size for the whole system — table cells, descriptions, list content. Notably small-scale throughout; this is a dense operational tool, not editorial content.
+- **Label** (500, 0.875rem): form field labels (`.label`), rendered at 90% ink opacity.
+- **Caption** (400, 0.75rem, opacity 60–80%): metadata — matrícula, inventory numbers, timestamps, hint text under fields.
+
+### Named Rules
+**The Tabular Numbers Rule.** Any figure that represents a count — cantidad, stock (`disponible / total`), KPI values, dates — is rendered with `font-variant-numeric: tabular-nums`. Numbers must align like a ledger column, because that is literally what they replace.
+
+## Layout
+
+The system runs on Tailwind's default breakpoint (`md:` = 768px) as the single hard split between the mobile and desktop layouts — there is no intermediate tablet treatment.
+
+**Desktop (≥768px):** a fixed 256px (`w-64`) left sidebar in solid Verde Institucional, full page height. It is collapsible: a hamburger button above the content toggles a `sidebar-collapsed` class on ``, animated with a 200ms `translateX(-100%)`; the choice persists in `localStorage` and is applied via a blocking inline `