# CONTINUE — Familia `commerce` del tier `blocks` > **Kickoff para sesión nueva**: _«Lee `docs/process/CONTINUE-blocks-commerce.md` > y continúa la fase que toque.»_ > > **Estado 2026-08-10: DISEÑO CERRADO, CERO CÓDIGO ESCRITO.** Las decisiones > D-COM.1…5 están firmadas por el usuario. La fase **C0** (dossier + plan de > ejecución + grupo en el catálogo + entrada en `next-features.md`) está > **pendiente**: este handoff es lo único que existe todavía. --- ## 0. Qué es esto y por qué existe Una familia de **26 blocks de ecommerce** más **8 componentes de canon** y un **art nuevo `$commerce`**, diseñada con el mismo método con que nació el tier: analizar lo hecho → comparativa contra las referencias reales de cada ecosistema → fijar suelo de paridad → producir la lista. El punto de partida, en tres hechos: 1. **No existía una sola línea de dominio commerce en el repo.** Barrido de `cart · checkout · product · sku · order · basket · storefront · payment` sobre `src/ · web/ · scripts/`: nada. Lo único adyacente es el block `pricing`, que es comparativa de planes SaaS y cuyo README declara explícitamente que **no formatea moneda**. `billing` figuraba diferido en F5 de [`PLAN-blocks.md`](./PLAN-blocks.md). 2. **El canon ya resuelve casi todo lo que un catálogo commerce exige** (§3), y por eso la lista de huecos es corta y cada uno tiene motivo escrito. 3. **La doctrina de coordinación (2026-07-31) es justo lo que commerce pide.** Hoy sólo 3 de 15 blocks coordinan y sólo 2 tienen máquina de estado. Commerce invierte esa proporción — **14 de 26 coordinan** — porque casi todo lo que se vende puede estar bloqueado. **El diseño arranca por el estado, nunca por el layout.** --- ## 1. Puerta de lectura — antes de tocar nada Precondición dura, no sugerencia. Leer **directo**, nunca delegando la lectura a agentes. - Siempre: `CLAUDE.md` (llega solo) · este handoff. - Contrato del tier: [`architecture/blocks.md`](../architecture/blocks.md) (B-1…B-11, la regla de admisión, la sección «Coordination»). - Para un componente de canon: [`building-a-component.md`](../building-a-component.md) (la ruta de 9 fases, LA puerta) + [`guides/component-guide.md`](../guides/component-guide.md) (§Build contract + reglas A1–A37) + [`guides/completion-checklist.md`](../guides/completion-checklist.md) (la matriz de aceptación). - Vocabulario cerrado: [`canon/vocabularies.md`](../canon/vocabularies.md) (generado del código — archetypes, familias, verbos, intents, holds). - Contratos transversales: [`canon/tsc.md`](../canon/tsc.md) · [`canon/recipe-contract.md`](../canon/recipe-contract.md) · [`canon/direction-contract.md`](../canon/direction-contract.md). - Para el ala agéntica: [`architecture/agent.md`](../architecture/agent.md) (§5 el primitivo de actor, §7 la superficie de revisión). - **Y el README de CADA componente que el block compone.** El mapa de composición se escribe leyendo, no de memoria. --- ## 2. Las decisiones firmadas (D-COM.1…5, 2026-08-10) | # | Decisión | Consecuencia operativa | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **D-COM.1** | El estado del carrito vive en un **art nuevo `$commerce`** (`EngineCommerce`/`ActiveCommerce` + port de transporte que suministra el app), espejo de `$agent`/`AgentTransport` | Un carrito vive en cabecera, PDP, carrito y checkout: es estado **inter-block**, y B-10 prohíbe que un block importe otro. B-4 deriva `$` de `src/arts/*`, así que la carpeta lo allowlista sola | | **D-COM.2** | Alcance: alas **A+B+C+D+E** (26 blocks). Admin FUERA. **B2B diferida con disparador** | Admin lo cubre F3 (`data-table`, `dashboard`, `settings`), y ningún design system serio shippea commerce admin. B2B se registra en `next-features.md` | | **D-COM.3** | La ley europea entra en el **TIPO**, más un token `jurisdiction` en el contexto comercial de `$commerce` | `priorPrice{amount, basis:'lowest-30d'}` obligatorio en rebaja · `taxIncluded` no opcional · `unit` cuando se vende por medida. No en `$prefs`: no es preferencia del usuario | | **D-COM.4** | La procedencia por actor es **hilo, no apéndice** | `actor` nace en la línea de carrito en C2; el ala E la renderiza al final sin reabrir nada | | **D-COM.5** | Grupo **«Commerce»** en `_lib/catalog.ts`; slugs sin prefijo salvo colisión | `cart`, no `commerce-cart`; pero `commerce-header`, igual que `site-header` | **Dos cabos abiertos que resuelve la fase 0 de C2, no antes:** 1. ⚠️ **`$commerce` sería el primer art de DOMINIO.** Los 25 actuales son infraestructura transversal (`adom`, `color`, `motion`, `prefs`…). O se acepta explícitamente y se anota en `src/arts/README.md`, o se enmienda la doctrina de arts. **No se cuela en silencio.** `src/svrs/commerce` no sirve: B-4 no admite `$svrs` desde un block. 2. **El objeto de valor de dinero vive en una hoja por debajo del art y de eidos**, para evitar el ciclo — mismo patrón que el `ActorToken`, cuyo tipo vive bajo orca y agent mientras la autoridad de acuñación queda en `EngineAgent`. Candidato: `src/libs/money`, junto al `src/libs/currency` que ya existe. --- ## 3. Lo que el canon YA resuelve (no lo reinventes) Medido sobre los 167 slugs de `src/uix/eidos/components/`, 100 con pila completa morfo+soma+eidos: | Necesidad | Se compone con | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | Rejilla de producto | `AutoGrid` / **`CardGroup`** (ya es rejilla **seleccionable**) + `Card` + `AspectRatio` + `Image` + `Badge` | | Estrellas | **`RatingGroup`** (`allowHalf · hoverPreview · clearable · readonly · valueText`) | | Cantidad | **`NumberField`** (`min/max/step/largeStep · clampOnBlur · Increment/DecrementTrigger · Scrubber`) | | Dinero | **`FormatNumber`** (`formatStyle="currency"`) + `Text numeric="tabular"` | | **Divisa y conversión** | **`$format.currency`**: `setCurrency · format · formatAs · convert/convertAs` + `createRates({ fetchRate })`. **Ninguna referencia del mundo tiene esto** | | Galería | `Carousel` + `Image` + `AspectRatio` (falta zoom/lightbox → §4) | | Variantes | `RadioCards` · `ToggleGroup` · `ColorSwatch` (falta el eje de disponibilidad → §4) | | Checkout | **`Stepper`** (`linear`) + `Form` + `Field` + **`Form.AutoFields`** + esquema SIUM | | Carrito | `Drawer` + `Table`/`GridList` + `NumberField` + `EmptyState` | | Facetas | `Accordion` + `CheckboxGroup` + **`Slider type="multiple"`** (ES el rango de precio) + `TagGroup` + `Combobox` | | Pedidos | `Table` + `$libs/datagrid` — sorting multi/server · filtering · selection · pagination · visibility · pinning · row-detail. **Sin virtualización** | | Seguimiento | **`Timeline`** (`align="alternate"`, `Marker`, `Connector`) | | Stock | `Meter` (zonas `below/above/optimum`) + `Badge` | | Estados | `EmptyState · Result · Callout · Skeleton · Toast · Announce` — **el cuarteto completo, que ningún catálogo del mercado tiene** | | Envío/peso | `$format.units` (`length · mass · volume` + `convert`) | | Retail | `Barcode` (EAN-13/UPC-A/ITF-14…) + `QrCode` | | Búsqueda | `SearchField` (`debounceMs`, `loading`) · `Combobox` · `Command` | --- ## 4. Los 8 huecos de canon (fase C1) Criterio único: **superficie de contrato** (evento real, máquina, `data-*` que el CSS selecciona, obligación a11y de widget). Todo lo demás es composición y va al block. Cada uno entra por la ruta de 9 fases. | # | Componente | Por qué es canon | | ---- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | C1.1 | **`price`** | La ley vive en el tipo. Obligación a11y real: el tachado debe leerse «antes X, ahora Y», no «X Y» | | C1.2 | **`variant-picker`** | El ítem de mayor valor de la comparativa. **Tres ejes booleanos por opción** (`exists`/`available`/`selected`, nomenclatura de Hydrogen) → eidos pinta las cuatro celdas de la matriz desde CSS sin ramas JS | | C1.3 | **`quantity-field`** | Especialización fina **componiendo `NumberField`** (precedente sancionado: NumberField dentro de Knob). Aporta mínimo 1, **quitar al bajar de mínimo**, tope = stock, estado en vuelo, nombre accesible por producto | | C1.4 | **`facets`** | La máquina, no las vistas. Data-driven (precedente: Menubar, NavTree). **Con la live region que Algolia nunca shippeó** — su `stats` renderiza el conteo como texto plano | | C1.5 | **`lightbox`** | Ya era candidato de canon en F5 con disparador «cuando un block real de media/galería lo pida». Este es el disparador | | C1.6 | **`address-field`** | Data-driven sobre metadatos públicos de país: conjunto, **orden**, **etiquetas**, regex postal y token `autocomplete` (WCAG 1.3.5). Requiere `$libs/address` | | C1.7 | **`selection-set`** | Wishlist, comparar y lista de requisición B2B son **el MISMO proveedor**: conjunto sincronizado a URL, con tope, optimista. Construirlo una vez | | C1.8 | **`embedded-field`** | Envoltorio de iframe de proveedor. **Nunca un input de tarjeta** — el PAN no entra en nuestro DOM. Aporta la resolución de tokens del tema al otro lado del límite vía `eidos.resolveToken()` | **Enmiendas de canon, no componentes nuevos**: `RatingGroup` (hoy `readonly` conserva `role="radio"` y cada estrella es parada de tabulación — diez ratings en un listado son 50 paradas) · `Timeline`/`Stepper` (¿estado `error`? el seguimiento de un pedido no es camino feliz) · `Card` (elevación + el invariante del enlace de tarjeta entera) · `Badge` (`tone` que no dependa del color) · `Table` (virtualización, gap conocido). --- ## 5. Los 26 blocks **Ala A · descubrimiento (7)** — `commerce-header` · `product-grid` · `product-listing`◆ · `category-preview` · `promo-section` · `incentives` · `recommendation-slot`. **Ala B · ficha (6)** — `product-gallery` · `buy-box`◆ · `product-info` · `reviews`◆ · `product-qa`◆ · `product-compare`◆. **Ala C · carrito y pago (4)** — `cart`◆ · `order-summary`◆ · `checkout`◆ · `payment-methods`◆. **Ala D · post-venta (6)** — `order-confirmation` · `order-tracking` · `order-history` · `order-detail` · `returns`◆ · `guest-order-lookup`◆. **Ala E · agéntica (3)** — `cart-review`◆ · `cart-handoff`◆ · `purchase-authorization`◆. ◆ = coordina (máquina de estado + palabras). Son **14 de 26**. **Ala F · B2B: DIFERIDA con disparador** — `quote-request` · `requisition-list` · `quick-order-pad` · aprobación de PO · pago a crédito · conmutador de cuenta/sede. `selection-set` (C1.7) ya la deja medio construida. --- ## 6. El patrón de coordinación — cópialo literal `contact` y `newsletter` son los dos ejemplares vivos. La forma exacta: - **`state.ts`** — unión exhaustiva de estados (cada miembro con su línea de doc) · interfaz `…StateInput` con campos `readonly` · `resolve…State(input)` como escalera de guardas **cuyo ORDEN es la prioridad** · `can…(state): boolean` · **dos `Record` exhaustivos**: `…_REASON: Record` y `…_ACTION: Record`. Como la clave es el tipo, **añadir un estado es un error de tipos hasta que escribes su frase y su etiqueta**: un estado que bloquea sin decir por qué no se puede escribir. - **`context.ts`** — interfaz con **getters** (para que las lecturas sigan la fuente reactiva), `reasonId`, `t(ref)`; `Symbol` privado del módulo; `set…Context` / `get…Context` (el getter devuelve `| undefined` y cada parte guarda). - **`…-reason.svelte`** — pinta la frase **y la anuncia** por `uix.announce` (el segundo servicio que el contrato B sanciona), **sólo al cambiar**, con un `let announced = ''` que NO es `$state` (es un libro de efectos, no estado reactivo). `aria-describedby` es mecanismo **aparte y aditivo**: asociación, no anuncio, y se quieren los dos. - **`state.test.ts`** — asserts **estructurales**, no caso a caso: `ALL_STATES.filter(can…)` · «para todo estado bloqueante, `…_REASON` es truthy» · el regex del idlangref `/^#\?blocks\.\.[a-z.]+\|.+/`. - El estado se deriva **UNA vez en la raíz** y las partes lo leen. Ninguna parte recalcula; ninguna inventa su propio `disabled`. **Regla de forma del tier — no la re-decidas**: una parte compound se gana sólo si **se repite** (el app mapea sobre N) o **coordina** (lee el contexto). Si no hace ninguna de las dos, es un **slot de snippet** en la raíz. **Las palabras** se registran en `web/routes/blocks/_lib/blocks-langs.ts` bajo `blocks.{slug}.*` con `es`/`en`; el arnés las inyecta en `langs.schema` desde `_lib/BootUix.svelte`. Sin registrar, sale el fallback inglés y no se rompe nada. --- ## 7. Anatomía de fichero ```text src/uix/blocks/{kebab}/ ├── README.md # Function · Composition map · Decisions · Gaps │ # + el párrafo **Landmark + headings** (B-8) ├── index.ts # export compound + tipos ├── types.ts # props; contenido por snippets ├── {kebab}.svelte # composición: sólo canon, layout por componentes, sin .css ├── {kebab}-{parte}.svelte └── (si coordina) state.ts · state.test.ts · context.ts web/routes/blocks/{kebab}/ ├── +page.svelte # BlockDemo + controles vivos + pestañas de doc ├── {Name}Site.svelte # el block dentro de una página de contenido REAL └── preview/ ├── +layout@.svelte # `@` resetea el layout; BootUix; lee mode/dir/lang de la URL └── +page.svelte # sirve {Name}Site desde searchParams ``` Y **flipar `shipped: true`** en `web/routes/blocks/_lib/catalog.ts`, dentro del grupo «Commerce». El guard parsea ese fichero **por regex** (`{ slug: '…', … shipped: true }`) y comprueba las **dos direcciones**: un block sin ficha es invisible para el raíl; un slug publicado sin block es un enlace muerto. Los tipos: la raíz extiende `Omit, 'children'>`; **toda otra parte extiende los props del componente de canon que envuelve**, nunca `HTMLAttributes` (los atributos crudos chocan con el `style`/`class` refinados del canon). Cuando un slot comparte nombre con un atributo HTML (`title`, `media`, `width`), se hace `Omit` — «la lección del hero». --- ## 8. Verificación **Por componente de canon**: `npm run component:audit --only {kebab}` PASS · `npm run morfo:check` · `npm run morfo:vocabulary` · `eidos-lint` · `npx vitest run src/uix/eidos` · `npm run perm:check` · `npm run rtl:check`. **Por block**: `npm run blocks:check` verde · `npx vitest run src/uix/blocks` · `npm run check` **sin regresión sobre baseline** (medir antes y después: el baseline se mueve) · `npm run docs:check` · prettier. **Navegador de verdad, y mirado**: claro y oscuro (`colorScheme: 'dark'`), 375 y 1280, **y RTL**. Sonda de audio + `MutationObserver` de `data-event*` tras la puerta de hidratación honesta, **con un control en el mismo entorno** — sin control, una latencia no es un veredicto. **Acceptance propia de la familia**, además del contrato B: 1. **Cero cadenas horneadas.** La lista de ~20 props traducibles que `ProductDetail` de BigCommerce Catalyst exige es la checklist: ninguna de ellas existe aquí. 2. **El cuarteto de estados** en todo block con datos: poblado · cargando · vacío · error. 3. **Ningún estado bloqueante mudo** — la propiedad ya está fijada por test en `contact`/`newsletter`; se hereda. 4. **Tabla de cumplimiento por block**, generada del morfo: qué criterios WCAG y qué artículos satisface. 5. **Página compuesta de integración** por ala (listado → PDP → carrito → checkout → confirmación). --- ## 9. Lo que muerde específicamente en commerce - **RTL es EL eje donde commerce rompe**, y es un foso real: de doce catálogos estudiados, exactamente uno anuncia RTL y no tiene ecommerce. Duele en el orden antes/ahora del precio, la colocación del símbolo de divisa, los `−`/`+` del stepper que **no** deben espejarse, el `scrollLeft` negativo del carrusel y los chips de filtro. - **Todo evento con consecuencia monetaria es `commit` con intent real**: añadir = positivo, quitar = negativo con deshacer, seleccionar agotado = `signal-threat`. Y el stepper suena por **repetición** (`step` por emisión), así que la velocidad del gesto ES la velocidad del trinquete. - **`Form.Submit` estampa su propio `aria-label` genérico**, que gana la computación del nombre accesible: un botón con palabra de estado DEBE pasar el suyo explícito o incumple WCAG 2.5.3. Medido en `contact` (A-04). - **Bajo validación `progressive` se pregunta al ESQUEMA** (`validateSync`), nunca a `form.isValid` — ahí significa «aún no se ha encontrado nada mal», y un formulario vacío se declara válido. Y se **deja pasar `invalid`** al submit, o el camino de error por campo es inalcanzable por construcción. - **Deuda heredada que commerce va a encontrar otra vez**: las 27 filas todavía CONFIRMADAS de [`AUDIT-blocks-ledger.md`](./AUDIT-blocks-ledger.md) y los 8 hallazgos de canon congelados (F15–F22 en [`PLAN-blocks-quality.md`](./PLAN-blocks-quality.md)). Muerden aquí en concreto: `Card` sin prop de elevación, la ranura `contrast` blanca sobre todo escalón sólido, y el `aria-label` incondicional del canon. - **Solape con F3**: `data-table` (F3.3) y `wizard` (F3.8) son parientes de `order-history` y `checkout`. No bloquea — commerce compone `Table` + `$libs/datagrid` y `Stepper` directamente, igual que haría F3 — pero cuando F3 llegue habrá que decidir si `order-history` **es** `data-table` configurado o su hermano. --- ## 10. Higiene de la comparativa El dossier de referencia lleva **marcas de verificación** y hay que respetarlas, por la misma razón que existe el ledger: - **Verificado**: el inventario de Tailwind Plus Ecommerce (transcrito categoría a categoría) · la ausencia total de commerce en Untitled UI, en los headless y en los design systems · el desglose de Storefront UI (26 componentes npm / 24 bloques copy-paste) · la ausencia de blocks de ecommerce en **todo** el ecosistema Svelte · las obligaciones legales de la UE. - **NO verificado, y por tanto no citable**: las cifras por categoría de Flowbite (su web es una SPA que devuelve cuerpo vacío) · todas las estadísticas de Baymard (corpus de pago) · la penalización de conversión del checkout en chat · los nombres de campo concretos de los esquemas de AP2. **Regla**: una cifra sin fuente leída no entra en una tabla comparativa ni justifica alcance por sí sola. Y **nunca se cita el total auto-declarado de un catálogo** — se publica la enumeración y el número se deriva de ella. Se observó dos veces que el total declarado y el enumerado no coinciden, y aplica también a nuestras propias cifras. --- ## 11. Lo siguiente **C0**, en este orden: 1. Escribir el dossier `docs/process/RESEARCH-commerce-references.md` con las 7 pistas y las marcas de §10. 2. Escribir el plan de ejecución `docs/process/PLAN-blocks-commerce.md` con la ficha por ítem (Función · Compone · API · Layout/landmark · v1 · Demo). 3. Añadir el grupo «Commerce» a `web/routes/blocks/_lib/catalog.ts` con los 26 slugs en `shipped: false`. 4. Registrar la iniciativa en `docs/next-features.md` (+ el ala B2B diferida con su disparador). Luego **C2** (el art `$commerce`, cuya fase 0 resuelve los dos cabos de §2), y sólo entonces **C1** — sus tipos los consume todo lo demás.