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
Páginas públicas (no requieren login)
Sección titulada «Páginas públicas (no requieren login)»| URL | Archivo | Qué hace |
|---|---|---|
/ | src/app/(public)/page.tsx | Home: carrusel de banners, exploración por TCG, sets recientes |
/about | src/app/(public)/about/page.tsx | Sobre tcgcards: qué es, cómo funciona el modelo |
/accesorios | src/app/(public)/accesorios/page.tsx | Catálogo de accesorios con filtros |
/banned | src/app/(public)/banned/page.tsx | Aviso para cuentas baneadas |
/cartas | src/app/(public)/cartas/page.tsx | Feed de cartas individuales más recientes |
/cartas/[tcg] | src/app/(public)/cartas/[tcg]/page.tsx | Hub 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.tsx | Detalle 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 |
/challa | src/app/(public)/challa/page.tsx | Catálogo de lotes (challa) |
/checkout | src/app/(dashboard)/checkout/page.tsx | Carrito + confirmación + integración MP |
/como-funciona | src/app/(public)/como-funciona/page.tsx | Guía visual de 5 pasos |
/contact | src/app/(public)/contact/page.tsx | Página de contacto |
/cookies | src/app/(public)/cookies/page.tsx | Política de cookies |
/help | src/app/(public)/help/page.tsx | FAQs |
/help/precios-dinamicos | src/app/(public)/help/precios-dinamicos/page.tsx | Ayuda del feature Precios dinámicos: cómo funciona, regla de redondeo y disclaimer |
/listings/[id] | src/app/(public)/listings/[id]/page.tsx | Detalle 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 |
/mazos | src/app/(public)/mazos/page.tsx | Feed público de mazos pre-armados. Buscador por nombre, filtro por TCG, sort recent/price-asc/price-desc |
/novedades | src/app/(public)/novedades/page.tsx | Novedades: changelog/roadmap de hitos no técnicos en línea de tiempo (zig-zag en escritorio, columna en móvil) |
/privacy | src/app/(public)/privacy/page.tsx | Política de privacidad |
/search | src/app/(public)/search/page.tsx | Búsqueda global con filtros (incluye filtros por atributo según el TCG, en columna) |
/sellado | src/app/(public)/sellado/page.tsx | Catálogo de productos sellados |
/sets | src/app/(public)/sets/page.tsx | Listado de sets agrupados por TCG |
/sets/[tcg]/[slug] | src/app/(public)/sets/[tcg]/[slug]/page.tsx | Detalle de un set: grilla de cartas + panel de filtros por TCG colapsable |
/tarifas | src/app/(public)/tarifas/page.tsx | Comisiones y tarifas |
/terms | src/app/(public)/terms/page.tsx | Términos y condiciones |
/u/[username] | src/app/(dashboard)/u/[username]/page.tsx | Perfil público de un vendedor |
/u/[username]/publicaciones | src/app/(dashboard)/u/[username]/publicaciones/page.tsx | Todas las publicaciones del vendedor |
Filtros por TCG (TcgFilters)
Sección titulada «Filtros por TCG (TcgFilters)»/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
/searchy 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).
Novedades (/novedades)
Sección titulada «Novedades (/novedades)»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(arrayNOVEDADESde{ 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, sinrevalidate): 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(formatNovedadDateTZ-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íasrc/lib/useReveal.ts(respetaprefers-reduced-motion, queda visible si no hayIntersectionObserver). - Accesos: footer (columna “Ayuda”), menú móvil y menú de usuario.
| URL | Archivo | Qué hace |
|---|---|---|
/login | src/app/(auth)/login/page.tsx | Login con Google |
/api/auth/[...nextauth] | src/app/api/auth/[...nextauth]/route.ts | API NextAuth (callbacks, logout, etc.) |
Dashboard del usuario (requiere login)
Sección titulada «Dashboard del usuario (requiere login)»Todas bajo src/app/(dashboard)/me/...
| URL | Qué hace |
|---|---|
/me | Perfil del usuario + edición + wallet + accesos rápidos |
/me/bank-account | Cuenta bancaria (encrypted) para retiros |
/me/compras | Mis compras (filtradas por estado) |
/me/compras/[id] | Detalle de compra: chat, fotos, disputar |
/me/listings | Mis publicaciones (con filtros) |
/me/listings/new | Crear publicación (5 tipos: card, accessory, bulk, sealed, deck) |
/me/listings/[id]/edit | Editar publicación existente |
/me/mensajes | Inbox de conversaciones |
/me/ventas | Mis ventas (filtradas por estado) |
/me/ventas/[id] | Detalle de venta: chat, aceptar/enviar |
/me/wallet | Saldo + retiros + historial |
/me/withdrawals | Historial de retiros |
/me/withdrawals/new | Solicitar retiro |
Publicar un mazo
Sección titulada «Publicar un mazo»/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.
Precios dinámicos (/me y /me/listings)
Sección titulada «Precios dinámicos (/me y /me/listings)»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/...
| URL | Qué hace |
|---|---|
/admin | Dashboard principal con KPIs |
/admin/banners | Gestión de banners del home |
/admin/banners/new | Crear banner |
/admin/banners/[id] | Editar banner |
/admin/disputes | Cola de disputas |
/admin/disputes/[id] | Resolver disputa |
/admin/orders | Búsqueda de órdenes |
/admin/orders/[id] | Detalle de orden (admin view) |
/admin/reconciliation | Reconcilia wallet vs ledger |
/admin/reports | Cola de reportes |
/admin/reports/[id] | Resolver reporte (warn/suspend/ban) |
/admin/scanner-usage | Consumo del cupo de OCR del escáner (hoy / mes / últimos 30 días, con status ok/warning/critical) |
/admin/users | Usuarios moderados |
/admin/users/[username] | Detalle de usuario (perfil + moderation) |
/admin/users/[username]/bank-account | Cuenta bancaria del user (decrypted) |
/admin/users/[username]/wallet | Wallet del user (credit/debit manual) |
/admin/wallet-system | System wallet (comisiones acumuladas) |
/admin/finanzas | Panel 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/withdrawals | Cola 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/.
API routes propias de Next.js
Sección titulada «API routes propias de Next.js»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.
| URL | Archivo | Qué hace |
|---|---|---|
/api/auth/[...nextauth] | src/app/api/auth/[...nextauth]/route.ts | NextAuth — login/logout/session/callback Google |
/api/banners/sign-upload | src/app/api/banners/sign-upload/route.ts | Firma upload a Cloudinary para banners (admin) |
/api/orders/[id]/resume-payment | src/app/api/orders/[id]/resume-payment/route.ts | Reanuda pago en MP de una orden en awaiting_payment |
Convenciones del App Router
Sección titulada «Convenciones del App Router»- 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.tsxdefine el layout compartido por todas las páginas de esa carpeta y sub-carpetas.route.tsson API endpoints (NO páginas).loading.tsx,error.tsx,not-found.tsxson archivos especiales para UI de estados.
| Tipo de página | Requiere |
|---|---|
(public)/* | Nada |
(auth)/login | No estar logueado |
(dashboard)/me/* | Sesión activa (cookie Auth.js) |
(dashboard)/admin/* | Sesión activa + user.isAdmin === true |
(dashboard)/checkout | Sesió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.