14 de septiembre de 2026 · plan de arquitectura · design system + vistas + backend, con tokens compartidos como fuente única
Decisiones de arranque (Gustavo, 14-sep): alcance = todo (DS + 66 vistas + backend/GraphQL, incluidos los 6 scaffolds) · mecanismo = tokens compartidos como fuente única (un origen → CSS + Dart; componentes a mano en cada lado bebiendo del mismo token; matriz de paridad como control).
El POS de Guiadalajara vive hoy en dos superficies Web (terminal @guiadalajara/pos + consola admin-shell) y va hacia tres apps Flutter (Caja, Cocina, Gestión) + displays LAN. Queremos que el POS entero exista en ambos mundos y no se desincronice. Este plan lo estructura en tres capas de paridad, todas ancladas a una fuente única de tokens:
Ds* primitivas + Pos* dominio) idéntico en Web y Flutter, con storybook vivo en ambos (hoy solo existe el de Web en CSS: pos-directorio.servicioscreativos.mx).El control anti-deriva no es disciplina sola: es tokens generados desde un origen + matriz de paridad viva + pruebas (visual regression del DS, contract tests del GraphQL) en CI.
Objetivo: que un cambio de marca o de contrato se propague a Web y Flutter desde un solo lugar, y que "estamos en paridad" sea medible, no una aspiración.
Dentro de alcance (las 3 capas):
- DS: ~35 componentes ya medidos en Flutter (gdl_ui) + su contraparte Web + los Pos* nuevos.
- Vistas: las 66 del TRACKER (Caja 23, Cocina 4, Gestión 37, Display 2) × Web/Flutter.
- Backend: 38 servicios (32 REAL, 6 SCAFFOLD) + el contrato GraphQL que ambos frontends comparten.
Fuera de alcance (por ahora, se nombran para acotar): terminal card-present, agregadores de delivery, y todo lo que no sea POS (marketplace consumer, etc.).
No-objetivos: no unificar los frameworks (Web sigue React, móvil sigue Flutter); no codegen de componentes (se descartó); la paridad es de contrato + apariencia + comportamiento, no de código.
Todo ✅ = medido en el repo nuevo
apps/marketplace/fluttery~/guiadalajara-project(monorepo). El baseline Web se completa con las mediciones en curso.
gdl_ui: 31 Ds* (SCMDG Soft, src/ds/, 14 enums de variante) + 7 Gdl* legacy + 4 helpers. Sin storybook (solo el catálogo Web que construimos).gdl_theme/src/scmdg/: tokens reales — DsTokens (ThemeExtension claro/oscuro), amarillo #FFE60A=brand400/rol accent, radio base 6, Space Grotesk + DM Mono, tema buildScmdgSoftTheme(brightness:). No hay token de "línea de acento" (se compone).pos_ui están por crear.@guiadalajara/pos (apps/marketplace/pos/app, Vite 6 + React 19 + Zustand + urql). Sin Tailwind: estilado por CSS vars en src/styles/tokens.css (header literal: "SCMDG Soft (DS-3 «Intermedio»)"). 13 componentes bespoke + consume paquetes compartidos.admin-shell (apps/admin-shell, @dockethub/admin-shell, mismo stack). Tokens en src/styles/tokens.css = trasplante 1:1 del POS (diff = solo el comentario de cabecera + 1 token --num-tabular). 26 componentes locales + primitivas CSS .adm-*.@dockethub/ui (packages/ui) — 20 primitivas: Button, Badge, Dialog, Card, Table, Input, Select, Field, Header, PageHeader, Section, ActionMenu, Toast, Stat, EmptyState, ErrorBanner, BarcodeInput, Tabs, TenantBranding + charts/.@dockethub/ui-restaurant (packages/ui-restaurant) — ~21 de dominio POS: NumPad, CashDenominationsForm, InteractiveFloor, FloorEditor, KdsColumn, TicketCard, MenuGrid, OrderPanel, ModifierDialog, TipSelector, ChangeDueDisplay, SplitDialog, CoversStepper, LockScreen, PrinterStatusBadge, TemplatePreview, TablePickerDialog…@dockethub/ui-patterns (11 power-tools) y @dockethub/ui-dockethub (DS independiente de plataforma).data-theme + clase en <html>), por-usuario en backend, script no-FOUC. Igual patrón en POS y admin.#FFE60A=--gdl-yellow-400, neutrales cálidos --gdl-warm-*, semánticos, Space Grotesk/DM Mono, radios 4/6/8/12/16, grid 4px, motion 180ms. Confirmado por diff de valores.pos-refunds (P1), pos-payments-mixed (P1), kds-stations (P2), kds-priorities (P2), pos-portal-facturacion (P2), pos-sessions (P3, redundante → consolidar en pos-floor).La buena noticia: los tres frontends ya son el mismo DS SCMDG Soft DS-3, con valores idénticos (verificado por diff y por el comentario "ported 1:1" en el Dart). La mala: son tres copias mantenidas a mano sin fuente única:
- apps/marketplace/pos/app/src/styles/tokens.css (CSS)
- apps/admin-shell/src/styles/tokens.css (CSS, copia 1:1 del anterior)
- apps/marketplace/flutter/packages/gdl_theme/lib/src/scmdg/ds_colors.dart (Dart, "Ported 1:1 from tokens/colors.css")
No existe paquete de tokens compartido (verificado: sin packages/tokens, sin style-dictionary, sin tokens.json). La "fuente" conceptual de la que todos dicen portar es tokens/colors.css del skill guiadalajara-design. Hoy coinciden por disciplina; sin origen común, la próxima corrección de marca se aplicará en un lugar y divergirán. Señales de deuda ya presentes: bloque legacy --hig-green-* remapeado, index.html del admin aún con data-brand="nintaigo", el --num-tabular que ya solo está en admin. Eso es exactamente lo que la Capa 0 elimina.
Origen: un paquete packages/design-tokens/ con los tokens en JSON declarativo (formato estilo W3C Design Tokens / Style Dictionary). Es la única verdad de color, tipografía, radio, espaciado, motion, sombra, con sus valores claro/oscuro.
Generación (build step, en CI):
- → CSS (tokens.css con :root{--...} + [data-theme=dark]) que consumen el terminal POS, el admin-shell y el storybook Web.
- → Dart (ds_tokens.g.dart que alimenta DsTokens/buildScmdgSoftTheme) para gdl_theme.
- (Opcional) → Tailwind theme extend, si el Web usa Tailwind.
Regla: nadie escribe un hex/tamaño a mano en componentes; todo lee un token. El PR que agrega un token corre el generador y ambos lados se actualizan en un commit.
Reconciliación inicial (bajo riesgo — ya son copias idénticas): como los tres frontends ya portan 1:1 del mismo sistema, la extracción es mecánica, no de diseño. Semilla del JSON = tokens/colors.css del skill guiadalajara-design (la fuente conceptual que todos citan), cotejada contra gdl_theme/src/scmdg (la más completa). Luego:
- el JSON genera el CSS que reemplaza las dos copias apps/.../pos/app/src/styles/tokens.css y apps/admin-shell/src/styles/tokens.css (hoy diff = 1 token);
- el JSON genera el Dart que alimenta gdl_theme (reemplaza el hand-port de ds_colors.dart);
- el CSS del directorio/storybook deja de hardcodear y consume el generado.
Los Gdl* legacy y el bloque --hig-green-* quedan deprecados. La "línea de acento" se formaliza como utilidad/token (accent + border-width, esquina recta).
Punto de partida real: el Web NO parte de cero — ya tiene dos paquetes de DS (@dockethub/ui primitivas, @dockethub/ui-restaurant dominio POS). El Flutter tiene las 31 Ds* primitivas pero le falta el dominio POS (pos_ui por crear). Es decir, en la capa de dominio Web va adelante. La paridad es alinear dos inventarios existentes y cerrar el hueco de Flutter.
Matriz de componentes (una fila por componente; columnas: Web / Flutter / estado / variantes). Mapeo natural en dos bloques:
Primitivas — @dockethub/ui ↔ gdl_ui/src/ds/:
| Web (@dockethub/ui) | Flutter (Ds*) |
|---|---|
| Button, Badge, Card, Dialog, Input, Select, Table, Tabs, Stat, Toast, EmptyState, ErrorBanner, ActionMenu, Field, Header/PageHeader/Section, BarcodeInput, charts | DsButton, DsBadge, DsCard, DsDialog, DsInput, DsSelect, DsTable, DsTabs, DsStat, DsToast, (Gdl)EmptyState, (Gdl)ErrorView, DsMenu, DsInput(label), — , — , DsDashboardGrid+DsBarChart (por crear) |
Huecos a cerrar: Flutter tiene extras sin par Web (DsAccordion, DsBreadcrumbs, DsPagination, DsProgress, DsSlider, DsCheckbox, DsRadio, DsSwitch, DsTextarea, DsTooltip, DsAvatar, DsQrCode, DsSkeleton, DsSpinner) → verificar si Web los tiene sueltos o faltan; y Web tiene charts/BarcodeInput/Header sin par Flutter.
Dominio POS — @dockethub/ui-restaurant ↔ pos_ui (por crear en Flutter):
| Web (@dockethub/ui-restaurant) | Flutter (Pos* propuesto) |
|---|---|
| NumPad | PosKeypad |
| InteractiveFloor / FloorEditor | PosTableTile (+ editor) |
| KdsColumn / TicketCard | PosKdsTicket |
| OrderPanel | PosOrderCard |
| TemplatePreview | PosReceipt |
| CashDenominationsForm | (corte de caja) |
| SplitDialog | (pago mixto — liga a pos-payments-mixed) |
| RefundDialog (en el POS app) | (reembolsos — liga a pos-refunds) |
| ChangeDueDisplay, TipSelector, CoversStepper, PrinterStatusBadge, LockScreen | PosDisplaySurface, tips, covers, etc. |
Insight: los
Pos*que el catálogo proponía "crear" ya existen en Web como@dockethub/ui-restaurant. El trabajo Flutter es portarlos apos_ui; Web solo necesita dedupe (ver §8).
Doble storybook (el "todo como Web y todo como Flutter"):
- Web: Storybook real de @dockethub/ui + @dockethub/ui-restaurant (componentes vivos, no las réplicas CSS del directorio actual). El catálogo pos-directorio queda como vista de referencia/documentación.
- Flutter: Widgetbook sobre gdl_ui/pos_ui, desplegado como Flutter web junto al de Web.
- Ambos leen los tokens generados (Capa 0) → misma apariencia por construcción.
Control: visual-regression por componente (mismo set de estados y ambos temas) para detectar divergencia Web↔Flutter.
Mapa de vistas (las 66 del TRACKER) con su equivalente en cada mundo:
- Caja (cj-01..23) ↔ terminal @guiadalajara/pos.
- Cocina (kd-01..04) ↔ vista KDS del terminal / app Cocina.
- Gestión (ge-01..37) ↔ admin-shell.
- Display LAN (dp-01/02) ↔ superficies LAN emitidas por la app Caja (no hay equivalente Web público — es panel LAN).
Cada vista: misma información, misma navegación (breadcrumbs ya documentados), mismos estados (vacío/carga/error). Se construyen sobre los componentes de la Capa 1, así que la paridad de vista se apoya en la de componente.
Orden sugerido: por dominio, empezando por los flujos de dinero (cobro, corte, tickets, recibo) y el piso/comanda, luego Gestión por módulos (inventario → recetario → catálogo → …).
Principio: el contrato GraphQL es la fuente; ambos frontends generan sus tipos desde el mismo schema (codegen de tipos, no de UI). Paridad de datos = ambos consumen las mismas queries/mutations.
Cerrar los 6 scaffolds (los huecos reales):
- 🔴 P1 pos-refunds — reembolsos (cableado en Terminal, servicio hueco).
- 🔴 P1 pos-payments-mixed — pago mixto/split por método.
- P2 kds-stations + kds-priorities — configuración del tablero KDS.
- P2 pos-portal-facturacion — portal de autofactura del cliente.
- P3 pos-sessions — redundante; consolidar en pos-floor, no reimplementar.
Control: contract tests contra el schema; el codegen de tipos en CI rompe el build si un frontend usa un campo que el schema no expone.
design-tokens JSON + generadores CSS/Dart; reemplazar hardcodes. Desbloquea todo.Pos* nuevos y promover los genéricos.(Fases secuenciadas por dependencia, no por calendario — F0 es prerrequisito; F1-F3 pueden solaparse por dominio.)
Gdl* legacy conviviendo → deprecar explícito; migrar al tocar; no crear nuevos.Métricas (cómo se mide la paridad):
- % de componentes en la matriz presentes en ambos con visual-regression verde.
- % de las 66 vistas presentes en ambos.
- Nº de tokens hardcodeados fuera del origen (meta: 0).
- Nº de scaffolds cerrados (meta: 6/6, o 5 + pos-sessions consolidado).
- Nº de campos GraphQL usados por un frontend sin respaldo de schema (meta: 0).
Próximos pasos (para arrancar F0):
1. Crear packages/design-tokens con el JSON semilla desde guiadalajara-design/tokens/colors.css (cotejado con gdl_theme/src/scmdg).
2. Escribir los dos generadores (CSS, Dart) y cablearlos en CI.
3. Reemplazar la primera de las dos copias CSS (apps/marketplace/pos/app/src/styles/tokens.css) por el generado — es la prueba de que un origen alimenta a los tres consumidores.
4. Levantar la matriz de paridad viva en el repo (con el mapeo de §5 como semilla).
5. En paralelo: deduplicar los wrappers app-level repetidos entre POS y admin-shell (ThemeSwitcher, ConfirmDialog, BrandMark, DataTable, AvatarMenu, Toast, Tabs, Skeleton) subiéndolos a @dockethub/ui.
Servicios Creativos — Guadalajara, México