Ir al contenido

Páginas web

Frontend en Next.js 16 App Router (tcgcards-web). Las rutas se definen por archivos page.tsx en src/app/**. Las carpetas con paréntesis como (public), (dashboard), (auth) son route groups y NO aparecen en la URL.

URL pública: https://tcgcards.cl

URLArchivoQué hace
/src/app/(public)/page.tsxHome: carrusel de banners, exploración por TCG, sets recientes
/aboutsrc/app/(public)/about/page.tsxSobre tcgcards: qué es, cómo funciona el modelo
/accesoriossrc/app/(public)/accesorios/page.tsxCatálogo de accesorios con filtros
/bannedsrc/app/(public)/banned/page.tsxAviso para cuentas baneadas
/cartassrc/app/(public)/cartas/page.tsxFeed de cartas individuales más recientes
/cartas/[tcg]src/app/(public)/cartas/[tcg]/page.tsxHub SEO por TCG (Tier 2, 2026-07-13): H1 “Cartas de {TCG} en Chile”, intro única (contenido estático en src/lib/data/tcg-hubs.ts), grilla de 24 recientes (ISR 300 s), chips de sets. Un hub existe si y solo si el slug está en TCG_HUBS; slug desconocido → 404. Enlazadas desde el footer (“Cartas por juego”), los tiles de la home y el sitemap
/cards/[id]src/app/(public)/cards/[id]/page.tsxDetalle de carta del catálogo: ofertas, ventas recientes. Al pie, bloque de cartas relacionadas (RelatedCards): prioriza el mismo set y rellena con el mismo TCG (con stock, fresco por visita; fail-soft) + link al hub del TCG
/challasrc/app/(public)/challa/page.tsxCatálogo de lotes (challa)
/checkoutsrc/app/(dashboard)/checkout/page.tsxCarrito + confirmación + integración MP
/como-funcionasrc/app/(public)/como-funciona/page.tsxGuía visual de 5 pasos
/contactsrc/app/(public)/contact/page.tsxPágina de contacto
/cookiessrc/app/(public)/cookies/page.tsxPolítica de cookies
/helpsrc/app/(public)/help/page.tsxFAQs
/help/precios-dinamicossrc/app/(public)/help/precios-dinamicos/page.tsxAyuda del feature Precios dinámicos: cómo funciona, regla de redondeo y disclaimer
/listings/[id]src/app/(public)/listings/[id]/page.tsxDetalle de publicación: galería, precio, vendedor, comprar. SEO: los listings de carta de catálogo llevan canonical/cards/{cardId} (consolida señales en la ficha permanente); customs/accesorios/lotes/sellados/mazos mantienen canonical propio
/mazossrc/app/(public)/mazos/page.tsxFeed público de mazos pre-armados. Buscador por nombre, filtro por TCG, sort recent/price-asc/price-desc
/novedadessrc/app/(public)/novedades/page.tsxNovedades: changelog/roadmap de hitos no técnicos en línea de tiempo (zig-zag en escritorio, columna en móvil)
/privacysrc/app/(public)/privacy/page.tsxPolítica de privacidad
/searchsrc/app/(public)/search/page.tsxBúsqueda global con filtros (incluye filtros por atributo según el TCG, en columna)
/selladosrc/app/(public)/sellado/page.tsxCatálogo de productos sellados
/setssrc/app/(public)/sets/page.tsxListado de sets agrupados por TCG
/sets/[tcg]/[slug]src/app/(public)/sets/[tcg]/[slug]/page.tsxDetalle de un set: grilla de cartas + panel de filtros por TCG colapsable
/tarifassrc/app/(public)/tarifas/page.tsxComisiones y tarifas
/termssrc/app/(public)/terms/page.tsxTérminos y condiciones
/u/[username]src/app/(dashboard)/u/[username]/page.tsxPerfil público de un vendedor
/u/[username]/publicacionessrc/app/(dashboard)/u/[username]/publicaciones/page.tsxTodas las publicaciones del vendedor

/search, el detalle de set y me/publicaciones (cuando el filtro es por carta y hay un TCG elegido) muestran filtros por atributo según el TCG vía src/components/catalog/TcgFilters.tsx. Renderiza un grupo de chips por filtro (con conteos) a partir de facets.attributes que devuelve la API. Es multi-select y vive en la URL (un parámetro por filtro, valores separados por coma) → compartible y SSR-friendly.

  • En /search y mis-publicaciones va como columna (siempre visible).
  • En el detalle de set va colapsable (collapsible): barra delgada con contador de filtros activos que se expande al click, para no comerse el alto de la pantalla.

Placeholder “Image Coming Soon” de TCGplayer

Sección titulada «Placeholder “Image Coming Soon” de TCGplayer»

Algunas cartas del catálogo apuntan al placeholder genérico de TCGplayer (imagen fija de 1000×573 px). En vez de mostrar ese cartel, src/lib/cardImage.ts#isTcgplayerComingSoon(w,h) lo detecta por sus dimensiones exactas en el onLoad de la imagen (CardCard y CardDetailImage) y cae a nuestro placeholder propio. Se usa el match exacto de dimensiones (no “es horizontal”) porque hay cartas legítimamente horizontales (ej. ubicaciones de Lorcana, BREAK de Pokémon).

Sección pública tipo changelog/roadmap con los hitos no técnicos de la plataforma (no se listan bugs, salvo una corrección pedida por usuarios que el usuario marque explícitamente). Línea de tiempo: zig-zag con spine central en escritorio, columna única con spine a la izquierda en móvil; más reciente arriba.

  • Contenido: archivo estático src/lib/data/novedades.ts (array NOVEDADES de { id, date, title, description }, ordenado por fecha descendente en código). Para agregar una novedad se añade un objeto y se despliega — no hay CMS ni fuente en runtime.
  • Página estática (force-static, sin revalidate): el contenido se hornea en el build, así que una novedad nueva aparece al desplegar a prod, sin limpiar caché. (Si se reactivara el caché de páginas de Cloudflare, habría que purgar esa ruta tras desplegar.)
  • Helpers en src/lib/novedades.ts (formatNovedadDate TZ-safe, esReciente); pill “Nuevo” en entradas ≤14 días (calculado en cliente para no quedar obsoleto en estático); la entrada más reciente va destacada. Reveal al scroll vía src/lib/useReveal.ts (respeta prefers-reduced-motion, queda visible si no hay IntersectionObserver).
  • Accesos: footer (columna “Ayuda”), menú móvil y menú de usuario.
URLArchivoQué hace
/loginsrc/app/(auth)/login/page.tsxLogin con Google
/api/auth/[...nextauth]src/app/api/auth/[...nextauth]/route.tsAPI NextAuth (callbacks, logout, etc.)

Todas bajo src/app/(dashboard)/me/...

URLQué hace
/mePerfil del usuario + edición + wallet + accesos rápidos
/me/bank-accountCuenta bancaria (encrypted) para retiros
/me/comprasMis compras (filtradas por estado)
/me/compras/[id]Detalle de compra: chat, fotos, disputar
/me/listingsMis publicaciones (con filtros)
/me/listings/newCrear publicación (5 tipos: card, accessory, bulk, sealed, deck)
/me/listings/[id]/editEditar publicación existente
/me/mensajesInbox de conversaciones
/me/ventasMis ventas (filtradas por estado)
/me/ventas/[id]Detalle de venta: chat, aceptar/enviar
/me/walletSaldo + retiros + historial
/me/withdrawalsHistorial de retiros
/me/withdrawals/newSolicitar retiro

/me/listings/new?kind=deck carga el componente DeckListingForm. Campos requeridos:

  • Nombre del mazo (deckTitle) — texto libre, 5-80 caracteres.
  • TCG (tcg) — slug del TCG (pokémon, magic, etc.).
  • Cantidad de cartas (deckCardCount) — entero 1-500.
  • Fotos (photos) — 1 a 5 imágenes via Cloudinary.
  • Precio (price) — CLP cents.
  • Cantidad (quantity) — stock disponible.
  • Descripción (description) — 10-500 caracteres.

Fotos propias en cartas de catálogo (opcional)

Sección titulada «Fotos propias en cartas de catálogo (opcional)»

Al publicar una carta del catálogo (/me/listings/new), el vendedor puede subir 0-2 fotos propias de su carta real (frente/dorso), de forma opcional y modesta (disclosure ”+ Agregar fotos de tu carta” en ListingForm). Se guardan en photos[] del listing y se muestran como thumbnails chicos clickeables —junto a la imagen del catálogo, sin reemplazarla— en la fila de vendedores (/cards/[id]), en mis-publicaciones (lista y grilla) y en el perfil público. Al hacer click abren un visor con zoom (ZoomLightbox, lazy, yet-another-react-lightbox + plugin Zoom) para inspeccionar bordes/detalle en mobile y desktop. El flujo de cartas custom (con customPhoto) no cambia.

Función opt-in: el vendedor puede activar que sus cartas del catálogo de TCGplayer se repricen solas una vez al día. El precio en CLP se calcula a partir del precio de referencia de TCGplayer (USD) y el tipo de cambio que el vendedor define, redondeado al múltiplo de $100 más cercano. Solo aplica a cartas de catálogo de TCGplayer: no se tocan cartas de Mitos y Leyendas, productos sellados, accesorios ni cartas custom. Superficies visibles:

  • Perfil (/me) — sección “Precios dinámicos”: checkbox maestro para activar la función + input del dólar (CLP). Si el nuevo tipo de cambio difiere mucho del anterior, aparece una confirmación con el impacto antes de guardar. Incluye el enlace de ayuda “¿Cómo funcionan los precios dinámicos?”.
  • Mis publicaciones (/me/listings) — sección “Precios dinámicos” sobre la tabla, con el botón “Activar en todas mis cartas” (abre un diálogo que avisa cuántas cartas se afectan y es cancelable) y el enlace “Cambiar mi tipo de cambio” (lleva a la configuración del perfil). Cada carta tiene un toggle “Precio dinámico”; cuando una carta queda fuera (no es elegible o su precio de referencia no está fresco) muestra el badge “Fuera de precios dinámicos”. Al recalcular en masa, un overlay de carga + un destello indican el avance.
  • Ayuda (/help/precios-dinamicos) — página de ayuda completa: cómo funciona, la regla de redondeo (al múltiplo de $100 más cercano) y el disclaimer.

El lanzamiento también figura como hito público en /novedades.

Admin (requiere login + user.isAdmin === true)

Sección titulada «Admin (requiere login + user.isAdmin === true)»

Todas bajo src/app/(dashboard)/admin/...

URLQué hace
/adminDashboard principal con KPIs
/admin/bannersGestión de banners del home
/admin/banners/newCrear banner
/admin/banners/[id]Editar banner
/admin/disputesCola de disputas
/admin/disputes/[id]Resolver disputa
/admin/ordersBúsqueda de órdenes
/admin/orders/[id]Detalle de orden (admin view)
/admin/reconciliationReconcilia wallet vs ledger
/admin/reportsCola de reportes
/admin/reports/[id]Resolver reporte (warn/suspend/ban)
/admin/scanner-usageConsumo del cupo de OCR del escáner (hoy / mes / últimos 30 días, con status ok/warning/critical)
/admin/usersUsuarios moderados
/admin/users/[username]Detalle de usuario (perfil + moderation)
/admin/users/[username]/bank-accountCuenta bancaria del user (decrypted)
/admin/users/[username]/walletWallet del user (credit/debit manual)
/admin/wallet-systemSystem wallet (comisiones acumuladas)
/admin/finanzasPanel financiero: sobres (ganancia / IVA / fondo usuarios / comisión MP / en tránsito), chequeo de salud (esperado vs saldo real MP), desglose por mes y movimientos; registrar pago de IVA y retiro de ganancia
/admin/withdrawalsCola de retiros
/admin/withdrawals/[id]Detalle de retiro (procesar/cancelar)

Escáner de cartas (modal, no es una página)

Sección titulada «Escáner de cartas (modal, no es una página)»

El escáner no es una ruta — es un modal (CardScannerModal) que se abre desde 3 entry points: el header en desktop, el header en mobile, y el formulario de venta (/me/listings/new). Se renderiza solo cuando el feature flag NEXT_PUBLIC_SCANNER_ENABLED === 'true' (ver envs) y solo en dispositivos táctiles (mobile/tablet, vía (pointer: coarse)); en computadores no aparece.

Flujo del modal: selección de TCG → cámara (el usuario encuadra la carta en la guía y toca Capturar; el botón espera ~1s a que la cámara enfoque, y una píldora superior recuerda capturar cuando la carta se vea nítida) → loader “Leyendo carta…” mientras corre el OCR → confirmación (1 match) o picker (varios) → navega a /cards/[id] (modo lookup) o rellena el form (modo autofill). Manda la imagen a POST /api/v1/scan/identify-image (ver endpoints). El código vive en src/components/scanner/ y src/lib/scanner/.

Estas viven dentro de tcgcards-web (no en tcgcards-api). Son endpoints que el cliente llama y que después hacen requests al backend o a servicios externos directamente.

URLArchivoQué hace
/api/auth/[...nextauth]src/app/api/auth/[...nextauth]/route.tsNextAuth — login/logout/session/callback Google
/api/banners/sign-uploadsrc/app/api/banners/sign-upload/route.tsFirma upload a Cloudinary para banners (admin)
/api/orders/[id]/resume-paymentsrc/app/api/orders/[id]/resume-payment/route.tsReanuda pago en MP de una orden en awaiting_payment
  • Carpetas con paréntesis ((public), (dashboard), (auth)) son route groups: agrupan páginas con un layout común sin afectar la URL.
  • [param] = ruta dinámica (un solo segmento).
  • [...slug] = catch-all (cero o más segmentos).
  • layout.tsx define el layout compartido por todas las páginas de esa carpeta y sub-carpetas.
  • route.ts son API endpoints (NO páginas).
  • loading.tsx, error.tsx, not-found.tsx son archivos especiales para UI de estados.
Tipo de páginaRequiere
(public)/*Nada
(auth)/loginNo estar logueado
(dashboard)/me/*Sesión activa (cookie Auth.js)
(dashboard)/admin/*Sesión activa + user.isAdmin === true
(dashboard)/checkoutSesión activa + items en carrito
(dashboard)/u/[username]Nada (es público, pero comparte layout con dashboard)

El control se hace via middleware o en cada page.tsx con auth() de Auth.js.