Store — Plataforma de e-commerce (catálogo, órdenes, cupones, blog y constructor de páginas con Puck). Sigue siendo un submodule (
platform/store). ⬆ índice
Store
[!NOTE] Para el flujo de despliegue remoto y ARM (sd2) ver
docs/internal/operations/deployment.mdyHANDOFF-sd2-aitha.md.
Qué es
Store es la tienda online de la plataforma: multi-tenant (una tienda por tenant), con catálogo, carrito/checkout, órdenes, cupones, blog, banners y un constructor de páginas visual (Puck) para armar la portada y el contenido del sitio. Vive en un submodule propio (platform/store, rama main); se empaqueta como remote de Module Federation consumido por el portal.
Arquitectura
- Next.js 16 (App Router,
output: 'standalone') + Drizzle ORM sobre Postgres centralizado (postgres-central, DBstore_db). - Todo el app (páginas Y API) se sirve bajo el basePath
/store(STORE_BASE_PATH, horneado en build). - El admin es un remote de Module Federation (
webpack.mf.config.js) que el portal embebe; la sesión del navegador vive en el portal (modo "embebido"). - El storefront público se sirve en
{portal}/store/{tenant}/...o en un dominio propio del tenant (reescritura por Host vía/api/tenant-by-host). - Contenedor: base
playwright(necesario para tests de logout), imagen standalone. Puerto interno127.0.0.1:3050.
Modelo de datos (resumen)
Fuente de verdad: platform/store/src/db/schema.ts. Principales:
| Área | Tablas |
|---|---|
| Tenant | stores (tenantId, zitadelOrgId, customDomain, publicEnabled, onboarding) |
| Catálogo | products, product_images, product_variants, product_currency_prices, external_listings, categories, product_categories, tags, product_tags |
| Carrito/checkout | carts, cart_items, checkout_sessions, payment_gateways, payment_configs, payment_events |
| Órdenes | orders, order_items, order_payments |
| Cupones | coupons, coupon_codes, coupon_products, coupon_categories, coupon_assignments, coupon_redemptions |
| Clientes | customers, customer_addresses, customer_notifications |
| CMS (Puck) | site_pages, site_page_versions, site_regions, site_menus, posts, post_categories, post_category_links |
| Config/analytics | settings, banners, integrations, sync_jobs, sync_outbox, visit_daily, visit_sessions |
Modelo de tenants y auth
- Tenant por header
X-Active-Tenant: el middleware lo inyecta desde cookieactive_tenant, querytenant_ido Referer; en dominio propio se deduce del Host (/api/tenant-by-host, solo sipublicEnabled). - Admin (modo embebido): el navegador entra por el portal (gate
requiredRole: 'store'). La API admin (/api/admin/**) valida además, si llega unAuthorization: Bearer, el token contra Zitadel (src/lib/zitadel-introspect.ts):- token de usuario con rol
store/store-owner/internal-assistant→ tenant derivado del org del token (stores.zitadel_org_id); elX-Active-Tenantdel cliente se ignora. - token de servicio (chat-server) → confía en
X-Active-Tenant. - sin Bearer → comportamiento embebido histórico (solo header), control delegado al portal.
- token de usuario con rol
- Comprador (storefront): NextAuth por tenant (Zitadel),
requireCustomer()lee la cookiestore-session.{tenant}y resuelvecustomerspor(tenantId, zitadelSub).
Constructor de páginas (Puck)
- Configs en
src/puck/config.tsx(puckConfig,regionConfig,cartPageConfig,checkoutPageConfig,productPageConfig,contactPageConfig); bloques ensrc/puck/blocks/*.tsx; campos custom ensrc/puck/{style-fields,data-fields,code-editor}.tsx. - Persistencia:
site_pages.draft_data/published_data(JSON Puck{root, content, zones}),site_page_versions(historial),site_regions(header/footer),site_menus. - Render público:
app/[tenant]/[...slug]/page.tsx+PuckRender; preview con token HMAC. - Catálogo de componentes vía API:
GET /api/admin/site/componentsdevuelve la estructura (labels/fields/options/defaults) de todos los bloques de cada config, más la documentación extraída de los JSDoc del código. Es la fuente de verdad para que un LLM arme páginas.
Documentación de la API (generada del código)
La doc se genera del JSDoc del código, no se edita a mano:
scripts/generate-api-docs.mjs(TypeScript compiler API) lee los comentarios de los bloques Puck y de los route handlers, inventaríasrc/app/api/**/route.tsy produce:src/generated/openapi.json— spec OpenAPI 3 (servida enGET /api/openapi.json).src/generated/puck-component-docs.json— JSDoc de bloques (fusionado en/api/admin/site/components).docs/generated/api-reference.mdydocs/generated/puck-components.md.
- Regeneración:
platform/store/run.sh docs(o./infra run store docs) y automáticamente enpredev/prebuild. - Regla: tocar la doc = editar el JSDoc en el código y regenerar. Los schemas de request/response que un comentario no expresa viven en
src/config/api-docs.json.
Despliegue
- Proyecto estándar (
project.confPRIORITY 40, DEPENDS_ON user-service)../infra up|restart|logs|clean store. - OJO ARM (sd2): imágenes con
--remote-build. VerHANDOFF-sd2-aitha.md. - OJO migraciones:
entrypoint.shhacedrizzle-kit migrate || echo "..."— traga fallos de migración (el contenedor arranca con schema viejo y luego 500s). Tras tocar schema, validar el conteo dedrizzle.__drizzle_migrationscontra_journal.json. - Las credenciales de pago se cifran AES-GCM con
STORE_SECRETS_KEY.
Tools de la tienda (MCP + internal tools = una sola implementación)
Las tools de la tienda viven en store, no en el MCP ni en chat-server:
- Catálogo:
platform/store/src/lib/tools/catalog.ts— una única lista (STORE_TOOLS) con id, descripción, inputSchema yrequiredRolespor tool, más cómo construir la llamada a la API admin. - Endpoints:
GET /api/internal-tools(listar catálogo) yPOST /api/internal-tools/{id}/execute(ejecutar). El execute resuelve la identidad Zitadel del caller (requireToolSession), verifica el rol requerido por la tool y llama a las propias rutas/api/admin/***de la tienda (self-fetch con el tenant resuelto). Una única implementación. - MCP de store (
infrastructure/mcp_servers/store/server.py): es un puente fino. En el arranque descarga el catálogo y registra una tool MCP por entrada; cada ejecución reenvía el Bearer del usuario aPOST /api/internal-tools/{id}/execute. Sin lógica duplicada. - Internal tools de chat-server (
app/features/tools/builtin/store.py): también un puente fino. Al arrancar descarga el catálogo y registra las mismas tools con idstore_<id>; cada una llama al execute de store con el access token del usuario. chat-server solo orquesta. - Verificación de rol: en store (
requiredRolesde cada tool, contra la introspección del token). Los tokens de servicio solo pueden ejecutar tools globales (catálogos).
Regla: si falta una tool, se agrega una sola vez en catalog.ts; MCP e internal tools la toman automáticamente. GET /store/api/internal-tools es la referencia viva del set.
Gotchas
src/collections/,src/blocks/,src/app/(payload)son vestigios vacíos (un intento de Payload CMS que nunca existió). Ignorar.src/puck/fields/está vacío: los fields custom viven enstyle-fields.tsx,data-fields.tsx,code-editor.tsx,embed-block.tsx,nav-blocks.tsx.- Submodule: commitear en el submodule y luego bump del puntero en el padre (ver reglas de submodule en el AGENTS raíz).