Decisiones técnicas (ADRs)
Estas son las decisiones técnicas significativas del proyecto, cada una con: qué se decidió, por qué y cuándo. Sirven para que una persona nueva entienda el “por qué” del código sin tener que reconstruirlo desde cero.
Las decisiones detalladas viven en tcgcards-api/docs/superpowers/specs/ y tcgcards-api/docs/superpowers/plans/. Acá hago el resumen ejecutivo.
Arquitectura
Sección titulada «Arquitectura»Frontend y backend separados (2 repos)
Sección titulada «Frontend y backend separados (2 repos)»Qué: tcgcards-web (Next.js) y tcgcards-api (Express) son repos separados con sus propios deploys.
Por qué:
- El frontend cambia más rápido (UI iterations) → deploy automático a Vercel.
- El backend cambia menos pero deploys son más cuidadosos → manual con
gcloud run deploy. - Permite escalar independientemente.
- Permite que en el futuro otros clientes (móvil) usen el mismo backend.
Trade-off: dos repos para mantener, y los secrets compartidos (INTERNAL_JWT_SECRET etc.) hay que rotar en ambos.
MongoDB Atlas en vez de Postgres
Sección titulada «MongoDB Atlas en vez de Postgres»Qué: la BD primaria es Mongo, no SQL.
Por qué:
- Modelo de listings es naturalmente discriminated union (
kind: card/accessory/bulk/sealed) con campos distintos por tipo. Schema-on-read encaja bien. - Atlas tiene tier free generoso + backups built-in + Search incluido.
- Mongoose ya provee schema validation a nivel de aplicación (Zod en routes + Mongoose en persistencia).
Trade-off: menos garantías de integridad referencial (hay que cuidar en código). Las transacciones existen pero son más caras que en Postgres.
Cloud Run en vez de servidor dedicado
Sección titulada «Cloud Run en vez de servidor dedicado»Qué: el backend corre en Cloud Run (serverless containers de GCP).
Por qué:
- Free tier alcanza para empezar.
- Auto-scaling sin configuración.
- Deploys atómicos con rollback trivial (cambiar tráfico entre revisiones).
- Builds en GCP (no necesitas Docker local).
Trade-off: cold starts en la primera request después de inactividad (~2-3 seg). Para una app productiva con tráfico mínimo se nota; con tráfico real desaparece.
Mercado Pago (no Stripe, no Webpay, no Flow)
Sección titulada «Mercado Pago (no Stripe, no Webpay, no Flow)»Qué: procesador único de pagos.
Por qué:
- Stripe no opera en Chile como acquirer todavía (2026).
- Webpay y Flow tienen integración más compleja y comisiones similares.
- MP soporta tarjetas, transferencias, MercadoCrédito, y cuotas — la mayor cobertura.
- Webhooks confiables.
Trade-off: estamos atados a las particularidades de MP (cambio de TEST tokens a APP_USR- en 2026, etc.).
Wallet interno propio
Sección titulada «Wallet interno propio»Qué: Los vendedores cobran a un wallet interno (user.walletBalance + tabla wallet_entries) y retiran a su cuenta bancaria con un job manual del admin.
Por qué:
- Evita complejidad de “split payments” en MP (que requiere KYC de cada vendedor con MP).
- Nos da control sobre cuándo liberamos los fondos (post-confirmación de recepción del comprador).
- El owner (admin) puede ver el flujo completo y reconciliar manualmente.
Trade-off: necesitamos hacer transferencias manuales a los bancos de los vendedores. Funciona bien hasta cierta escala; después automatizar via API bancaria.
Frontend
Sección titulada «Frontend»Next.js 16 con App Router
Sección titulada «Next.js 16 con App Router»Qué: versión más reciente de Next.js, no Pages Router.
Por qué:
- Server Components reducen JS shipped al browser.
- Server Actions eliminan boilerplate de API routes para mutations.
- Streaming/Suspense para UX mejor.
Trade-off: Next.js 16 tiene breaking changes vs versiones previas. Hay que leer AGENTS.md antes de tocar código.
Tailwind v4 + shadcn
Sección titulada «Tailwind v4 + shadcn»Qué: Tailwind v4 (config en CSS, no JS) + shadcn como sistema de componentes.
Por qué:
- shadcn da accesibilidad (Radix) sin lockin de librería pesada (Material UI, Chakra).
- Tailwind v4 es más rápido y simple.
- Permite el design system “Onyx + Champagne” custom con tokens shadcn.
Auth.js (next-auth) v5 con Google OAuth
Sección titulada «Auth.js (next-auth) v5 con Google OAuth»Qué: login solo con Google por ahora.
Por qué:
- Sin password reset, magic link emails, ni soporte recovery — todo problema delegado a Google.
- La mayoría del público objetivo tiene Gmail.
Trade-off: usuarios sin Gmail no pueden registrarse (raro en Chile).
Cero hex hardcoded fuera de emails
Sección titulada «Cero hex hardcoded fuera de emails»Qué: Todos los colores en componentes vienen de tokens shadcn. Solo los templates de email tienen colores literales.
Por qué:
- Dark/light theme funciona desde el primer commit sin reescribir.
- Cambio de paleta = cambio en
globals.css, no en cada componente. - Los emails son una excepción porque los clientes de email no soportan CSS variables consistentemente.
Multi-TCG sin defaults a ‘pokemon’
Sección titulada «Multi-TCG sin defaults a ‘pokemon’»Qué: Ningún server component asume “pokemon” como TCG por defecto. Server components fetchean getTcgs() y propagan availableTcgs a clientes.
Por qué:
- Si en el futuro deshabilitamos Pokémon o agregamos un TCG nuevo, no rompemos nada.
- Los placeholders no muestran iconografía de un TCG específico.
Backend
Sección titulada «Backend»Precios en CLP cents (integer)
Sección titulada «Precios en CLP cents (integer)»Qué: Backend guarda y maneja precios como centavos enteros (e.g., $15.000 CLP = 1500000). Frontend convierte para display con formatPriceCents().
Por qué:
- Aritmética de floats da errores de redondeo. Con integers nunca pasa.
- MP también usa cents en su API.
Schemas Mongoose alineados con TypeScript
Sección titulada «Schemas Mongoose alineados con TypeScript»Qué: Si TS dice condition: string | null, el Mongoose schema usa default: null, NO required: true.
Por qué:
- Si están desalineados, inserts fallan con errores raros. Bug real ocurrió 2026-05-19 con
condition/language/varianten listings de tiposaccessory/bulk.
Switch exhaustivo con default: const _exhaustive: never
Sección titulada «Switch exhaustivo con default: const _exhaustive: never»Qué: Toda switch sobre kind (card/accessory/bulk/sealed) termina con default: const _exhaustive: never.
Por qué:
- Si alguien agrega un nuevo kind (
packs, etc.) y no actualiza todos los switches, falla compilación. - Bug real 2026-05-19: bulk listings caían silently al else en CartService y OrderService porque no había exhaustividad.
Detalle del incidente y checklist al agregar nuevo kind: ver memoria feedback_new_listing_kind_checklist.md.
import 'server-only' en archivos sensibles
Sección titulada «import 'server-only' en archivos sensibles»Qué: Archivos como src/lib/env.ts, src/lib/jwt.ts, src/lib/api/client.ts empiezan con import 'server-only'.
Por qué:
- Si alguien importa por error en un client component, el build de Next.js falla.
- Mejor fallar en CI que filtrar
INTERNAL_JWT_SECRETal browser.
SessionProvider pattern para sesión
Sección titulada «SessionProvider pattern para sesión»Qué: En lugar de leer la sesión en Header con auth() server-side, se usa SessionProvider global + useSession() en client.
Por qué:
- Leer la sesión en cada server component rompía el UI en páginas mixtas (header parpadeaba entre logged/anonymous).
SessionProvidercachea client-side y mantiene estado consistente.
Stock-dependent reads sin cache
Sección titulada «Stock-dependent reads sin cache»Qué: Endpoints que consultan stock (/cards/[id]/listings, etc.) usan cache: 'no-store'. Solo sets/rarities cachean 5 min.
Por qué:
- Mostrar stock viejo lleva a “agregar al carrito → no hay stock” en checkout. Mala UX y posible double-sell.
- Sets/rarities cambian rara vez, cache es seguro ahí.
Cloudflare Email Routing (inbound) + Resend (outbound)
Sección titulada «Cloudflare Email Routing (inbound) + Resend (outbound)»Qué: Para recibir emails (soporte@, disputas@) usamos Email Routing de Cloudflare. Para enviar transaccionales usamos Resend SMTP. Para envío manual desde Gmail, configurado “Send As” con SMTP de Resend.
Por qué:
- Email Routing es gratis y robusto.
- Resend es simple, tiene buen tier free, deliverability decente.
- Combinar ambos cubre todos los casos sin pagar una suite tipo SendGrid.
Copy en tú-form español neutro
Sección titulada «Copy en tú-form español neutro»Qué: Todo texto user-facing usa “tú” forma neutral. Sin voseo argentino (querés/podés/tenés/iniciá/usá).
Por qué:
- Audiencia chilena, no argentina.
- El tuteo es universal en LATAM para registro casual.
- Aplica a web, emails, toasts, admin.
Estrategia SEO (Plan 5h, 2026-05-10)
Sección titulada «Estrategia SEO (Plan 5h, 2026-05-10)»Qué: Sitemap XML por sub-tipo (cards, sets, users, static), structured data JSON-LD, metadata por página, OG tags.
Por qué:
- Marketplace nuevo necesita indexación rápida en Google.
- Sets y cards generan URL canónica indexable.
- JSON-LD ayuda a Google a entender Product/Offer.
Detalle: spec tcgcards-api/docs/superpowers/specs/2026-05-10-plan-5h-seo-design.md.
/search sin query queda en placeholder
Sección titulada «/search sin query queda en placeholder»Qué: Cuando entran a /search sin query, no se muestra “browse all” — se muestra placeholder.
Por qué:
- El sort
releaseDateagregado es lento sin índice apropiado. - “Explora por TCG” lleva a
/sets?tcg=...que sí es rápido.
Diseño / branding
Sección titulada «Diseño / branding»Paleta Onyx + Champagne
Sección titulada «Paleta Onyx + Champagne»Qué: Background cream #f5f4ef, foreground onyx #18181c, accent champagne #c9a05d.
Por qué:
- Diferencia visual de los marketplaces típicos (azules/verdes).
- Asocia con cartas premium (champagne = oro suave).
- Funciona en dark mode con inversión de tonos.
Spec del design system: tcgcards-api/docs/superpowers/specs/2026-05-08-plan-5-5-design-system-premium-design.md.
Decisiones pendientes / diferidas
Sección titulada «Decisiones pendientes / diferidas»- Image fallback (catalog backup) — diferido 2026-05-18. Backup de TCGplayer → Cloudflare R2 (~$0.60/mes). Valorar más adelante.
- Filtrar cartas individuales futuras — diferido 2026-05-18. Hoy filtramos sets futuros pero no cartas individuales. Revisitar si crecen vendedores reales o aparecen disputas.
- Búsqueda global multi-TCG (estilo tcgmatch.cl) — diferido 2026-05-08. Grid mezclado + checkboxes multi-select TCG en sidebar. ~3-4h. Revisitar tras uso real de la solución actual.