Ir al contenido

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.

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.

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.

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.).

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.

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.

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.

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).

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.

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.

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.

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/variant en listings de tipos accessory/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.

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_SECRET al browser.

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).
  • SessionProvider cachea client-side y mantiene estado consistente.

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.

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.

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.

Qué: Cuando entran a /search sin query, no se muestra “browse all” — se muestra placeholder.

Por qué:

  • El sort releaseDate agregado es lento sin índice apropiado.
  • “Explora por TCG” lleva a /sets?tcg=... que sí es rápido.

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.

  • 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.