From 57b07b9b82f7eaf2285d8ddaf02278b564320a6e Mon Sep 17 00:00:00 2001 From: Lak-G Date: Sun, 16 Aug 2026 15:19:57 -0700 Subject: [PATCH] =?UTF-8?q?Redise=C3=B1o=20impeccable:=20brand=20mark,=20p?= =?UTF-8?q?=C3=A1ginas=20403/404,=20DESIGN.md=20+=20PRODUCT.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rediseño hecho en sesión aparte con el skill impeccable. Se registran las especificaciones y ajustes visuales al sistema ya en producción. Añade: - BrandMark.astro con el escudo/logo del laboratorio. - Favicon actualizado (svg + ico). - Páginas propias 403 y 404 consistentes con el sistema visual. - DESIGN.md y PRODUCT.md documentando decisiones. - .impeccable/design.json con el design system tokenizado. Ajusta componentes existentes (modales, autocomplete, layout, login, solicitudes, mis-préstamos, middleware) para alinearse a los tokens del design system. Co-Authored-By: Claude Opus 4.7 --- .impeccable/design.json | 203 ++++++++++++++++ AGENTS.md | 14 ++ DESIGN.md | 226 ++++++++++++++++++ PRODUCT.md | 65 +++++ public/favicon.ico | Bin 655 -> 22382 bytes public/favicon.svg | 8 +- src/components/BrandMark.astro | 9 + .../admin/inventario/EliminarMaterial.tsx | 4 +- .../admin/inventario/MaterialForm.tsx | 6 +- .../admin/reportes/Autocomplete.tsx | 6 +- .../admin/solicitudes/AccionesSolicitud.tsx | 5 +- .../admin/solicitudes/VerDetalles.tsx | 6 +- src/components/alumno/SolicitarModal.tsx | 6 +- src/layouts/AppLayout.astro | 9 +- src/middleware.ts | 2 +- src/pages/403.astro | 18 ++ src/pages/404.astro | 18 ++ src/pages/admin/solicitudes/index.astro | 4 +- src/pages/alumno/mis-prestamos.astro | 9 +- src/pages/login.astro | 5 +- 20 files changed, 596 insertions(+), 27 deletions(-) create mode 100644 .impeccable/design.json create mode 100644 DESIGN.md create mode 100644 PRODUCT.md create mode 100644 src/components/BrandMark.astro create mode 100644 src/pages/403.astro create mode 100644 src/pages/404.astro diff --git a/.impeccable/design.json b/.impeccable/design.json new file mode 100644 index 0000000..93f8012 --- /dev/null +++ b/.impeccable/design.json @@ -0,0 +1,203 @@ +{ + "schemaVersion": 2, + "generatedAt": "2026-08-16T00:00:00Z", + "title": "Design System: LabPréstamos", + "extensions": { + "colorMeta": { + "verde-institucional": { + "role": "primary", + "displayName": "Verde Institucional", + "canonical": "oklch(48.5% 0.12 154.8)", + "tonalRamp": [ + "oklch(15% 0.12 154.8)", + "oklch(28% 0.12 154.8)", + "oklch(40% 0.12 154.8)", + "oklch(52% 0.12 154.8)", + "oklch(65% 0.12 154.8)", + "oklch(76% 0.12 154.8)", + "oklch(86% 0.12 154.8)", + "oklch(95% 0.12 154.8)" + ] + }, + "dorado-uabc": { + "role": "secondary", + "displayName": "Dorado UABC", + "canonical": "oklch(72.8% 0.148 74.4)", + "tonalRamp": [ + "oklch(15% 0.148 74.4)", + "oklch(28% 0.148 74.4)", + "oklch(40% 0.148 74.4)", + "oklch(52% 0.148 74.4)", + "oklch(65% 0.148 74.4)", + "oklch(76% 0.148 74.4)", + "oklch(86% 0.148 74.4)", + "oklch(95% 0.148 74.4)" + ] + }, + "superficie": { + "role": "neutral", + "displayName": "Superficie", + "canonical": "oklch(97.3% 0.004 157.2)", + "tonalRamp": [ + "oklch(15% 0.004 157.2)", + "oklch(28% 0.004 157.2)", + "oklch(40% 0.004 157.2)", + "oklch(52% 0.004 157.2)", + "oklch(65% 0.004 157.2)", + "oklch(76% 0.004 157.2)", + "oklch(86% 0.004 157.2)", + "oklch(95% 0.004 157.2)" + ] + }, + "tinta": { + "role": "neutral", + "displayName": "Tinta", + "canonical": "oklch(33.5% 0.033 260.9)", + "tonalRamp": [ + "oklch(15% 0.033 260.9)", + "oklch(28% 0.033 260.9)", + "oklch(40% 0.033 260.9)", + "oklch(52% 0.033 260.9)", + "oklch(65% 0.033 260.9)", + "oklch(76% 0.033 260.9)", + "oklch(86% 0.033 260.9)", + "oklch(95% 0.033 260.9)" + ] + }, + "peligro": { + "role": "danger", + "displayName": "Peligro", + "canonical": "oklch(54.4% 0.186 26)", + "tonalRamp": [ + "oklch(15% 0.186 26)", + "oklch(28% 0.186 26)", + "oklch(40% 0.186 26)", + "oklch(52% 0.186 26)", + "oklch(65% 0.186 26)", + "oklch(76% 0.186 26)", + "oklch(86% 0.186 26)", + "oklch(95% 0.186 26)" + ] + } + }, + "typographyMeta": { + "headline": { "displayName": "Headline", "purpose": "Page-level h1 — dashboard/section titles, login title." }, + "title": { "displayName": "Title", "purpose": "Card and section headers within a page." }, + "body": { "displayName": "Body", "purpose": "Default text size across the whole system: table cells, descriptions, list content." }, + "label": { "displayName": "Label", "purpose": "Form field labels, always paired with htmlFor/id." }, + "caption": { "displayName": "Caption", "purpose": "Metadata: matrícula, inventory numbers, timestamps, field hints." } + }, + "shadows": [ + { "name": "floating-chrome", "value": "0 10px 30px -8px rgba(0,0,0,0.25)", "purpose": "Mobile dock nav — soft ambient shadow signaling it floats above the scrolling page." }, + { "name": "overlay-notification", "value": "0 16px 40px -12px rgba(0,0,0,0.25)", "purpose": "Toast stack — same ambient quality, slightly heavier, paired with backdrop-blur-xl." } + ], + "motion": [ + { "name": "toast-entrance", "value": "cubic-bezier(0.22, 1, 0.36, 1)", "purpose": "260ms translate+scale+fade entrance for toast notifications." }, + { "name": "chrome-transition", "value": "cubic-bezier(0, 0, 0.2, 1)", "purpose": "150-200ms default ease-out for sidebar collapse, hover states, dock icon transitions." } + ], + "breakpoints": [ + { "name": "md", "value": "768px" } + ] + }, + "components": [ + { + "name": "Primary Button", + "kind": "button", + "refersTo": "button-primary", + "description": "Default call to action: submit, approve, continue with Google.", + "html": "", + "css": ".ds-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 10px 16px; border-radius: 8px; font-weight: 500; font-size: 0.875rem; border: none; cursor: pointer; touch-action: manipulation; transition: filter 150ms ease-out, background-color 150ms ease-out; } .ds-btn-primary { background: #00723F; color: #ffffff; } .ds-btn-primary:hover { filter: brightness(1.1); } .ds-btn-primary:focus-visible { outline: 2px solid #00723F; outline-offset: 2px; } .ds-btn-primary:disabled { opacity: 0.5; pointer-events: none; }" + }, + { + "name": "Secondary Button", + "kind": "button", + "refersTo": "button-secondary", + "description": "Sparingly used accent action — filtering, non-primary confirms.", + "html": "", + "css": ".ds-btn-secondary { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 10px 16px; border-radius: 8px; font-weight: 500; font-size: 0.875rem; border: none; cursor: pointer; touch-action: manipulation; background: #DD971A; color: #ffffff; transition: filter 150ms ease-out; } .ds-btn-secondary:hover { filter: brightness(1.1); } .ds-btn-secondary:focus-visible { outline: 2px solid #00723F; outline-offset: 2px; }" + }, + { + "name": "Ghost Button", + "kind": "button", + "refersTo": "button-ghost", + "description": "Low-commitment counterpart to Primary — cancel, clear filters.", + "html": "", + "css": ".ds-btn-ghost { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 10px 16px; border-radius: 8px; font-weight: 500; font-size: 0.875rem; cursor: pointer; touch-action: manipulation; background: transparent; color: #2D3748; border: 1px solid color-mix(in oklab, #2D3748 15%, transparent); transition: background-color 150ms ease-out; } .ds-btn-ghost:hover { background: color-mix(in oklab, #2D3748 5%, transparent); } .ds-btn-ghost:focus-visible { outline: 2px solid #00723F; outline-offset: 2px; }" + }, + { + "name": "Status Pill", + "kind": "chip", + "refersTo": "badge", + "description": "Tinted-fill status indicator used for every préstamo/material estado; solid-fill only for the confirmed Vencido exception.", + "html": "Disponible Mantenimiento Vencido", + "css": ".ds-pill { display: inline-flex; align-items: center; border-radius: 9999px; padding: 2px 10px; font-size: 0.75rem; font-weight: 600; white-space: nowrap; } .ds-pill-primary { background: color-mix(in oklab, #00723F 13%, transparent); color: #00723F; } .ds-pill-secondary { background: color-mix(in oklab, #DD971A 15%, transparent); color: #c78614; } .ds-pill-danger-solid { background: #C53030; color: #ffffff; }" + }, + { + "name": "Card", + "kind": "card", + "refersTo": "card", + "description": "Flat white container — the system's default surface for content, forms, and KPI tiles. No shadow, hairline ink-mix border only.", + "html": "

Inicia sesión

Accede con tu cuenta institucional.

", + "css": ".ds-card { background: #ffffff; border-radius: 8px; padding: 24px; border: 1px solid color-mix(in oklab, #2D3748 10%, transparent); } .ds-card-title { font-size: 1.125rem; font-weight: 600; margin: 0 0 4px; color: #2D3748; } .ds-card-body { font-size: 0.875rem; opacity: 0.7; margin: 0; color: #2D3748; }" + }, + { + "name": "Input Field", + "kind": "input", + "refersTo": "input", + "description": "Labeled text field with hairline border and single-color focus outline (no glow, no border-color shift).", + "html": "
", + "css": ".ds-label { display: block; font-size: 0.875rem; font-weight: 500; margin-bottom: 4px; color: color-mix(in oklab, #2D3748 90%, transparent); } .ds-input { width: 100%; min-height: 44px; border-radius: 8px; padding: 10px 12px; background: #ffffff; border: 1px solid color-mix(in oklab, #2D3748 20%, transparent); font-size: 0.875rem; touch-action: manipulation; transition: border-color 150ms, box-shadow 150ms; } .ds-input:focus-visible { outline: 2px solid #00723F; outline-offset: 2px; }" + }, + { + "name": "Mobile Dock Nav Item", + "kind": "nav", + "refersTo": "nav-dock-item", + "description": "Signature component: icon-only floating pill navigation for the alumno/admin mobile layout, replacing a conventional bottom tab bar.", + "html": "", + "css": ".ds-dock { display: inline-flex; align-items: center; gap: 4px; border-radius: 9999px; padding: 8px; backdrop-filter: blur(8px); background: rgba(255,255,255,0.95); box-shadow: 0 10px 30px -8px rgba(0,0,0,0.25); border: 1px solid color-mix(in oklab, #2D3748 10%, transparent); } .ds-dock-item { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 9999px; color: color-mix(in oklab, #2D3748 70%, transparent); transition: background-color 150ms, color 150ms, transform 150ms; text-decoration: none; } .ds-dock-item:hover { background: color-mix(in oklab, #2D3748 5%, transparent); color: #2D3748; } .ds-dock-item:active { transform: scale(0.95); } .ds-dock-item-active { background: #00723F; color: #ffffff; }" + }, + { + "name": "Toast", + "kind": "custom", + "refersTo": "toast", + "description": "Signature component: floating top-center notification with a colored circular icon chip, survives page reload via a sessionStorage queue.", + "html": "
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 `