Saltar al contenido principal

Console nav / IA — modules vs operator tools (the org-context resolution)

:::note Contenido en inglés Esta página del wiki se sincroniza desde la base de conocimiento en inglés y todavía no está traducida. :::

reference · designer · updated 2026-06-27 · source

Settles the two-overlapping-navs collision now that the module system is live, so effort #963 (module pages + per-module sub-navigation) builds on a decided IA. Sibling to console-structure-spec.md (the org→module→section→resource model + the GLOBAL/ORG/MODULE config taxonomy), console-design.md (Aurea Ink matiz, black top-bar chrome), and console-overview-spec.md (the org landing). Grid v2: grid-system.md. Hues: color-scope.md (Amber = console). Rubric: design-principles.md.

Frontend implements off this; Designer reviews the rendered result. This is IA + nav pattern + routing shape + tokens, not pixel layout.

The collision (today). Two navs occupy one flat org namespace:

  1. Operator top-bar tabs (packages/ui/src/admin/console/console-nav.ts) — Overview · Builder · Datos & Memoria · Finanzas · Infra · Logs · Configurar, all at /org/<slug>/<segment>.
  2. Modules (apps/console/src/lib/module-overview-fixtures.ts) — Educación · Finanzas · Certificados · CRM, surfaced as Overview cards, each pointing segment into the same flat namespace.

So Finanzas is a tab and a module on the same route; Educación squats on /datos (overlapping the Datos & Memoria tab); Certificados/CRM fall through the [section] catch-all stub. The fix is to give modules their own namespace and split the nav into two classes.


1. The decision — three nav classes, modules are primary

The org context (L2) nav resolves into three classes, by what they answer. Knowing a thing's class is the structural call:

ClassItemsRoute shapeSourceRole in chrome
Org indexOverview/org/<slug>/overviewfixedRow-2 first tab, the org home
Modules (primary)Educación · Finanzas · Certificados · CRM · (future)/org/<slug>/<module>/<section>generated from enabledModules (#922)Row-2 left cluster, the protagonists
Operator tools (secondary)Builder · Memoria & IA · Infra 🔒 · Logs · Configurar/org/<slug>/<tool>fixed registryRow-2 right cluster, all folded into one Operador ▾ dropdown

Modules are the primary org nav — they are the client's business, what the operator works in day-to-day, and they must scale to N by declaration (structure-spec §1.2). Operator tools are cross-cutting utilities — not domains; they recede into a single dropdown.

Naming (recommended, ratify with José): "Datos & Memoria" → "Memoria & IA". The data now lives in the modules (alumnos, pagos…), so "Datos" duplicated the module nav and muddied the section. What remains here is the AI substrate — connectors + the memory layer that powers the org's agents. Renaming removes the confusion (full concept: console-memoria-spec.md). The nav spec uses the new name throughout; revert to "Datos & Memoria" only if José rejects it.

1.1 Row 2 layout — primary clean, operator tools in ONE dropdown

Decision: the primary row carries only Overview + the enabled modules. All five operator tools collapse into a single right-aligned Operador ▾ dropdown — including Configurar. This keeps the module nav pristine and scalable; the ⌘K command pill (Row 1) is the instant fast-path to any tool, so the dropdown costs no reach.

Row 2 ┌──────────────────────────────────────────────────────────────────────────────┐
│ Overview │ Educación Finanzas Certificados CRM …………hairline…… [ Operador ▾ ] │
└──────────────────────────────────────────────────────────────────────────────┘
org index ── enabled modules (generated, expand left) ── operator tools (one chip)
  • Overview first (org index / home), then the enabled modules inline (the expanding region; module overflow folds into a Más ▾ exactly like the current console-tabs.tsx mechanic).
  • A full-height hairline separator (border-l border-console-chrome-edge) then the right-aligned Operador ▾ chip.

Why all five (incl. Configurar) and not a persistent gear: one mechanism is calmer than a chip

  • a stray gear, and grouping (below) keeps Configurar one obvious click away. Settings is reachable via the dropdown's "Organización" group and ⌘K — a persistent gear would re-introduce a second trailing affordance the fold exists to remove.

The Operador ▾ dropdown — full spec

AspectSpec
TriggerA chip at the row's right end: label Operador + ArrowDown01Icon (14px, 1.5). type-nav-label. Idle text-fg-muted → hover text-fg-primary. Active (any operator tool is the current route) → text-fg-primary and the chrome Amber under-rule sits under the chip (reuse the existing Más ▾ active-state logic in console-tabs.tsx).
Contents (grouped)Two labelled groups, forward-compatible with the L1 lift (§1.3):
· Plataforma — Builder · Infra 🔒 · Logs
· Organización — Memoria & IA · Configurar
Group headers: type-meta text-fg-muted (uppercase eyebrow), non-interactive; a border-t border-border-subtle between groups. Each item: HugeIcon (16px) + label, superadmin items keep the lock glyph.
PlacementRadix DropdownMenu, align="end", sideOffset={4}, anchored to the chip (top-right of the chrome). min-w-56.
Item active statethe current tool → text-accent-text (Amber, the dropdown's read-only "you are here" cue — a category echo of the chrome accent, not a second action magnet); others text-fg-secondarydata-[highlighted]:bg-surface. Mirrors the existing Sección ▾ item styling.
Tokenscontent rounded-md border border-border-default bg-elevated p-1 shadow-md z-[var(--z-modal)]; items rounded-sm px-2.5 py-2 font-… via type-nav-label. All .console scope — no raw hex.
Motionpopover scales from the trigger (transform-origin), --duration-enter (220ms) --ease-out; exit --duration-exit (160ms). prefers-reduced-motion → opacity-only. No animation on the ⌘K path.
Responsiveunchanged from today: <md the whole primary row (Overview + modules) already collapses to Sección ▾; the operator tools join that same dropdown under their groups so there is one menu on mobile.

Why fold the tools, not interleave them. Modules (4) + Overview + 5 tools = 10 mono-label tabs is a wall, and it worsens as modules grow — the exact anti-pattern the module system exists to avoid. One grouped dropdown makes "modules = the work, tools = utilities" legible (Simplicity, Familiarity) and frees the module region to expand. The grouping (Plataforma / Organización) pre-stages the L1 lift (§1.3): when Builder/Infra/Logs move to the platform shell, the dropdown simply drops the Plataforma group.

1.2 Duplication — resolved explicitly

ConflictResolution
Finanzas (tab ⨯ module)Folds into the module. Remove the standalone Finanzas operator tab from org context. In an org, "Finanzas" = the client's finance operations = the Finanzas module (/org/<slug>/finances/*: pagos, facturas, conciliación — the admin's Pagos & CFDI domain). The operator billing the client (MRR/receivables) is a platform L1 roll-up (deferred, structure-spec §1) plus an org "Billing setup" row under Configurar (org scope). One word, one meaning in org context.
Datos & Memoria (tab overlapping Educación's /datos)Renamed to "Memoria & IA"; stays an operator tool — does NOT fold into a module. The data is the modules; what remains is the AI substrate (connectors + memory layer). It is the ADR-007 substrate (cross-module), distinct from any single module's CRUD. The overlap was an accident of Educación squatting on /datos; fixed in §3 (Educación → /education/*). It moves to /org/<slug>/memoria and lives in the Operador ▾ dropdown (Organización group). Full concept + screen: console-memoria-spec.md.
Certificados / CRM (modules in the [section] catch-all)Become first-class module namespaces (/org/<slug>/certificates/*, /crm/*); the flat catch-all is retired (§3).

Net: modules own a /<module>/<section> namespace; operator tools own flat /<tool> segments; the two never collide because they live at different levels.

1.3 Forward note — L1 vs L2 (deferred, unblocks #963 either way)

Builder and Infra are genuinely platform (L1) surfaces (operator building tedos itself / fleet health — console-design.md §5), not per-org. In the two-level target (structure-spec §1) they lift to an L1 platform shell and leave org context entirely; only Memoria & IA · Logs (org audit) · Configurar (org) remain org-context operator tools (the dropdown's Organización group). Until that lift lands they all sit in the Operador ▾ dropdown. #963 does not depend on the lift — it only touches module nav.


2. The module sub-nav (the #963 pattern)

Inside a module (/org/<slug>/<module>/<section>) the module's sections need a secondary nav. The columns + data joins each section table renders are specced in console-module-tables-spec.md (names not FK ids, formatted numbers/money/dates, categorical badges).

2.1 Placement — a content sub-tab strip, not a left rail, not a third chrome row

A horizontal sub-tab strip in the content area, at the top of the module page, below a compact module header and above the section body. This reuses the Builder sub-tab pattern (console-design.md §4 Builder: "Sub-tab strip under the primary tab"), so it is already a console idiom.

┌ black top-bar (Row 1 + Row 2) ──────────────────────────────────────────┐ chrome (unchanged)
└──────────────────────────────────────────────────────────────────────────┘
Educación [ Nuevo alumno ] module header (+ the
──────────────────────────────────────────────────────────────────────── ONE content amber moment)
▸ Alumnos Cursos Inscripciones section sub-tab strip
──────────────────────────────────────────────────────────────────────── ← border-b border-border-subtle
┌ DataTable (ResourceListView) ─────────────────────────────────────────┐ section body (full width)

Not a left rail: module screens are data-dense (wide DataTables) and honor G5 anti-stretch — they want full width. The left rail stays reserved for Configurar (ConfigureLayout), keeping that surface's "the one local left-nav" identity intact (console-design.md §4 Configurar). Sub-tabs ≠ config sections.

Not a third top-bar row: a third sticky bar pushes content down and competes with the chrome's two rows. The strip lives in the content, scrolls with it (or sticks just under the top-bar — Frontend's call; default static at content top).

2.2 Active state — NEUTRAL (no second Amber)

The chrome's Amber under-rule already marks the active module in Row 2 (chrome budget). The module screen's content Amber moment is reserved for its one primary action (e.g. Nuevo alumno / Crear) or a data protagonist. Therefore the section sub-tab active state is neutral — it consumes no accent (G17: one Amber per screen, and the chrome under-rule + one content moment is the established two-budget pattern, same as the Overview screen).

Sections are route links, not a local toggle, so style them as link-tabs (not a Segmented thumb):

StateTreatment
activebg-elevated text-fg-primary rounded-md · type-nav-label · aria-current="page"
inactivetext-fg-muted → hover text-fg-primary + bg-surface
focusfocus-visible ring (--shadow-focus) — a11y gate, not polish

This reads clearly distinct from the chrome's sliding Amber under-rule (a different shape — pill vs under-rule — and a different hue — neutral vs Amber).

2.3 How sections render

Each section is a resource listResourceListView (structure-spec §3) over the DataTable family, with its own 4 states + toolbar (search/filter/columns). The sub-tab navigates between sections; the body is the generic list view fed by the resource registry. Resource detail/edit/create render on deeper routes (§3), not in the strip.

Module landing (/org/<slug>/<module> with no section): redirect to the module's default section (first in sections[]) — e.g. /org/<slug>/education…/education/alumnos. No empty module index page.

2.4 Responsive

  • ≥sm — sub-tabs inline (modules carry 2–3 sections; they fit). Wrapping is forbidden (no ragged second line); overflow-x is clip (G6).
  • <sm (or if a future module's sections overflow) — collapse the whole strip to a single Sección ▾ dropdown, mirroring ConsoleTabs' <md behavior. One scroll-free pattern at every size.

3. Routing shape (concrete)

/ org picker (L1 home, no tabs)
/org/<slug>/overview org index / landing [org index]
/org/<slug>/<module> → redirect to default section
/org/<slug>/<module>/<section> resource LIST (the section body)
/org/<slug>/<module>/<section>/<id> resource SHOW
/org/<slug>/<module>/<section>/<id>/edit resource EDIT
/org/<slug>/<module>/<section>/new resource CREATE
/org/<slug>/memoria[/<section>] Memoria & IA [operator tool] (was /datos)
/org/<slug>/builder[/<section>] Builder [operator tool]
/org/<slug>/infra Infra 🔒 [operator tool]
/org/<slug>/logs Logs [operator tool]
/org/<slug>/configurar/* Configurar (org) [operator tool]

3.1 Module + section slugs

Route segments are identifiers → English (language.md), even though UI labels stay Spanish. The module segment = the module key. Recommended mapping (UI label → route slug):

Module (label)key / segmentSections (label → slug)
EducacióneducationAlumnos → students · Cursos → courses · Inscripciones → enrollments
FinanzasfinancesPagos → payments · Facturas → invoices · Conciliación → reconciliation
CertificadoscertificatesEmitidos → issued · Plantillas → templates
CRMcrmContactos → contacts · Leads → leads · Pipeline → pipeline

The English-slug migration of the existing Spanish stubs (datos/cursos/finanzas…) is a Frontend/Tech-Lead application of language.md (latest-touched wins). The load-bearing design decision is the module-namespacing (/<module>/<section>), which kills the collision regardless of slug language. If the operator/Tech-Lead prefer Spanish slugs short-term, namespacing alone still resolves it — but new routes should land English.

3.2 What happens to the existing routes + the [section] catch-all

  • Operator-tool flat routes stay: overview, builder, infra, logs, configurar remain at /org/<slug>/<tool>. datosrenamed memoria (redirect the old path).
  • finanzas flat route is removed/repurposed — folds into the finances module namespace (/org/<slug>/finances/*). The old /org/<slug>/finanzas either redirects to …/finances or is deleted with the standalone tab.
  • The [section] catch-all stub is RETIRED. It catches module sections at the wrong (flat) level and is the source of the collision. Replace it with the nested module tree:
    • /org/<slug>/[module]/page.tsx — redirect to default section.
    • /org/<slug>/[module]/[section]/page.tsx — the generic ResourceListView (data-in-props, fed by the resource registry; structure-spec §5 composition contract).
    • deeper [id] / [id]/edit / new routes per §3 as the views land. Static operator-tool segments take precedence over the [module] dynamic segment (as the static siblings already do over today's [section]).
  • module-overview-fixtures.ts (Overview cards) updates: Educación segment datoseducation; Finanzas finanzasfinances; section segments become module-relative so a card's section link resolves to /org/<slug>/<module>/<section> (e.g. Educación → education + students), and Abrir → the module's default section. This also de-collides the Overview links.
  • console-nav.ts: drop Finanzas from the operator tab list; the module set is not hardcoded here — it generates from enabledModules (a new module-nav source, structure-spec §1.2). console-nav.ts keeps only the operator tools (Builder · Memoria & IA · Infra · Logs · Configurar) plus Overview, each carrying a group: 'plataforma' | 'organizacion' field so the dropdown renders the two groups (§1.1) from data.

3.3 Operator-tool page layouts (target, so Frontend fixes to a spec)

The operator-tool pages need layout fixes (José). Targets — brief, token-level; full per-screen directions live in console-design.md §4. All reuse the §4 tokens + the §2 neutral content sub-tab strip where a screen has sub-sections (no second Amber).

ToolTarget layout
BuilderAdopt the §2 sub-tab strip for its sub-sections (Estado · Roadmap · Bounties · Mi trabajo · Aportar al kit) — same neutral pill strip as modules, not an ad-hoc tab row (this is the "tabs layout" fix). Body per sub-tab: Estado = efforts board — a CSS-grid of columns (Backlog · En progreso · En revisión · Hecho), each column a vertical stack of effort cards (parent title, sub-issue progress ring, role = neutral/info badge, PR/deploy chips in type-mono); one Amber moment = the single focused/In-Progress effort. Roadmap = a lane/timeline. Cards drag-snap (C2: scale(1.02) lift, dotted-ink ghost, transform-only). Full-width canvas (G5), overflow-x: clip (columns scroll-y within, page never scrolls-x). 4 states; empty = "Sin efforts abiertos".
Infra 🔒Read-only, no accent. Top: a grid of service health cards (Vercel admin · Vercel api · Fly api · DB · Engine) — each bg-surface rounded-xl card with a StatusDot (success/warning/danger), last deploy, region, latency sparkline. Below: a recent-deploys DataTable (service · type-mono sha · status badge · time · duration). Row → DetailPanel (right slide-in; rollback = the only danger button, inline confirm). "Solo superadmin" pill + lock glyph in the header.
LogsDense event-log DataTable (Tipo · Sujeto · Proyecto · Fuente · type-mono timestamp) + a left filter rail (event type · subject · source) + search + a range control. Virtualize; selection is instant (no per-row tween — high-frequency surface). Row → DetailPanel (right) with the raw payload as a type-mono JSON block on bg-sunken (copy button). Selected row = bg-accent-soft tint (the one Amber, as an active-row cue). 4 states; empty = "Sin eventos en el rango".

The left filter rail (Logs) and the ConfigureLayout left rail (Configurar) are the only local left-navs (console-design.md); module + Builder sub-sections use the top sub-tab strip, never a rail (§2.1).


4. Tokens · states · motion (Frontend invents nothing)

All utilities resolve through the .console ink scope already on <body> (packages/ui/src/admin/console/console-theme.css). No raw hex in TSX.

Tokens

ElementToken / utility
Row-2 separator (modules ⟍ tools)border-l --console-chrome-edge
Operador ▾ chiptype-nav-label · text-fg-muted → hover text-fg-primary; active (a tool is open) text-fg-primary + the chrome Amber under-rule
Chrome active under-rule (the ONE chrome Amber)bg-accent, 2px, slides via translateX (unchanged)
Module header labeltype-h2 text-fg-primary
Sub-tab strip containerflex gap-1 + border-b border-border-subtle; overflow-x: clip
Sub-tab (active)bg-elevated text-fg-primary rounded-md (R3) · type-nav-label
Sub-tab (inactive)text-fg-muted → hover text-fg-primary + bg-surface
Section bodyResourceListView / DataTable on bg-surface, header bg-sunken
Primary action (the content Amber moment)the foundation Button (one per module screen, e.g. Nuevo …) — Amber per the scope

Type roles: type-nav-label (tabs), type-h2 (module header), type-meta/type-mono (table) — all existing (admin-ui-conventions.md §Typography). Radii G19. Icons HugeIcons stroke 1.5.

The 4 states (every section list — frontend-guardrails.md H1)

StateTreatment
loadinglayout-matched skeleton over the DataTable (animate-pulse on bg-sunken); no spinner
emptyEmptyState — honest copy ("Sin alumnos todavía"), never fake rows; seed mark allowed in calm empties
errorinline ReadError (danger tone, role="alert"), reachable via ApiResult.!ok (DF4) — never inferred from empty
successthe table

The sub-tab strip itself needs no separate states (it is nav); a module with zero enabled sections → the module landing shows the EmptyState instead of redirecting.

Motion (console tokens, transform/opacity only — console-design.md §6, C3)

InteractionSpec
Chrome under-ruleslides translateX 200ms --ease-out (unchanged); width/left never animated
Operador ▾ / Sección ▾ popoverscales from trigger (transform-origin), --duration-enter (220ms) --ease-out
Sub-tab switchcolor/bg only (no sliding rule, no thumb) — instant, --duration-press (120ms) --ease-out. Nav between data views must feel immediate (perceived performance).
Section list mountoptional opacity 0→1 + translateY(8px→0) --duration-enter --ease-out; reduced-motion → instant
Press feedbackscale(0.97) --duration-press on the primary action

prefers-reduced-motion → kill transforms/loops, keep ≤200ms opacity crossfades. No animation on keyboard-repeated actions (⌘K instant). Sub-tab navigation is not animated with a sliding indicator — that would read as a second under-rule competing with the chrome's Amber one.


5. Principle check + open items

Rubric (design-principles.md): Simplicity + Familiarity are load-bearing — splitting nav into modules (primary, generated) vs operator tools (folded) makes the mental model legible and stops the wall-of-tabs. Flexibility — the module row + [module]/[section] tree scale to N modules by declaration (structure-spec §5), zero new nav code per module. Craft/Delight — one Amber chrome under-rule + one content Amber moment per module screen; neutral sub-tabs; calm ink.

Named trade-off — Simplicity vs Agency: folding the five operator tools behind Operador ▾ costs one click to reach Builder/Logs/etc. Mitigated by ⌘K (Row 1, instant) and by the forward L1 lift that removes Builder/Infra from org context entirely (§1.3). The win — modules unambiguously primary, no collision — outweighs the extra click for low-frequency tools.

Open items (for the operator / Tech-Lead — assumed, not decided):

  1. "Datos & Memoria" → "Memoria & IA" rename (§1, recommended) — needs José's ratify. The whole nav + memoria spec assume the new name; defaulting to it.
  2. Slug language (§3.1) — English module/section slugs (recommended, language.md) vs keep Spanish short-term. Namespacing resolves the collision either way.
  3. L1/L2 lift (§1.3) — when Builder/Infra/platform-Logs/Finanzas-rollup move to an L1 platform shell; until then they sit in the Operador ▾ dropdown (and the dropdown drops its Plataforma group on the lift).
  4. enabledModules source — the module-nav generation reads per-org enablement (#922); fixture today, typed contract when #934/#922 land.
  5. Module landing — redirect-to-default-section (recommended) vs a per-module mini-overview. If a module ever wants its own glance page, the landing route hosts it instead of redirecting.

Next step: Frontend builds the [module]/[section] route tree + the neutral sub-tab strip (reusing Builder's pattern) + updates console-nav.ts (drop Finanzas) and module-overview-fixtures (module-relative segments). Designer reviews the rendered result against the 8 principles before close.