---
name: ecomiq-storefront-themes
description: Crea, genera y edita la tienda online de Ecomiq — un proyecto React Router 8 + Tailwind CSS v4 + shadcn con el SDK @ecomiq/storefront. Úsalo para diseñar una tienda nueva, agregar secciones o páginas, cambiar colores, tipografías y textos, conectar productos y colecciones, o bajar y publicar la tienda con la CLI `ecomiq` (login, init, dev, pack, publish).
---

# Tienda online de Ecomiq

Cada tienda es un proyecto web estándar: React Router 8 en modo framework,
Tailwind CSS v4 y componentes shadcn. Los datos (productos, precios, stock,
carrito, cuenta) vienen de Ecomiq por el SDK `@ecomiq/storefront`; el diseño
es tuyo.

- Guía para crear un tema: https://docs.ecomiq.pe/es/docs/guides/storefront/themes
- Guía de la API de la tienda: https://docs.ecomiq.pe/es/docs/storefront

## Estructura

```text
app/
  root.tsx               layout raíz
  routes.ts              mapa de rutas
  routes/                páginas: home, products, product, collection, cart, checkout, account…
  components/
    sections/            secciones editables (hero, featured-products…)
    ui/                  componentes shadcn compartidos
  app.css                variables del tema (:root) y Tailwind
public/images/           imágenes del tema (webp)
```

- `vite.config.ts`, `react-router.config.ts`, `workers/app.ts` y
  `wrangler.jsonc` son de la plataforma: no los edites. El editor usa los
  suyos, pero `ecomiq publish` compila con tus copias.
- No agregues en la raíz `postcss.config.*`, `tailwind.config.*`,
  `babel.config.*`, `.babelrc`, `.postcssrc` ni `vitest.config.*`.
- `app/routes.ts` solo usa `index`, `route`, `layout` y `prefix` de
  `@react-router/dev/routes`, con valores literales.
- El CSS no usa `@plugin` ni `@config`.
- Cada archivo pesa hasta 1 MB y el tema tiene hasta 2000.
- Imports con `~/` para `app/`. Componentes shadcn en `~/components/ui`.
- Usa solo las dependencias que ya trae el proyecto (React Router, Tailwind,
  shadcn/Radix, embla-carousel, sonner, vaul, zod y las librerías de íconos
  instaladas). No agregues paquetes.

## Rutas principales

`/` inicio · `/products` catálogo · `/products/:handle` ficha ·
`/collections` y `/collections/:handle` · `/pages/:handle` · `/cart` ·
`/checkout` y `/checkout/done` · `/account/*` (login, registro, pedidos,
direcciones, perfil) · `/blog` · `/sellers` (marketplace) · `*` resuelve URLs
propias de la tienda.

## Datos: siempre del SDK

En el `loader` de cada ruta, `store(context)` da el cliente de la tienda:

```ts
import { pickProducts, store } from "@ecomiq/storefront";
import type { Route } from "./+types/home";

export async function loader({ context }: Route.LoaderArgs) {
  const client = store(context);
  const products = await pickProducts(client, { collection: "novedades", limit: 8 });
  return { products };
}
```

- Ficha: `loadProduct(request, context)`; categoría: `loadCategory`; URLs
  propias: `resolveUrl`, `canonicalUrl`, `requireCanonical`.
- Carrito: `loadCart`, `cartAction`; checkout: `useCheckout`; cuenta:
  `customer`, `requireCustomer`, `CustomerLoginForm`,
  `CustomerRegistrationForm`; precios: `money`.
- Enlaces internos con `StorefrontLink` (respeta la estructura de URLs de la
  tienda).
- Todo formulario `post` lleva `<CustomerCsrfField />`. Sin él, el SDK
  rechaza con 403 el envío de un cliente con sesión.
- Nunca escribas a mano precios, stock, productos ni textos del catálogo.
- No cambies loaders, acciones, carrito, checkout ni sesión salvo que el pedido
  lo exija. El SDK decide qué pasa; el tema, cómo se ve.

## Cargar datos sin esperas

Cada lectura que espera el resultado de otra suma un viaje a la API. Desde
`@ecomiq/storefront` 3.3.0, el SDK manda en una sola consulta las lecturas que
se piden a la vez.

- En un loader, pide juntas con `Promise.all` las lecturas que no dependen
  entre sí. No encadenes un `await` tras otro.
- Si hay un respaldo (la colección elegida viene vacía → los más vendidos),
  pide las dos listas a la vez y elige después.
- Productos de una colección: `pickProducts(client, { collection, limit })`
  directo, sin leer antes `getCollections()`.
- Colecciones con foto de portada: `client.getCollectionCovers({ limit })` y
  `withImages(client, colecciones)`. La foto llega con la colección.
- La primera colección, o las que sobran, con sus productos:
  `client.getCollectionsWithProducts({ limit, products, sort })`.
- Reseñas de cada producto: `pickProductsWithReviews(client, fuente, { limit,
  sortBy: "rating", sortDirection: "desc" })`.
- Vendedores: `client.getSettings()` y `client.getSellers()` a la vez; en una
  tienda que no es marketplace, `getSellers()` da una lista vacía.

```ts
const [chosen, bestSelling] = await Promise.all([
  pickProducts(client, { collection: featured.collection, limit: 8 }).catch(() => []),
  pickProducts(client, { limit: 8, sort: "best_selling" }).catch(() => []),
]);
const products = chosen.length > 0 ? chosen : bestSelling;
```

## Secciones y ajustes editables

Cada sección exporta `settings`. El editor de Ecomiq los muestra para que el
comerciante cambie textos, imágenes y productos sin tocar código.

```tsx
import { setting, StorefrontLink as Link } from "@ecomiq/storefront";
import { Button } from "~/components/ui/button";

export const settings = {
  hero: {
    title: setting.text("Piezas que se quedan contigo", "Título"),
    image: setting.image("/images/hero-1.webp", "Imagen"),
    cta: setting.text("Ver novedades", "Botón"),
    link: setting.link("/products?sort=newest", "Enlace del botón"),
  },
};

export function Hero() {
  return (
    <section className="mx-auto max-w-7xl px-4 py-12 md:px-8">
      <h1 className="text-5xl md:text-7xl">{settings.hero.title}</h1>
      <Button asChild size="lg">
        <Link to={settings.hero.link}>{settings.hero.cta}</Link>
      </Button>
    </section>
  );
}
```

- Tipos de ajuste: `setting.text`, `image`, `link`, `collection`, `category`,
  `product`, `products`, `number`, `toggle`. El segundo argumento es la
  etiqueta que ve el comerciante.
- Agrupa por sección (`settings.<grupo>.<campo>`) y usa nombres cortos.
- Los productos de una sección salen de `pickProducts` con la colección,
  categoría o productos del ajuste.

## Estilo

- Solo clases Tailwind. Colores, radios y fuentes salen de las variables de
  `:root` en `app/app.css` (`--primary`, `--background`, `--foreground`,
  `--secondary`, `--accent`, `--muted`, `--border`, `--radius`, `--heading`,
  `--body`): usa `bg-primary`, `text-muted-foreground`, `border-border`,
  `rounded-lg`. No definas colores propios fuera de esas variables.
- Para un tono puntual usa la escala de Tailwind (`bg-stone-100`); nunca
  valores fijos como `bg-[#ff0000]`.
- Móvil primero: clases base para móvil, `md:` para escritorio.
- Para igualar anchos, reutiliza el contenedor del tema (`max-w-… mx-auto` con
  su padding); no uses anchos en píxeles.
- Lo que está en `app/components` (cabecera, pie, tarjeta de producto) se
  repite en toda la tienda; cambiarlo afecta a todas las páginas.

## Imágenes

- Guárdalas en `public/images/` en webp, de menos de 1 MB, y refiérelas como
  `/images/<archivo>`.
- Nunca enlaces imágenes externas: viajan con el tema a los archivos de la
  tienda.
- Toda imagen lleva `alt`.

## Textos

- Acciones de comercio literales: «Agregar al carrito», «Comprar ahora»,
  «producto(s)». La personalidad va en títulos y textos editoriales.
- Marketplace: las secciones de vendedores solo se muestran si
  `store.type === "marketplace"`.

## Probar y publicar

Con la CLI `ecomiq` (`npm install -g ecomiq`, Node 22.22 o superior):

```bash
ecomiq login
ecomiq init mi-tienda
cd mi-tienda && npm install
ecomiq dev
ecomiq pack
ecomiq publish
ecomiq publish --live
```

- `login` abre el navegador: la persona inicia sesión, elige la tienda y
  autoriza. No le pidas contraseñas, tokens ni llaves. `logout` cierra la
  sesión en el equipo y en Ecomiq.
- `init` baja el código del editor de la tienda, con los archivos de la
  plataforma, a una carpeta nueva.
- `dev` levanta la tienda en local con sus datos reales.
- `pack` construye el tema y lo guarda como `<carpeta>.zip` junto al proyecto,
  para importarlo en Tienda online → Personalizar tienda → Tema → Cambiar →
  Importar ZIP. Importar reemplaza el código del editor y se puede deshacer.
- `publish` construye y sube una vista previa, también con datos reales;
  `--live` además la publica. Antes de `--live`, comparte la vista previa y
  espera el sí de la persona: cambia lo que ven sus clientes.
- Publicar con la CLI no cambia el borrador del editor de Ecomiq. `--live`
  solo copia tu código al editor si el editor está vacío.
- `publish` y `pack` no suben `.env*`, `.dev.vars`, `node_modules`, `build`,
  los `.map` del build ni los archivos ocultos de la raíz salvo `.gitignore`.
  Igual, nunca escribas llaves ni secretos en el código del tema.
- Mantén TypeScript válido: si `dev` o `publish` fallan, corrige antes de
  seguir.

## Crear una tienda desde cero

1. Baja el proyecto de la tienda con `ecomiq init` y conserva rutas, loaders y
   SDK.
2. Define la marca en `:root` de `app/app.css`: colores, `--radius` y
   fuentes.
3. Rediseña `app/components/sections/*` y las piezas compartidas con Tailwind y
   shadcn, declarando `settings` para todo texto o imagen que el comerciante
   deba poder cambiar.
4. Revisa móvil y escritorio, el catálogo con filtros, la ficha con variantes,
   el carrito, el checkout y la cuenta.
5. Publica una vista previa con `ecomiq publish` y, cuando la persona la
   apruebe, `ecomiq publish --live`.
