@unbox-plus/cli 0.20.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/LICENSE.md +46 -0
  2. package/README.md +175 -0
  3. package/bin/cli.js +439 -0
  4. package/package.json +43 -0
  5. package/src/colors.js +100 -0
  6. package/src/presets.js +241 -0
  7. package/src/theme.js +253 -0
  8. package/template/.claude/agents/assets-marca.md +40 -0
  9. package/template/.claude/agents/avaliador-visual.md +48 -0
  10. package/template/.claude/agents/branding-briefing.md +401 -0
  11. package/template/.claude/agents/conteudo-secao.md +46 -0
  12. package/template/.claude/agents/paginas-legais.md +44 -0
  13. package/template/.claude/settings.json +3 -0
  14. package/template/.claude/skills/frontend-design/ATTRIBUTION.md +3 -0
  15. package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
  16. package/template/.claude/skills/frontend-design/SKILL.md +55 -0
  17. package/template/.claude/skills/web-design-guidelines/ATTRIBUTION.md +3 -0
  18. package/template/.claude/skills/web-design-guidelines/SKILL.md +50 -0
  19. package/template/.claude/skills/web-design-guidelines/guidelines-snapshot.md +180 -0
  20. package/template/.env.example +158 -0
  21. package/template/.mcp.json.example +14 -0
  22. package/template/.nvmrc +1 -0
  23. package/template/CLAUDE.md +333 -0
  24. package/template/COVERAGE.md +80 -0
  25. package/template/DEPLOY.md +179 -0
  26. package/template/QA.md +108 -0
  27. package/template/README.md +152 -0
  28. package/template/agents/CONSTRUCAO.md +207 -0
  29. package/template/agents/MANAGER.md +192 -0
  30. package/template/agents/PADROES.md +255 -0
  31. package/template/agents/definitions/00-scaffold.md +56 -0
  32. package/template/agents/definitions/01-layout.md +47 -0
  33. package/template/agents/definitions/02-homepage.md +70 -0
  34. package/template/agents/definitions/03-catalog.md +86 -0
  35. package/template/agents/definitions/04-pdp.md +97 -0
  36. package/template/agents/definitions/05-cart.md +116 -0
  37. package/template/agents/definitions/06-checkout.md +146 -0
  38. package/template/agents/definitions/07-auth.md +130 -0
  39. package/template/agents/definitions/08-customer.md +55 -0
  40. package/template/agents/definitions/09-promotions.md +62 -0
  41. package/template/agents/definitions/10-feedback.md +128 -0
  42. package/template/agents/definitions/11-seo-infra.md +159 -0
  43. package/template/agents/definitions/12-deploy.md +173 -0
  44. package/template/agents/definitions/13-cro.md +275 -0
  45. package/template/agents/definitions/14-seo.md +388 -0
  46. package/template/agents/definitions/15-branding.md +26 -0
  47. package/template/agents/definitions/16-qa-visual.md +138 -0
  48. package/template/agents/definitions/17-aeo.md +257 -0
  49. package/template/app/(loja)/busca/page.tsx +72 -0
  50. package/template/app/(loja)/carrinho/oferta/page.tsx +57 -0
  51. package/template/app/(loja)/carrinho/page.tsx +160 -0
  52. package/template/app/(loja)/categoria/[tagSlug]/layout.tsx +31 -0
  53. package/template/app/(loja)/categoria/[tagSlug]/loading.tsx +17 -0
  54. package/template/app/(loja)/categoria/[tagSlug]/page.tsx +49 -0
  55. package/template/app/(loja)/checkout/page.tsx +80 -0
  56. package/template/app/(loja)/checkout/pix/[ref]/page.tsx +133 -0
  57. package/template/app/(loja)/conta/assinaturas/[referenceId]/page.tsx +112 -0
  58. package/template/app/(loja)/conta/assinaturas/page.tsx +51 -0
  59. package/template/app/(loja)/conta/enderecos/page.tsx +20 -0
  60. package/template/app/(loja)/conta/entrar/page.tsx +119 -0
  61. package/template/app/(loja)/conta/page.tsx +45 -0
  62. package/template/app/(loja)/conta/pedidos/[referenceId]/page.tsx +49 -0
  63. package/template/app/(loja)/conta/pedidos/page.tsx +104 -0
  64. package/template/app/(loja)/conta/preferencias/page.tsx +24 -0
  65. package/template/app/(loja)/devolucoes/page.tsx +36 -0
  66. package/template/app/(loja)/layout.tsx +22 -0
  67. package/template/app/(loja)/oferta/page.tsx +57 -0
  68. package/template/app/(loja)/page.tsx +86 -0
  69. package/template/app/(loja)/pedido/[referenceId]/page.tsx +78 -0
  70. package/template/app/(loja)/privacidade/page.tsx +113 -0
  71. package/template/app/(loja)/produto/[productSlug]/layout.tsx +23 -0
  72. package/template/app/(loja)/produto/[productSlug]/loading.tsx +20 -0
  73. package/template/app/(loja)/produto/[productSlug]/page.tsx +372 -0
  74. package/template/app/(loja)/produtos/loading.tsx +16 -0
  75. package/template/app/(loja)/produtos/page.tsx +33 -0
  76. package/template/app/(loja)/termos/page.tsx +104 -0
  77. package/template/app/acesso/page.tsx +129 -0
  78. package/template/app/api/account/addresses/route.ts +39 -0
  79. package/template/app/api/account/exists/route.ts +21 -0
  80. package/template/app/api/account/me/route.ts +25 -0
  81. package/template/app/api/account/otp/route.ts +26 -0
  82. package/template/app/api/account/preferences/route.ts +19 -0
  83. package/template/app/api/account/signin/route.ts +27 -0
  84. package/template/app/api/account/signout/route.ts +9 -0
  85. package/template/app/api/acesso/route.ts +171 -0
  86. package/template/app/api/capi/route.ts +34 -0
  87. package/template/app/api/cart/coupon/route.ts +49 -0
  88. package/template/app/api/cart/items/route.ts +45 -0
  89. package/template/app/api/cart/link/route.ts +69 -0
  90. package/template/app/api/cart/route.ts +113 -0
  91. package/template/app/api/cart/share/route.ts +21 -0
  92. package/template/app/api/cep/[code]/route.ts +18 -0
  93. package/template/app/api/checkout/address/route.ts +24 -0
  94. package/template/app/api/checkout/email/route.ts +41 -0
  95. package/template/app/api/checkout/installments/route.ts +16 -0
  96. package/template/app/api/checkout/route.ts +110 -0
  97. package/template/app/api/checkout/shipping/route.ts +65 -0
  98. package/template/app/api/checkout-destination/route.ts +31 -0
  99. package/template/app/api/order/[ref]/route.ts +17 -0
  100. package/template/app/api/payment-link/route.ts +28 -0
  101. package/template/app/api/revalidate/route.ts +52 -0
  102. package/template/app/api/shipping/quote/route.ts +29 -0
  103. package/template/app/api/subscriptions/[id]/route.ts +52 -0
  104. package/template/app/api/track/route.ts +28 -0
  105. package/template/app/api/unbox/catalogo/route.ts +136 -0
  106. package/template/app/api/unbox/paginas/route.ts +167 -0
  107. package/template/app/api/unbox/vitrine/route.ts +84 -0
  108. package/template/app/api/webhooks/unbox/route.ts +97 -0
  109. package/template/app/apple-icon.svg +5 -0
  110. package/template/app/error.tsx +15 -0
  111. package/template/app/globals.css +457 -0
  112. package/template/app/icon.svg +4 -0
  113. package/template/app/layout.tsx +90 -0
  114. package/template/app/llms.txt/route.ts +43 -0
  115. package/template/app/manifest.ts +21 -0
  116. package/template/app/not-found.tsx +18 -0
  117. package/template/app/opengraph-image.tsx +35 -0
  118. package/template/app/robots.ts +31 -0
  119. package/template/app/sitemap.ts +42 -0
  120. package/template/bootstrap.sh +87 -0
  121. package/template/components/account/account-shell.tsx +47 -0
  122. package/template/components/account/address-book.tsx +147 -0
  123. package/template/components/account/preferences-form.tsx +77 -0
  124. package/template/components/account/reorder-button.tsx +47 -0
  125. package/template/components/account/signout-button.tsx +20 -0
  126. package/template/components/account/subscription-actions.tsx +204 -0
  127. package/template/components/account-nav.tsx +41 -0
  128. package/template/components/address-fields.tsx +102 -0
  129. package/template/components/analytics/data-layer-ready.tsx +16 -0
  130. package/template/components/analytics/purchase-tracker.tsx +32 -0
  131. package/template/components/brand-search.tsx +34 -0
  132. package/template/components/cart/cart-provider.tsx +340 -0
  133. package/template/components/cart/mini-cart.tsx +228 -0
  134. package/template/components/cart-button.tsx +35 -0
  135. package/template/components/catalog/catalog-client.tsx +523 -0
  136. package/template/components/catalog/grid-skeleton.tsx +19 -0
  137. package/template/components/catalog/pager.tsx +66 -0
  138. package/template/components/catalog/product-grid-card.tsx +52 -0
  139. package/template/components/catalog/product-grid.tsx +27 -0
  140. package/template/components/checkout/checkout-client.tsx +1333 -0
  141. package/template/components/chrome/announce-bar.tsx +57 -0
  142. package/template/components/chrome/chrome-recipe.ts +13 -0
  143. package/template/components/chrome/footers/captura-botao.tsx +28 -0
  144. package/template/components/chrome/footers/colunas.tsx +110 -0
  145. package/template/components/chrome/footers/conversao.tsx +90 -0
  146. package/template/components/chrome/footers/editorial.tsx +98 -0
  147. package/template/components/chrome/footers/minimal.tsx +55 -0
  148. package/template/components/chrome/header-bar-mobile.tsx +64 -0
  149. package/template/components/chrome/headers/centralizado.tsx +71 -0
  150. package/template/components/chrome/headers/classico.tsx +64 -0
  151. package/template/components/chrome/headers/compacto.tsx +66 -0
  152. package/template/components/chrome/headers/equilibrado.tsx +73 -0
  153. package/template/components/chrome/headers/imersivo.tsx +62 -0
  154. package/template/components/chrome/registry.ts +70 -0
  155. package/template/components/chrome/solid-on-scroll.tsx +39 -0
  156. package/template/components/empty-state.tsx +22 -0
  157. package/template/components/home/combo-card-compact.tsx +67 -0
  158. package/template/components/home/combos-home.tsx +64 -0
  159. package/template/components/home/combos-section.tsx +177 -0
  160. package/template/components/home/home-recipe.ts +41 -0
  161. package/template/components/home/sections/attributes-marquee.tsx +178 -0
  162. package/template/components/home/sections/benefits.tsx +80 -0
  163. package/template/components/home/sections/bloco-html.tsx +47 -0
  164. package/template/components/home/sections/catalogo.ts +165 -0
  165. package/template/components/home/sections/category-pills.tsx +32 -0
  166. package/template/components/home/sections/combos-carousel.tsx +141 -0
  167. package/template/components/home/sections/comparison.tsx +73 -0
  168. package/template/components/home/sections/founder-story.tsx +40 -0
  169. package/template/components/home/sections/hero.tsx +187 -0
  170. package/template/components/home/sections/kits.tsx +11 -0
  171. package/template/components/home/sections/media-cards.tsx +70 -0
  172. package/template/components/home/sections/newsletter.tsx +62 -0
  173. package/template/components/home/sections/product-showcase.tsx +175 -0
  174. package/template/components/home/sections/purchase-hero.tsx +187 -0
  175. package/template/components/home/sections/quote-banner.tsx +50 -0
  176. package/template/components/home/sections/registry.ts +186 -0
  177. package/template/components/home/sections/reviews-carousel.tsx +66 -0
  178. package/template/components/home/sections/reviews.tsx +68 -0
  179. package/template/components/home/sections/ritual.tsx +96 -0
  180. package/template/components/home/sections/savings.tsx +47 -0
  181. package/template/components/home/sections/social-row.tsx +65 -0
  182. package/template/components/home/sections/spec-table.tsx +117 -0
  183. package/template/components/home/sections/stars.tsx +12 -0
  184. package/template/components/home/sections/stats-grid.tsx +54 -0
  185. package/template/components/home/sections/trust-bar.tsx +56 -0
  186. package/template/components/home/sections/trust-strip.tsx +40 -0
  187. package/template/components/home/sections/video-wall.tsx +59 -0
  188. package/template/components/landing/landing-recipe.ts +20 -0
  189. package/template/components/landing/oferta-sections.tsx +40 -0
  190. package/template/components/landing/product-picker.tsx +257 -0
  191. package/template/components/mobile-nav.tsx +71 -0
  192. package/template/components/order-status.tsx +163 -0
  193. package/template/components/powered-by-unbox.tsx +27 -0
  194. package/template/components/product/pdp/buy-box.tsx +604 -0
  195. package/template/components/product/pdp/catalog-grid.tsx +113 -0
  196. package/template/components/product/pdp/faq-modelo.ts +86 -0
  197. package/template/components/product/pdp/gallery.tsx +87 -0
  198. package/template/components/product/pdp/interactive.tsx +200 -0
  199. package/template/components/product/pdp/newsletter.tsx +64 -0
  200. package/template/components/product/pdp/payment-chips.tsx +22 -0
  201. package/template/components/product/pdp/pdp-view.tsx +227 -0
  202. package/template/components/product/pdp/recommendations.tsx +144 -0
  203. package/template/components/product/pdp/sections.tsx +173 -0
  204. package/template/components/quantity-stepper.tsx +36 -0
  205. package/template/components/search-box.tsx +37 -0
  206. package/template/components/site-footer.tsx +59 -0
  207. package/template/components/site-header.tsx +64 -0
  208. package/template/components/ui/accordion.tsx +72 -0
  209. package/template/components/ui/alert-dialog.tsx +187 -0
  210. package/template/components/ui/alert.tsx +76 -0
  211. package/template/components/ui/aspect-ratio.tsx +22 -0
  212. package/template/components/ui/avatar.tsx +109 -0
  213. package/template/components/ui/badge.tsx +52 -0
  214. package/template/components/ui/breadcrumb.tsx +125 -0
  215. package/template/components/ui/button.tsx +58 -0
  216. package/template/components/ui/card.tsx +103 -0
  217. package/template/components/ui/carousel.tsx +242 -0
  218. package/template/components/ui/command.tsx +196 -0
  219. package/template/components/ui/dialog.tsx +160 -0
  220. package/template/components/ui/dropdown-menu.tsx +268 -0
  221. package/template/components/ui/foto.tsx +54 -0
  222. package/template/components/ui/input-group.tsx +158 -0
  223. package/template/components/ui/input-otp.tsx +87 -0
  224. package/template/components/ui/input.tsx +20 -0
  225. package/template/components/ui/label.tsx +20 -0
  226. package/template/components/ui/navigation-menu.tsx +168 -0
  227. package/template/components/ui/pagination.tsx +132 -0
  228. package/template/components/ui/popover.tsx +90 -0
  229. package/template/components/ui/progress.tsx +83 -0
  230. package/template/components/ui/radio-group.tsx +38 -0
  231. package/template/components/ui/scroll-area.tsx +55 -0
  232. package/template/components/ui/select.tsx +201 -0
  233. package/template/components/ui/separator.tsx +25 -0
  234. package/template/components/ui/sheet.tsx +138 -0
  235. package/template/components/ui/skeleton.tsx +13 -0
  236. package/template/components/ui/sonner.tsx +44 -0
  237. package/template/components/ui/switch.tsx +32 -0
  238. package/template/components/ui/table.tsx +116 -0
  239. package/template/components/ui/tabs.tsx +82 -0
  240. package/template/components/ui/textarea.tsx +18 -0
  241. package/template/components/ui/toggle-group.tsx +89 -0
  242. package/template/components/ui/toggle.tsx +45 -0
  243. package/template/components/ui/tooltip.tsx +66 -0
  244. package/template/components.json +25 -0
  245. package/template/eslint.config.mjs +36 -0
  246. package/template/gitignore +21 -0
  247. package/template/lib/analytics.ts +369 -0
  248. package/template/lib/api.ts +60 -0
  249. package/template/lib/capi.ts +87 -0
  250. package/template/lib/cart-link.ts +51 -0
  251. package/template/lib/cart-normalize.ts +142 -0
  252. package/template/lib/cart-recovery.ts +23 -0
  253. package/template/lib/cart-response.ts +48 -0
  254. package/template/lib/catalog-map.ts +54 -0
  255. package/template/lib/catalog.ts +3 -0
  256. package/template/lib/checkout-lock.ts +21 -0
  257. package/template/lib/checkout-nav.ts +22 -0
  258. package/template/lib/config.ts +51 -0
  259. package/template/lib/crm.ts +40 -0
  260. package/template/lib/customer-session.ts +21 -0
  261. package/template/lib/dataloader.ts +23 -0
  262. package/template/lib/editable/config.ts +11 -0
  263. package/template/lib/editable/document.ts +1762 -0
  264. package/template/lib/editable/index.ts +3 -0
  265. package/template/lib/editable/primitives.tsx +821 -0
  266. package/template/lib/editable/provider.tsx +861 -0
  267. package/template/lib/editable/rastreio-navegacao.tsx +65 -0
  268. package/template/lib/editable/rastreio.tsx +168 -0
  269. package/template/lib/editable/server.ts +189 -0
  270. package/template/lib/editable/tokens.ts +22 -0
  271. package/template/lib/editable/verify.ts +24 -0
  272. package/template/lib/enrichment/combos.ts +184 -0
  273. package/template/lib/enrichment/index.ts +188 -0
  274. package/template/lib/enrichment/products.json +1 -0
  275. package/template/lib/env-check.ts +47 -0
  276. package/template/lib/format.ts +130 -0
  277. package/template/lib/icons.ts +67 -0
  278. package/template/lib/json-ld.ts +9 -0
  279. package/template/lib/llms-txt.ts +75 -0
  280. package/template/lib/mockup.ts +24 -0
  281. package/template/lib/newsletter.ts +12 -0
  282. package/template/lib/orders.ts +160 -0
  283. package/template/lib/queries.ts +66 -0
  284. package/template/lib/ratelimit.ts +47 -0
  285. package/template/lib/rotas-editaveis.ts +192 -0
  286. package/template/lib/sanitize.ts +36 -0
  287. package/template/lib/schemas.ts +78 -0
  288. package/template/lib/session.ts +98 -0
  289. package/template/lib/store-config.ts +67 -0
  290. package/template/lib/unbox/client.ts +829 -0
  291. package/template/lib/unbox/customer.ts +224 -0
  292. package/template/lib/unbox/errors.ts +97 -0
  293. package/template/lib/unbox/index.ts +10 -0
  294. package/template/lib/unbox/store.ts +113 -0
  295. package/template/lib/unbox/types.ts +195 -0
  296. package/template/lib/unbox/webhooks.ts +66 -0
  297. package/template/lib/utils.ts +6 -0
  298. package/template/lib/vitrine.ts +242 -0
  299. package/template/lib/webhook-store.ts +22 -0
  300. package/template/middleware.ts +178 -0
  301. package/template/next.config.ts +63 -0
  302. package/template/package.json +62 -0
  303. package/template/postcss.config.mjs +5 -0
  304. package/template/public/brand/coll/default.png +0 -0
  305. package/template/public/brand/hero-desktop.svg +12 -0
  306. package/template/public/brand/hero-mobile.svg +12 -0
  307. package/template/public/brand/heros/boutique-desktop.svg +11 -0
  308. package/template/public/brand/heros/boutique-mobile.svg +11 -0
  309. package/template/public/brand/heros/editorial-desktop.svg +11 -0
  310. package/template/public/brand/heros/editorial-mobile.svg +11 -0
  311. package/template/public/brand/heros/essencial-desktop.svg +12 -0
  312. package/template/public/brand/heros/essencial-mobile.svg +12 -0
  313. package/template/public/brand/heros/promocional-desktop.svg +12 -0
  314. package/template/public/brand/heros/promocional-mobile.svg +12 -0
  315. package/template/public/brand/logo-chrome.svg +6 -0
  316. package/template/public/brand/logo-white.svg +6 -0
  317. package/template/public/brand/logo.svg +7 -0
  318. package/template/public/brand/pay-amex.webp +0 -0
  319. package/template/public/brand/pay-elo.webp +0 -0
  320. package/template/public/brand/pay-mastercard.webp +0 -0
  321. package/template/public/brand/pay-pix.webp +0 -0
  322. package/template/public/brand/pay-visa.webp +0 -0
  323. package/template/public/brand/ph/avatar-a.svg +5 -0
  324. package/template/public/brand/ph/avatar-b.svg +5 -0
  325. package/template/public/brand/ph/avatar-c.svg +5 -0
  326. package/template/public/brand/ph/avatar-d.svg +5 -0
  327. package/template/public/brand/ph/photo-a.svg +7 -0
  328. package/template/public/brand/ph/photo-b.svg +6 -0
  329. package/template/public/brand/ph/photo-c.svg +6 -0
  330. package/template/public/brand/ph/poster-a.svg +6 -0
  331. package/template/public/brand/ph/poster-b.svg +6 -0
  332. package/template/public/brand/ph/poster-c.svg +6 -0
  333. package/template/public/unbox/powered-by-fundo-preto.png +0 -0
  334. package/template/public/unbox/powered-by-transparente.png +0 -0
  335. package/template/public/unbox/powered-by.png +0 -0
  336. package/template/scripts/abandoned-cart.ts +82 -0
  337. package/template/scripts/check-editable.mjs +467 -0
  338. package/template/scripts/check-honestidade.mjs +111 -0
  339. package/template/scripts/check-placeholder.mjs +200 -0
  340. package/template/scripts/check-recipe.mjs +76 -0
  341. package/template/scripts/check-unbox-brand.mjs +298 -0
  342. package/template/scripts/contraste.js +177 -0
  343. package/template/scripts/dump-catalog.ts +95 -0
  344. package/template/scripts/load-env.ts +13 -0
  345. package/template/scripts/medir-sistema.mjs +56 -0
  346. package/template/scripts/place-order-pix.ts +61 -0
  347. package/template/scripts/qa-screenshots.mjs +150 -0
  348. package/template/scripts/sistema.py +231 -0
  349. package/template/scripts/subscribe-webhook.ts +36 -0
  350. package/template/scripts/test-live.ts +186 -0
  351. package/template/scripts/vocabulario.py +264 -0
  352. package/template/tsconfig.json +27 -0
  353. package/tools/LEIA-ME.md +39 -0
  354. package/tools/check-template-neutro.mjs +111 -0
  355. package/tools/notion-doc.mjs +191 -0
  356. package/tools/tokenize-neutrals.mjs +0 -0
  357. package/tools/tokenize-radius.mjs +52 -0
  358. package/tools/workflow-storefront.legado.js +1207 -0
@@ -0,0 +1,333 @@
1
+ # Mapa do projeto
2
+
3
+ Storefront Next.js 15 (App Router, Tailwind v4, shadcn) gerado pelo `create-unbox-store`,
4
+ conectado à API headless da Unbox. São ~220 arquivos e ~18 mil linhas: **não cabe em contexto,
5
+ nem perto.** Este arquivo existe pra você não precisar procurar.
6
+
7
+ ## Antes de abrir qualquer componente
8
+
9
+ Três arquivos indexam quase tudo. Juntos custam ~2k tokens e substituem ~30k de leitura às
10
+ cegas. Leia o que for do seu caso **antes** de sair abrindo `.tsx`:
11
+
12
+ | Arquivo | O que indexa | Custo |
13
+ |---|---|---|
14
+ | `agents/PADROES.md` §2 | **o catálogo**: o que cada uma das 23 seções é, suas variantes e quando cabe | ~4,3k |
15
+ | `components/home/sections/registry.ts` | os nomes válidos de seção (union tipada, o `tsc` reprova nome inventado) | ~0,9k |
16
+ | `components/chrome/registry.ts` | os 5 headers e 4 rodapés, com quando usar cada um | ~0,7k |
17
+
18
+ Para **escolher** uma seção, leia o PADROES §2, não os componentes. Para **escrever** o nome dela
19
+ na receita, o registry é a fonte da verdade. Os arquivos de chrome (`headers/`, `footers/`) abrem
20
+ com um comentário de "quando usar / quando não usar"; os de seção não, por isso o catálogo.
21
+
22
+ ## Quero mudar X, mexo em Y
23
+
24
+ ### Identidade e aparência
25
+ | Quero | Arquivo |
26
+ |---|---|
27
+ | Cores da marca, tipografia, raio, ritmo vertical, largura de container | `app/globals.css` (tokens `--store-*` no topo; o resto do arquivo deriva daí) |
28
+ | Trocar o logo | `public/brand/logo.svg` (+ `logo-white.svg`, `logo-chrome.svg` para fundo escuro) |
29
+ | Intensidade de animação/parallax | token `--motion` em `app/globals.css` (`0` desliga tudo) |
30
+ | Qual header e qual rodapé a loja usa | `components/chrome/chrome-recipe.ts` (só troca os nomes; as variantes já existem) |
31
+ | Barra de aviso do topo | `components/chrome/announce-bar.tsx` |
32
+ | Menu mobile | `components/chrome/header-bar-mobile.tsx` |
33
+
34
+ **Não edite** `site-header.tsx` / `site-footer.tsx` para trocar aparência. Eles são a casca fixa:
35
+ fazem o fetch, derivam o nome da loja e montam o `<header>`/`<footer>` raiz. O miolo variável
36
+ está em `components/chrome/headers/` e `footers/`.
37
+
38
+ ### Conteúdo e estrutura
39
+ | Quero | Arquivo |
40
+ |---|---|
41
+ | Quais seções a home tem, em que ordem, com que texto | `components/home/home-recipe.ts`, **um array só, com as props de conteúdo dentro** |
42
+ | Criar uma seção que não existe | novo arquivo em `components/home/sections/` + entrada no `registry.ts` + registro no `PADROES.md` |
43
+ | Página de produto (PDP) | `app/(loja)/produto/[productSlug]/page.tsx` + `components/product/` |
44
+ | Listagem, busca, categoria | `app/(loja)/{produtos,busca,categoria}/` + `components/catalog/` |
45
+ | Carrinho / checkout | `app/(loja)/{carrinho,checkout}/` + `components/{cart,checkout}/` |
46
+ | Textos de produto enriquecidos (FAQ, benefícios, combos) | `lib/enrichment/` (`products.json`, `combos.ts`) |
47
+ | Termos, privacidade, devoluções | `app/(loja)/{termos,privacidade,devolucoes}/` |
48
+ | Landing de campanha | `app/(loja)/oferta/` + `components/landing/` |
49
+
50
+ ### Promessas comerciais: a área perigosa
51
+ `lib/store-config.ts` concentra desconto do Pix, limiar de frete grátis e régua de brinde.
52
+ Os defaults saem **zerados de propósito**: cada valor ali é uma promessa exibida ao cliente, e
53
+ prometer o que o backend não cumpre faz o checkout mostrar um total e cobrar outro. Só preencha
54
+ depois de confirmar a promoção real com `npm run unbox:dump`. O gate `unbox:honestidade` cobra
55
+ isso.
56
+
57
+ ### Integração e infra
58
+ | Quero | Arquivo |
59
+ |---|---|
60
+ | O que o `/llms.txt` diz a agentes de IA | `lib/llms-txt.ts` (formato) + `app/llms.txt/route.ts` (dados). É ROTA, montada do catálogo real: nunca crie `public/llms.txt`, ele esconderia a rota |
61
+ | Credenciais, domínio, IDs de analytics | `.env.local` |
62
+ | Cliente da API Unbox, queries, tipos | `lib/unbox/` e `lib/queries.ts` |
63
+ | Porta de preview (prévia privada) | `middleware.ts` + `app/acesso/` |
64
+ | Recuperação de carrinho abandonado | `lib/cart-recovery.ts` (o link com id e token nasce aí) |
65
+ | Tracking (GA4, GTM, Meta Pixel, CAPI) | `lib/analytics.ts`, a camada ÚNICA; nenhum outro arquivo empurra no dataLayer |
66
+ | O que o cliente respondeu no formulário do CLI | `marca/briefing.json` |
67
+ | Editor da Unbox: o que o lojista edita sem código | `lib/editable/` (foundation, não edite à mão), `lib/rotas-editaveis.ts` (páginas e containers), `app/api/unbox/*`, `app/api/revalidate` |
68
+
69
+ ## Comandos
70
+
71
+ ```
72
+ npm run dev # servidor local
73
+ npm run typecheck # tsc --noEmit, o gate mais barato, rode primeiro
74
+ npm run build # dispara o prebuild de marca automaticamente
75
+ npm run unbox:honestidade # promessas comerciais sem lastro
76
+ npm run unbox:receita # variedade da receita (mede, não bloqueia)
77
+ npm run unbox:qa # screenshots do QA (emulação de dispositivo, servidor no ar)
78
+ npm run unbox:dump # imprime o catálogo e as promoções REAIS da loja
79
+ npm run unbox:editavel # gate do editor: cobertura editável por página (servidor no ar)
80
+ ```
81
+
82
+ Ordem barata → cara: `typecheck` → `unbox:honestidade` → `unbox:receita` → `build` → só então
83
+ subir o servidor e capturar tela. Screenshot é o passo mais caro do ciclo; não use como primeiro
84
+ diagnóstico.
85
+
86
+ ## Medidores de sistema visual (relatório, ainda não gate)
87
+
88
+ `npm run unbox:medir` roda dois medidores de Python que contam o que faz uma loja ter "cara de
89
+ template": quantos tamanhos de tipo, raios, hex fora de token e larguras de container existem
90
+ (`scripts/sistema.py`), e se o vocabulário gráfico da marca foi distribuído ou vive numa seção só
91
+ (`scripts/vocabulario.py`). Rodam no `prebuild` e **não bloqueiam**: imprimem e saem 0.
92
+
93
+ Não são gate ainda de propósito. Medido em cinco lojas geradas e na própria foundation, todas
94
+ estouram os tetos que vieram no script, a foundation inclusive: ligar como gate hoje reprovaria
95
+ até um scaffold recém-criado. Os números servem para escolher a régua depois, e para o agente de
96
+ branding ver o custo do que acrescenta enquanto trabalha.
97
+
98
+ Sem python3 na máquina, os medidores são pulados com aviso. Nunca derrubam build.
99
+
100
+ ## Placeholder no ar (antes de publicar)
101
+
102
+ `npm run unbox:placeholder` varre o **HTML servido**, não o código-fonte. É a diferença que
103
+ decide dois casos: varredura de fonte acusa `TODO` em comentário de componente que nem está na
104
+ receita, e não acha o texto que vaza sem existir como string, como `[NOME DA LOJA]` vindo do
105
+ conteúdo ou um logotipo com o nome errado dentro de um SVG.
106
+
107
+ Precisa de build de produção. Sem base explícita ele sobe o próprio servidor a partir do `.next`
108
+ e mede a si mesmo: **nunca** aponte para "o que estiver na porta 3000", que foi como a primeira
109
+ versão deste gate mediu outro projeto e quase aprovou a loja errada. Para medir o que está no ar:
110
+ `PLACEHOLDER_BASE=https://a-loja.com.br npm run unbox:placeholder`.
111
+
112
+ **Scaffold recém-criado reprova de propósito**, porque as páginas legais nascem com `[NOME DA
113
+ LOJA]` e `[CNPJ]` para o lojista preencher. Por isso o gate NÃO está no `prebuild`: ele é da
114
+ publicação, não do build. Imagem raster não é varrida por texto, e o script avisa quando existe
115
+ alguma: logotipo errado dentro de um PNG só aparece olhando.
116
+
117
+ ## Contraste e classificação de cor
118
+
119
+ `scripts/contraste.js` é para COLAR no console do DevTools com a loja aberta. Duas funções:
120
+ `contraste()` varre o texto visível e lista o que reprova; `classificar("#HEX")` diz se a cor
121
+ pode receber texto (**superfície**) ou só serve para contorno, ícone e faixa (**acento**).
122
+
123
+ A classificação é o uso principal: ao receber a paleta, meça cada cor contra a tinta e contra o
124
+ branco. Passa em algum dos dois (≥4,5) é superfície; não passa em nenhum é acento e **nunca**
125
+ vira fundo de texto. Se a marca quiser aquela cor como fundo, gere a versão funda e ponha branco
126
+ em cima. **Escreva o número ao lado do token no CSS**, senão o próximo agente refaz a mesma
127
+ avaliação e erra de novo.
128
+
129
+ O CLI não escolhe cor sozinho "para passar no WCAG": corrigir cor de marca em silêncio
130
+ descaracteriza a marca. Mede, aplica o que a marca pediu, e deixa o número e o custo escritos.
131
+
132
+ Duas coisas que a ferramenta não resolve, e estão ditas no cabeçalho dela: texto sobre foto (é
133
+ preciso amostrar os pixels da imagem) e conclusão tirada de captura de tela (esqueleto de
134
+ carregamento já enganou a leitura; confirme no DOM).
135
+
136
+ ## Não negociável
137
+
138
+ O prebuild (`scripts/check-unbox-brand.mjs`) **bloqueia o build** se faltar:
139
+ - `<PoweredByUnbox />` em `components/site-footer.tsx`
140
+ - o container GTM em `app/layout.tsx`
141
+
142
+ Ele também avisa (sem bloquear) sobre logo, ícones, manifest e paleta ainda no placeholder, a
143
+ paleta default é canvas provisória, não escolha estética, e trocar pelas cores reais da marca é
144
+ o primeiro ato do briefing.
145
+
146
+ ## Editor: o que não pode quebrar
147
+
148
+ A loja nasce ligada ao editor da Unbox (`editor.myunbox.com.br`): o lojista edita texto, foto, cor
149
+ e vitrine no painel, e a loja lê o publicado por HTTP e aplica por cima do código. A fiação é do
150
+ scaffold e tem oito pontos; mexer em qualquer um deles sem saber o que faz desliga o editor em
151
+ silêncio, sem erro de build:
152
+
153
+ - `lib/editable/` é cópia da foundation do editor. Não edite à mão: correção entra por versão da
154
+ foundation. A exceção é `lib/editable/tokens.ts`, a lista de cores que o lojista pode mudar.
155
+ - `lib/editable/config.ts` carrega o slug desta loja no editor (o CLI carimba). Tem de ser IGUAL ao
156
+ `slug` da entrada dela no `shops.json` do editor, senão o lojista publica e a loja nunca muda.
157
+ - `app/robots.ts` EXPORTA `DISALLOW`, e é essa lista que decide quais páginas o editor oferece
158
+ (`lib/rotas-editaveis.ts` varre `app/(loja)/` e tira o que o robots bloqueia). Não há segunda lista.
159
+ - Rota nova em `app/(loja)/` entra em `CONTAINERS_POR_ROTA` (e em `SO_CHROME` se for só texto legal
160
+ ou mecânica de compra), ou o gate reprova dizendo a rota.
161
+ - `next.config.ts`: `frame-ancestors` com `NEXT_PUBLIC_EDITOR_ORIGIN` (o editor abre a loja num
162
+ iframe; `X-Frame-Options` não aceita origem externa) e `outputFileTracingIncludes` dos `page.tsx`
163
+ (sem isso a varredura acha zero em produção).
164
+ - `middleware.ts` deixa passar `?unbox_editor_token=` (a prévia atrás da porta) e as rotas que o
165
+ editor chama de servidor (`/api/revalidate`, `/api/unbox/catalogo`, `/api/unbox/paginas`).
166
+ - `/api/revalidate` aceita `x-editor-token`, purga a tag `unbox-editor-content` e devolve o recibo
167
+ com `conteudo`: é com ele que o editor afirma "a loja está no ar com a versão N".
168
+ - `app/layout.tsx`: `EditableProvider` em volta do chrome e a linha única `<Rastreio>` no fim do
169
+ `<body>`. Nenhum snippet de GTM, GA4 ou Pixel escrito à mão fora dela, senão o provedor dispara
170
+ duas vezes.
171
+
172
+ Toda seção nova nasce editável pelos primitivos (`Editable.Text`, `Editable.Image`, `Editable.Icon`,
173
+ `Editable.Section` com `kind` e `label`). As regras completas, com o modo de falha de cada uma, estão
174
+ no README do editor da Unbox. O que NÃO vira primitivo: preço, produto do catálogo, texto legal,
175
+ rótulo de formulário e "Powered by Unbox".
176
+
177
+ Gate: `npm run unbox:editavel` com o servidor no ar e `NEXT_PUBLIC_EDITOR_ORIGIN` preenchida. Saída
178
+ `2` é NÃO RODOU, nunca aprovação.
179
+
180
+ ## Subagentes disponíveis
181
+
182
+ Declarados em `.claude/agents/`, para uso **depois** que o contrato de design fecha (o
183
+ protocolo está em `agents/CONSTRUCAO.md`):
184
+
185
+ | Agente | Para | Escreve? |
186
+ |---|---|---|
187
+ | `avaliador-visual` | uma leitura do QA (composição, contraste, responsivo, copy ou honestidade) | não |
188
+ | `conteudo-secao` | o conteúdo de uma seção da home | não, devolve o objeto |
189
+ | `paginas-legais` | uma página institucional | sim, só a dela |
190
+ | `assets-marca` | preparar `public/brand/` | sim, só essa pasta |
191
+
192
+ Os dois primeiros não têm Write de propósito: `home-recipe.ts` e `globals.css` são arquivos de
193
+ dono único, e escrita concorrente neles perde trabalho em silêncio.
194
+
195
+ ## Dado que a loja não tem, o bloco não renderiza
196
+
197
+ A regra veio de uma loja real em produção: a foundation preenchia vazio com conteúdo plausível
198
+ (nota 4,9 com 25 mil avaliações, "793 vendidos esta semana", depoimentos de "Cliente A" com
199
+ selo de compra verificada, estoque que descia sozinho enquanto a pessoa lia, "Sem glúten" por
200
+ default, prazo de devolução de 30 dias contradizendo a página do CDC). Tudo foi ao ar como fato,
201
+ e parte disso é violação de política do Google, da ANVISA ou do CDC. Nada disso existe mais.
202
+ Se um bloco depende de dado (avaliação, depoimento, estoque, prazo, atributo de produto), ele
203
+ **some** sem o dado. Nunca cai em default. O `unbox:honestidade` pega o que voltar.
204
+
205
+ Seções da home que somem sem props na receita: `reviews`, `reviews-carousel`, `savings`, `kits`,
206
+ `combos-carousel`, `category-pills`, `product-showcase`, `trust-bar` (sem `items` e sem frete
207
+ grátis real), `trust-strip`, `spec-table`, `comparison`, `attributes-marquee`, `founder-story`,
208
+ `video-wall`, `newsletter` (sem `title` + `action`, a URL que recebe o e-mail). `purchase-hero` só mostra
209
+ estrelas com `rating` e perks com `perks`. O que resta com
210
+ default é copy de interface (título do hero, rótulo de botão), nunca um fato sobre a loja.
211
+
212
+ ## Copy sem travessão
213
+
214
+ Nada de travessão na copy da loja: use vírgula, dois-pontos, ponto ou parênteses, e `·` quando
215
+ for separador (endereço, título de aba, selo). Vale para a interface, para os textos que você
216
+ escreve na conversa com o lojista e para o que você deixa em comentário: o travessão em
217
+ comentário é o que ensina a próxima seção a nascer com um. O `npm run build` reprova travessão
218
+ na copy de `app/` e `components/`.
219
+
220
+ ## Como falar da plataforma
221
+
222
+ A loja é de um cliente que comprou a Unbox, e **tudo o que você escreve chega nele**: a conversa,
223
+ o comentário no código, a copy da tela, o relatório do fim. Restrição de plataforma existe em
224
+ qualquer plataforma; o que não pode é você virar crítico dela dentro do produto que ela vende.
225
+
226
+ **Diga o que a regra é, o que ela significa para a loja e qual é a decisão.** Sem juízo de valor,
227
+ sem diagnóstico da qualidade da plataforma, sem dramatizar. Ficam de fora: "é pior do que parece",
228
+ "limitação da plataforma", "não dá pra resolver", "infelizmente", "a Unbox não deixa/não aceita",
229
+ "culpa/problema/falha da Unbox". Não é eufemismo: o fato inteiro continua dito, o julgamento é
230
+ que sai. Se a restrição realmente bloqueia o que o lojista pediu, diga o que dá pra fazer e
231
+ registre o pedido, quem leva isso adiante é o gerente de conta da Unbox, não a interface da loja.
232
+
233
+ **Para o comprador, nunca explique a plataforma.** Ele precisa da regra da loja, não do motivo
234
+ técnico dela, e o único lugar onde a Unbox aparece para ele é o selo "Powered by Unbox".
235
+
236
+ O caso que originou esta regra, frequência de assinatura, que é campo do pedido e vale para o
237
+ pedido inteiro:
238
+
239
+ | | |
240
+ |---|---|
241
+ | ✗ para o lojista | "É pior do que parece: a Unbox guarda um `recurringItemsFrequencyId` por pedido. É limitação da plataforma e não dá pra resolver no front." |
242
+ | ✓ para o lojista | "A frequência é do pedido, então vale para todos os itens assinados dele: escolher 60 dias num segundo produto passa o pedido todo para 60. Deixei isso explícito na tela antes de finalizar." |
243
+ | ✗ para o comprador | "Por limitação do sistema, só é possível uma frequência por pedido." |
244
+ | ✓ para o comprador | "Os itens assinados deste pedido chegam na mesma frequência." |
245
+
246
+ O `npm run build` reprova o julgamento sobre a plataforma em qualquer arquivo do projeto.
247
+
248
+ ## A foundation é de ramo nenhum
249
+
250
+ Ela nasceu de uma loja de alimentação e por duas varreduras ainda entregava loja de outro ramo
251
+ com instrução de uso de um produto alimentício na PDP, aba nutricional com traços e selo "Sem
252
+ glúten" inventado. A regra que decorre disso:
253
+
254
+ **Nada específico de um ramo renderiza sem dado daquele ramo.** Tabela nutricional, modo de
255
+ uso, composição, selos de atributo, FAQ, produtos relacionados: tudo vem do enriquecimento
256
+ (`lib/enrichment/products.json`) ou de `lib/store-config.ts`, e o que não existir não
257
+ aparece. Não existe default de conteúdo na PDP. Copy com vocabulário de um ramo é escrita
258
+ pelo briefing, para a marca certa, nunca na foundation. O `npm pack` do CLI reprova se isso
259
+ voltar, inclusive em comentário, porque comentário ensina o agente a escrever igual.
260
+
261
+ ## Armadilhas que já custaram caro
262
+
263
+ - **O prompt do briefing já está no seu contexto.** `.claude/settings.json` carrega
264
+ `.claude/agents/branding-briefing.md` como system prompt da sessão. Não abra esse arquivo pra
265
+ "conferir o que fazer": são ~8k tokens do que você já tem.
266
+ - **`home-recipe.ts` e `globals.css` são arquivos-gargalo.** Concentram, cada um, decisões de
267
+ várias frentes. Se houver trabalho paralelo, eles precisam de um dono só.
268
+ - **Header imersivo tem dois lados que precisam concordar:** a seção emite `.hero-imersivo` e o
269
+ header lê `[data-chrome="imersivo"]`. Mexer num sem o outro quebra silenciosamente.
270
+ - **`html { overflow-x: clip }`** é o que segura o marquee no layout. Por causa dele o header
271
+ imersivo usa `sticky` com margem negativa, não `fixed`.
272
+ - **O dataLayer fala GA4 padrão E o dialeto do GTM central da Unbox, no mesmo push.** O
273
+ container central lê `ecommerce.items` (GA4), `ecommerce.purchase.*` (Universal Analytics),
274
+ `transactionId`/`transactionTotal` (clássico) e `dataLayerReady { pageType, products[] }`. A
275
+ camada emite tudo; "limpar" para GA4 puro deixa a compra vazia no GA4 da Unbox, sem erro
276
+ visível. O build bloqueia. Nunca priorize o `gtag` sobre o dataLayer: foi assim que o GTM
277
+ central ficava cego em toda loja com GA4 próprio. `value` é número, `discount` vem do
278
+ carrinho, purchase só com pedido PAGO, dados de cliente saem hasheados.
279
+ - **A URL do checkout SEMPRE carrega `?id=&token=` do carrinho, e o build bloqueia sem isso.**
280
+ É o que permite recuperar carrinho abandonado: o CRM e o pixel leem a URL navegada, não o
281
+ cookie httpOnly. A garantia vive no `middleware.ts` (`urlDoCheckoutComPonteiro`), porque ele
282
+ roda em toda requisição, servidor, client-side, link direto, refresh, e não depende de
283
+ alguém lembrar de chamar o helper certo. A regra já se perdeu uma vez por viver só na página.
284
+ - **`loading.tsx` no segmento anula o `notFound()` da página.** Ele abre um `<Suspense>`; o
285
+ shell fica pronto na hora e sai com HTTP 200, e quando o `notFound()` acontece o status já
286
+ foi, soft-404, que faz o Google indexar página vazia. Por isso `categoria/[tagSlug]` e
287
+ `produto/[productSlug]` validam a existência num `layout.tsx`, que renderiza acima da
288
+ fronteira do Suspense. Criou rota dinâmica com `loading.tsx`? Valide no layout, não na page.
289
+ - **Nunca escreva cor literal em `box-shadow`.** Use os tokens `--store-shadow-*`. O verde da
290
+ foundation sobreviveu a uma varredura de 304 cores literais porque estava dentro de
291
+ `shadow-[...]` e de `boxShadow` inline, que ninguém olhou.
292
+ - **Fonte vive no `<body>`, não no `<html>`.** As variáveis do `next/font` são declaradas na
293
+ className do body; regra em `html` não as enxerga e o corpo cai em serif. Só aparecia no
294
+ build de produção.
295
+ - **Falha da API só vira "vazio" SEM credenciais.** Nunca `.catch(() => [])` em página, layout ou
296
+ chrome: use `mockupOr(promessa, fallback, "rótulo")` (`lib/mockup.ts`). Sem `.env`, devolve o
297
+ fallback (modo mockup). Com credenciais, loga e relança: o ISR mantém a versão anterior e o
298
+ `error.tsx` aparece. O `.catch` cego produzia home vazia com HTTP 200 cacheada por 5 minutos e,
299
+ nos layouts de produto/categoria, um 404 cacheado quando a Unbox oscilava.
300
+ - **`placeOrder` tem timeout próprio (90 s) e erro próprio (`PLACE_ORDER_TIMEOUT`, 504).** Abortar
301
+ no cliente não aborta no servidor: o pedido pode nascer depois que a loja desistiu. Com esse
302
+ código, o checkout-client NÃO reabre o botão de pagar e manda conferir em Meus pedidos. Não
303
+ "simplifique" isso num retry.
304
+ - **`alternates`/`openGraph` não fazem merge entre layout e página.** Canonical no `app/layout.tsx`
305
+ é herdado por TODAS as rotas que não declaram o seu (catálogo, categorias, legais viraram
306
+ canonical da home e saíram do índice). Canonical é por página, sempre relativo (`/produtos`),
307
+ e o `metadataBase` resolve. Nunca use `publishedUrl` do painel como canonical: pode ser outro domínio.
308
+ - **Posse de pedido é cookie ASSINADO.** `setOrderToken` grava `<token>.<HMAC(SESSION_SECRET)>` e
309
+ `getOrderToken` verifica. Sem isso qualquer valor de cookie abria qualquer pedido pelo
310
+ `referenceId` (modo parceiro nem valida o token). O CLI gera `SESSION_SECRET` no `.env.local`;
311
+ em produção ele precisa existir (o `env-check` avisa).
312
+ - **O preço final vem sempre do catálogo.** O servidor recalcula o carrinho a partir dele, então
313
+ o preço enviado no `addCartItems` não altera o que é cobrado.
314
+ Kit "com desconto" calculado no front (`combos.ts`) é promessa que o carrinho desmente: desconto
315
+ real de kit é regra de preço/cupom no painel Unbox. O mesmo vale pra desconto de assinatura: só
316
+ com `pricingPolicy` do backend (sem ele, `percentOff` é `null` e nenhum "-N%" aparece).
317
+ - **Foto de seção usa `<Foto>` (`components/ui/foto.tsx`), com `sizes` do tamanho real na tela.**
318
+ `next/image` direto quebra a página inteira quando o `src` da receita aponta para um host que o
319
+ `next.config.ts` não autoriza; o `<Foto>` cai em `<img>` nesse caso, sem otimizar mas sem
320
+ derrubar. E `sizes` errado é o mesmo que não ter: baixa grande demais (peso) ou pequena demais
321
+ (borrada). Logo do chrome, bandeira de pagamento e o hero em `<picture>` continuam em `<img>`
322
+ cru, cada um com o motivo escrito ao lado do `eslint-disable`.
323
+ - **`<img>` abaixo da dobra nasce `loading="lazy" decoding="async"`.** O React 19 emite
324
+ `<link rel="preload">` sozinho para `<img>` eager renderizado no servidor: as bandeiras do
325
+ RODAPÉ estavam disputando conexão com a primeira dobra. Não é sobre bytes, é sobre prioridade.
326
+ - **Em `<picture>`, cada `<source>` precisa da SUA dimensão e do SEU `sizes`.** Sem dimensão, o
327
+ navegador reserva a proporção do `<img>` (a de desktop) e a página pula quando a arte mobile
328
+ chega. Sem `sizes` no source que casou, ele escolhe uma variante pequena e a arte chega borrada.
329
+ - **ID de analytics passa por `idValido()`.** Placeholder é truthy: uma loja rodou 31 dias com
330
+ `NEXT_PUBLIC_GA_ID="x"`, script carregando e nada chegando em conta nenhuma. Campo vazio quebra
331
+ visivelmente; placeholder quebra em silêncio. Mesma razão da description nascer vazia.
332
+ - **`NEXT_PUBLIC_GTM_ID` desliga o Pixel do código.** Pixel no container da loja E no código conta
333
+ PageView duas vezes, e a inflação não dá sinal. Com container, tudo entra por ele.
@@ -0,0 +1,80 @@
1
+ # Cobertura — SDK Unbox + 11 documentos → onde está no storefront
2
+
3
+ Mapa recurso → arquivo. Tudo validado: `npm run unbox:test` → **17/17**, `npm run build` limpo,
4
+ smoke test HTTP do BFF (carrinho→endereço→frete) **200** em todas as etapas.
5
+
6
+ ## Métodos do SDK (`UnboxClient`) — todos usados
7
+
8
+ | Método | Onde |
9
+ |---|---|
10
+ | `signIn` + cache/re-signin | `lib/unbox/store.ts` (`getStoreClient`/`withStoreClient`) |
11
+ | `gql` (checa `errors` em HTTP 200, `x-captcha`) | `lib/unbox/client.ts` |
12
+ | `getCatalog` (first/offset/search/tagIds/sort) | `lib/queries.ts` → home, `/produtos`, `/busca`, `/categoria/[tagSlug]` |
13
+ | `getProductBySlug` | `app/(loja)/produto/[productSlug]/page.tsx` |
14
+ | `getProductById` | `lib/unbox/client.ts` (fallback/deep link) |
15
+ | `getTags` | `components/site-header.tsx`, home, categoria |
16
+ | `getShop` (shopSales, política de assinatura, settings) | `lib/queries.ts` → header, home, PDP, checkout |
17
+ | `getPaymentMethods` | `app/(loja)/checkout/page.tsx` |
18
+ | `listDiscountCodes` | `lib/queries.ts`, resolução de cupom no route |
19
+ | `createCart` / `addCartItems` / `updateItemQuantity` / `removeCartItems` | `app/api/cart/**` |
20
+ | `getCart` (rehidratar) | `app/api/cart` (GET), checkout, recuperação |
21
+ | `getFulfillmentGroupIds` (N grupos) | `app/api/checkout/address`, `shipping` |
22
+ | `setShippingAddress` | `app/api/checkout/address` |
23
+ | `quoteShipping` (obrigatório p/ cotar) | `app/api/checkout/shipping` (POST) |
24
+ | `selectShipping` | `app/api/checkout/shipping` (PUT) |
25
+ | `applyDiscount` / `findDiscountIdByCode` / `removeDiscount` | `app/api/cart/coupon` |
26
+ | `buildOrderItems` (exclui brindes) | `app/api/checkout` |
27
+ | `placeOrder` (Pix/Cartão + `orderRecurrence` + `device` antifraude no root) | `app/api/checkout` |
28
+ | API de PARCEIROS (nova): signIn GQL, tags, cupons, pedido, parcelas, `simpleInventory`, webhook, cart template — roteados via `UNBOX_PARTNER_API_KEY` | `lib/unbox/client.ts` (`usesPartnerApi`) |
29
+ | `getOrder` (com token de posse) | `lib/orders.ts`, `app/api/order/[ref]`, `/api/track` |
30
+ | `requestCustomerOtp` / `customerSignIn` / `customerAccountExists` | `app/api/account/{otp,signin,exists}` |
31
+ | `getAddressByPostalCode` | `app/api/cep/[code]`, `AddressFields` |
32
+ | `quoteShippingForProduct` (calcule-o-frete na PDP) | `app/api/shipping/quote` |
33
+ | `createPaymentLink` / `getPublicPaymentLink` | `app/api/payment-link` |
34
+ | `createCartByTemplate` | `lib/unbox/client.ts` (campanhas) |
35
+ | `subscribeWebhook` | `scripts/subscribe-webhook.ts` |
36
+
37
+ ## `UnboxCustomerClient` — área do cliente
38
+
39
+ | Método | Onde |
40
+ |---|---|
41
+ | `me` (currentCustomerAccount) | `/conta`, `/conta/enderecos`, `/conta/preferencias` |
42
+ | `updateAccount` | `app/api/account/preferences` |
43
+ | `orders` / `order` | `/conta/pedidos` + `[referenceId]`, `lib/orders.ts` |
44
+ | `subscriptions` / `subscription` / `subscriptionCycles` | `/conta/assinaturas` + `[referenceId]` |
45
+ | `pause` / `skipNextCycle` / `cancel` | `app/api/subscriptions/[id]` (+ `SubscriptionActions`) |
46
+ | `updateItems` / `updateCard` / `updateAddress` | idem |
47
+ | `addressBooks` / `upsertAddress` / `deleteAddresses` | `app/api/account/addresses` (+ `AddressBook`) |
48
+ | Rótulos `orderStatusLabel` / `paymentStatusLabel` / `subscriptionStatusLabel` | `components/order-status.tsx`, listas |
49
+
50
+ ## Helpers do SDK
51
+
52
+ | Recurso | Onde |
53
+ |---|---|
54
+ | `friendlyError` / `cartEventLabel` (feedback PT-BR) | `lib/api.ts`, `cart-provider` (toasts) |
55
+ | `datalayer.*` (GA4 + contrato do GTM central) | `lib/analytics.ts` — camada única (+ GTM/GA4 em `app/layout.tsx`) |
56
+ | `verifyUnboxWebhook` / `parseUnboxWebhook` (HMAC corpo cru) | `app/api/webhooks/unbox` |
57
+
58
+ ## Documentos (01–11) — recursos cobertos
59
+
60
+ - **01 Autenticação** — signin REST, JWT Bearer, cache/renovação, `x-captcha-verification`, dois contextos de auth, shopId/slug do JWT, erros em HTTP 200 → `lib/config.ts`, `lib/unbox/store.ts`, `lib/unbox/client.ts`.
61
+ - **02 Catálogo** — `catalogItems`, `catalogItemProductBySlug`/`ById`, `tags`, variantes/preço, de/por, badges de estoque, min/max, mídia, HTML sanitizado → catálogo/PDP + `lib/sanitize.ts`.
62
+ - **03 Carrinho & assinatura** — create/add/update/remove/get, brindes, `recurringItemsFrequencyId`, cart templates → `app/api/cart/**`, PDP (assinar), checkout.
63
+ - **04 Promoções** — `shopSales` (banner), `applyDiscountCodeToCart` (antes do frete), `removeDiscountCodeFromCart` (por discountId), brinde automático, `discountCodes` → header/home + `app/api/cart/coupon`.
64
+ - **05 Checkout** — endereço → frete (N grupos) → Pix/Cartão → `placeOrder`, QR copia-e-cola, parcelas, releitura antes do pedido → `app/api/checkout/**`, `components/checkout/*`, `/checkout/pix/[ref]`.
65
+ - **06 Área do cliente** — OTP 2 passos, conta, pedidos/rastreio, assinaturas self-service, address book + CEP, guest tracking → `/conta/**`, `/pedido/[referenceId]`.
66
+ - **07 Webhooks & extras** — `subscribeToWebhook`, HMAC, PING, idempotência, ORDER_CREATED/STATUS_UPDATE, payment links, flags NF/entrega → `app/api/webhooks/unbox`, `scripts/subscribe-webhook.ts`, `OrderStatusCard`.
67
+ - **08 Feedback/erros** — ordem de sinais (errors→failures→cartEvents), `friendlyError`, rótulos de status, reconciliação de preço → `lib/api.ts`, `cart-provider`, `order-status`.
68
+ - **09 Segurança** — BFF, isolamento de tokens (cookies httpOnly), posse de pedido, rate-limit/anti-enumeração, headers/Referrer-Policy, PCI (PAN só passa pelo BFF) → `lib/session.ts`, `lib/ratelimit.ts`, `next.config.ts`, `app/api/**`.
69
+ - **10 URLs/SEO/carrinho** — `generateMetadata`, canonical (`publishedUrl`), `sitemap.ts` (paginado, só visíveis), `robots.ts`, JSON-LD, ISR, persistência/recuperação de carrinho → PDP, `app/sitemap.ts`, `app/robots.ts`, `lib/crm.ts`, `scripts/abandoned-cart.ts`.
70
+ - **11 Produção/resiliência** — idempotência (lock por cartId, anti-duplo-clique, sem retry cego), N grupos, releitura antes do placeOrder, confirmação de Pix (webhook + polling), cache de token + re-signin, CDC art. 49, a11y → `lib/checkout-lock.ts`, `app/api/checkout`, `/checkout/pix/[ref]`, `lib/unbox/store.ts`, `/devolucoes`.
71
+
72
+ ## Limitações conhecidas (da API, não da implementação)
73
+
74
+ - O `Cart` lido (`anonymousCartByCartId`) **não expõe** os ids de cupons aplicados nem `cartEvents`;
75
+ o `discountId` para remoção é resolvido por código via `findDiscountIdByCode` e mantido na sessão.
76
+ - **N fulfillmentGroups**: a loja de exemplo retorna 1 grupo; o BFF cota/seleciona por grupo, mas o
77
+ `placeOrder` do SDK monta 1 grupo `SHIPPING` (suficiente aqui; multi-grupo exigiria estender o payload).
78
+ - **Payment links / cart templates / cancelOrderItem / refund total**: existem no SDK/doc mas dependem de
79
+ permissões admin/efeitos reais; expostos via rota/script protegidos, não no fluxo público.
80
+ - Infra de produção (KV, Redis, DB, CRM) está como implementação **em memória/cookie** com pontos `// PROD:`.
@@ -0,0 +1,179 @@
1
+ # Deploy no Vercel — Lojas Unbox/Next.js
2
+
3
+ > **A primeira versão no ar NÃO precisa de git nem de GitHub.** O `vercel` faz upload direto
4
+ > da pasta local, e a conta Vercel pode ser criada com **email** (na tela de login, ignore o
5
+ > "Continue with GitHub" e use "Continue with Email"). GitHub é um passo opcional DEPOIS, só
6
+ > pra quem quiser redeploy automático a cada push — veja a última seção.
7
+
8
+ ## Pré-requisitos locais
9
+
10
+ - Node.js **v22 LTS** (`nvm install 22 && nvm use 22`)
11
+ > v26 causa startup lento no Next.js — sempre use a LTS.
12
+ - Conta Vercel (email serve) — o CLI roda via `npx vercel`, sem instalar nada global
13
+ - `.env.local` com todas as credenciais da loja (veja `.env.example`)
14
+
15
+ ---
16
+
17
+ ## Checklist pré-deploy (na ordem certa)
18
+
19
+ ```bash
20
+ # 1. Node version correta
21
+ nvm install 22 && nvm use 22
22
+
23
+ # 2. Remover lock file global que conflita com npm
24
+ rm -f ~/package-lock.json
25
+
26
+ # 3. Instalar dependências
27
+ npm install
28
+
29
+ # 4. Verificar conexão com a API antes de subir
30
+ npm run unbox:test
31
+
32
+ # 5. Rodar local — primeira vez demora 2-4 min (compilação inicial)
33
+ npm run dev
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Setup no Vercel (via dashboard)
39
+
40
+ ### Framework Preset
41
+
42
+ > Settings → Build & Development Settings
43
+
44
+ Selecionar **Next.js** no dropdown "Framework Preset".
45
+
46
+ **Nunca deixar como "Other"** — sem isso o Vercel não configura o build command correto e o deploy falha silenciosamente.
47
+
48
+ ### Variáveis de ambiente
49
+
50
+ > Settings → Environment Variables
51
+
52
+ Adicionar **antes do primeiro deploy** em **Production**, **Preview** e **Development**:
53
+
54
+ | Variável | Obrigatório | Descrição |
55
+ |---|---|---|
56
+ | `UNBOX_PARTNER_API_KEY` | ✅ (recomendado) | Api key única do PARCEIRO (`da2-...`) — modelo atual |
57
+ | `UNBOX_CAPTCHA_BYPASS` | ✅ com a key de parceiro | x-captcha-verification do signIn (pedir à Unbox) |
58
+ | `UNBOX_USER` | ✅ | Usuário de acesso à API da loja |
59
+ | `UNBOX_PASS` | ✅ | Senha de acesso à API |
60
+ | `SESSION_SECRET` | ✅ | String aleatória forte (≥ 32 chars) |
61
+ | `NEXT_PUBLIC_SITE_URL` | ✅ | URL pública da loja (ex: `https://minhaloja.com.br`) |
62
+ | `NEXT_PUBLIC_SITE_NAME` | ✅ | Nome de exibição (manifest/PWA) |
63
+ | `UNBOX_API_KEY` | — | Modelo ANTIGO (key por loja); só sem a key de parceiro |
64
+ | `UNBOX_SHOP_ID` | — | Extraído do JWT se vazio |
65
+ | `UNBOX_SHOP_SLUG` | — | Extraído do JWT se vazio |
66
+ | `UNBOX_WEBHOOK_SECRET` | — | Secret(s) dos webhooks (vírgula p/ múltiplos) |
67
+ | `REVALIDATE_SECRET` | — | Protege `/api/revalidate` |
68
+ | `CRM_WEBHOOK_URL` | — | Webhook do CRM (carrinho abandonado; ver `.env.example`) |
69
+ | `PIPEDRIVE_API_TOKEN` | — | Leads da porta de preview → Pessoa+Negócio no Pipedrive |
70
+ | `PREVIEW_PASSWORD` | — | Chave do time da porta de preview (padrão `unbox`) |
71
+ | `UNBOX_HOSTED_CHECKOUT_URL` | — | Só no modo checkout hospedado da Unbox. **Caminho completo** (`https://sualoja.com.br/carrinho/finalizar-pedido`, nunca só o domínio) e **mesmo domínio da loja**: em hosts diferentes o cliente cai no `/login`. |
72
+ | `NEXT_PUBLIC_GA_ID` | — | GA4 measurement ID |
73
+ | `NEXT_PUBLIC_META_PIXEL_ID` | — | Meta Pixel ID |
74
+ | `META_PIXEL_ID` / `META_CAPI_TOKEN` | — | Meta Conversions API (server) |
75
+
76
+ > **Sem env vars o build falha** com `UnboxError: signin HTTP 403: Forbidden` durante o prerender.
77
+
78
+ ### Via CLI (alternativa)
79
+
80
+ ```bash
81
+ npx vercel env add UNBOX_API_KEY
82
+ npx vercel env add UNBOX_USER
83
+ npx vercel env add UNBOX_PASS
84
+ npx vercel env add SESSION_SECRET
85
+ npx vercel env add NEXT_PUBLIC_SITE_URL
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Primeiro deploy (sem git, sem GitHub)
91
+
92
+ ```bash
93
+ # 1. Entrar na conta (abre o browser; login por email funciona)
94
+ npx vercel login
95
+
96
+ # 2. Criar/linkar o projeto (responda os prompts; upload direto da pasta local)
97
+ npx vercel link
98
+
99
+ # 3. Deploy de produção
100
+ npx vercel --prod
101
+ ```
102
+
103
+ Pronto: o `vercel --prod` devolve a URL pública da loja. Nada de repositório envolvido.
104
+
105
+ ---
106
+
107
+ ## Opcional (depois): redeploy automático via GitHub
108
+
109
+ Só faz sentido quando a loja já está no ar e o time quer que todo push em `main` redeploye
110
+ sozinho. Aí sim:
111
+
112
+ ```bash
113
+ git init
114
+ git add .
115
+ git commit -m "chore: initial storefront"
116
+ git remote add origin <repo-url>
117
+ git push -u origin main
118
+ ```
119
+
120
+ E no dashboard do Vercel: Settings → Git → conectar o repositório. Sem isso, o redeploy é
121
+ manual (`npx vercel --prod` de novo) e funciona igual.
122
+
123
+ ---
124
+
125
+ ## Erros comuns e soluções
126
+
127
+ | Erro | Causa | Solução |
128
+ |---|---|---|
129
+ | `UnboxError: signin HTTP 403` | Env vars ausentes no Vercel | Adicionar em Settings → Environment Variables (todos os 3 ambientes) |
130
+ | Build falha silenciosamente | Framework Preset como "Other" | Alterar para **Next.js** em Settings → Build |
131
+ | `Project already exists` ao criar | Nome já usado na conta Vercel | Usar nome diferente (ex: `nome-loja-app`) |
132
+ | Prerender error na home `/` | API inacessível durante build | Verificar credenciais e rodar `npm run unbox:test` |
133
+ | `npm ERR! ...` no install | `~/package-lock.json` global | `rm -f ~/package-lock.json` |
134
+ | Startup > 5 min em dev | Node.js v26 instalado | `nvm use 22` |
135
+
136
+ ---
137
+
138
+ ## Porta de preview (prévia privada) e o LANÇAMENTO da loja
139
+
140
+ A loja nasce travada atrás da tela `/acesso` ("Prévia privada"): quem preencher
141
+ nome/marca/WhatsApp/e-mail entra (cookie de 30 dias) e vira lead — com
142
+ `PIPEDRIVE_API_TOKEN` configurado, cada lead cria Pessoa + Negócio no Pipedrive
143
+ (título `"<slug> - Nome"`, no funil de Negócios).
144
+
145
+ **A porta se escopa pelo domínio sozinha**: só existe em host de preview
146
+ (`*.vercel.app`, `*.myunbox.com.br`, + sufixos de `PREVIEW_HOSTS`). Não precisa ligar
147
+ nem desligar nada:
148
+
149
+ - **No deploy de prévia** (URL vercel.app/myunbox): porta LIGADA automaticamente.
150
+ Configure `PIPEDRIVE_API_TOKEN` (production + preview) e, se quiser aviso em tempo
151
+ real, `PREVIEW_WEBHOOK_URL` (Slack/Zapier).
152
+ - **No lançamento** (domínio próprio da marca apontado): porta SOME automaticamente no
153
+ domínio próprio. A URL vercel.app continua com porta — o que é bom: ninguém indexa a
154
+ loja pelo endereço de preview. Kill switch manual, se precisar: `PREVIEW_DISABLED=1`.
155
+ - **Time interno não preenche formulário**: qualquer URL da loja com
156
+ `?chave=<PREVIEW_PASSWORD>` (padrão `unbox`) grava o cookie e segue direto — ex.:
157
+ `https://loja.vercel.app/?chave=unbox`. Compartilhe ESSE link internamente; o cookie
158
+ vale 30 dias por navegador.
159
+ - **Local**: localhost fica fora da porta por padrão (dev e QA não tropeçam nela). Pra
160
+ testar a porta localmente: `PREVIEW_FORCE=1` no `.env.local`.
161
+ - ⚠️ Env var nova só vale **depois de um redeploy** — o snapshot anterior não a enxerga.
162
+ - Conferência pós-deploy (não pule): abrir a URL vercel.app numa aba anônima → deve
163
+ cair em `/acesso`; preencher → deve entrar; nos logs, `[preview-lead-pipedrive]` com
164
+ `ok:true` e `dealId` preenchido (a recusa do Pipedrive vem com **status 200** — não
165
+ confie em "não deu erro"). Apague a Pessoa/Negócio de teste do CRM.
166
+
167
+ ---
168
+
169
+ ## Redeploy após alterar env vars
170
+
171
+ 1. Vercel dashboard → Deployments → último deploy → **Redeploy**
172
+
173
+ Ou pela CLI:
174
+
175
+ ```bash
176
+ npx vercel --prod
177
+ ```
178
+
179
+ (Quem conectou GitHub também pode só dar `git push origin main`.)