Skip to content

Latest commit

 

History

History
357 lines (275 loc) · 15.7 KB

File metadata and controls

357 lines (275 loc) · 15.7 KB

SWAP — Documentación del Proyecto

Red social y marketplace para intercambiar figuritas de álbumes coleccionables (Panini Mundial 2026). Los usuarios gestionan su colección, siguen a otros coleccionistas, coordinan intercambios inteligentes por chat y participan en comunidades.

El sistema cruza automáticamente tus figuritas repetidas con las faltantes de otros usuarios, mostrando exactamente cuántas y cuáles pueden intercambiar sin necesidad de publicar nada manualmente.


Índice

  1. Stack tecnológico
  2. Requisitos previos
  3. Configuración local
  4. Estructura del proyecto
  5. Arquitectura y patrones
  6. Base de datos
  7. Autenticación
  8. Rutas y páginas
  9. Componentes
  10. Tiempo real (Realtime)
  11. Despliegue
  12. Qué NO tocar

Stack tecnológico

Capa Tecnología Versión
Framework Next.js (App Router) 14.2
Lenguaje TypeScript 5
Estilos Tailwind CSS 3.4
Backend / DB Supabase (Postgres + Auth + Storage + Realtime) 2.x
Despliegue Vercel

No hay librerías de UI externas. Todo el diseño es Tailwind puro. No hay test runner configurado.


Requisitos previos

  • Node.js 18+
  • Una cuenta y proyecto en Supabase
  • (Opcional) CLI de Supabase para manejar migraciones

Configuración local

  1. Clonar el repositorio e instalar dependencias:
npm install
  1. Crear el archivo .env.local en la raíz con estas variables:
NEXT_PUBLIC_SUPABASE_URL=https://<tu-proyecto>.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key>
SUPABASE_SERVICE_ROLE_KEY=<service-role-key>

Las claves se obtienen en Supabase Dashboard → Project Settings → API.

  1. Aplicar las migraciones de base de datos en orden (ver sección Base de datos).

  2. Cargar los datos iniciales (seed):

Ir a Supabase Dashboard → SQL Editor y ejecutar el contenido de supabase/seed.sql.

  1. Iniciar el servidor de desarrollo:
npm run dev
# Disponible en http://localhost:3000

Estructura del proyecto

SWAP/
├── app/
│   ├── (auth)/                          # Páginas públicas de autenticación
│   │   ├── login/
│   │   ├── register/
│   │   ├── forgot-password/
│   │   └── reset-password/
│   ├── (protected)/                     # Páginas que requieren sesión activa
│   │   ├── layout.tsx                   # Layout compartido: sidebar + bottom nav
│   │   ├── colecciones/                 # Gestión del álbum personal
│   │   │   ├── page.tsx
│   │   │   └── [albumId]/
│   │   ├── marketplace/                 # Publicaciones de intercambio
│   │   │   ├── page.tsx
│   │   │   └── [listingId]/
│   │   ├── chat/
│   │   │   └── [tradeId]/               # Chat de negociación por trade o DM
│   │   ├── chats/                       # Lista de todas las conversaciones
│   │   ├── perfil/                      # Perfil propio (Instagram-style)
│   │   ├── perfil/[userId]/             # Perfil público de otro usuario
│   │   │   └── album/[albumId]/         # Álbum de otro usuario (solo lectura)
│   │   ├── amigos/                      # Buscador de usuarios
│   │   ├── comunidades/                 # Lista de comunidades
│   │   └── comunidades/[communityId]/   # Chat grupal de una comunidad
│   ├── auth/callback/                   # Callback OAuth de Supabase
│   ├── layout.tsx                       # Layout raíz
│   ├── page.tsx                         # Landing: redirige a /colecciones si hay sesión
│   └── globals.css
├── components/
│   ├── album/
│   │   ├── AlbumView.tsx                # Grilla de figuritas con filtros
│   │   ├── StickerModal.tsx             # Modal para marcar figurita
│   │   └── ImportarModal.tsx            # Importar colección desde otra app
│   ├── chat/
│   │   └── ChatClient.tsx               # Chat con Realtime, reply, borrar mensajes
│   ├── comunidades/
│   │   └── CommunityChatClient.tsx      # Chat grupal con Realtime
│   ├── amigos/
│   │   └── AmigosClient.tsx             # Buscador de usuarios
│   ├── layout/
│   │   ├── Sidebar.tsx                  # Navegación lateral (desktop)
│   │   ├── BottomNav.tsx                # Navegación inferior (mobile)
│   │   └── UnreadBadge.tsx              # Badge de mensajes no leídos (Realtime)
│   ├── marketplace/
│   │   ├── MarketplaceClient.tsx
│   │   ├── ListingCard.tsx
│   │   ├── ListingModal.tsx
│   │   └── NewListingModal.tsx          # Crea publicación con repetidas/faltantes automático
│   ├── perfil/
│   │   ├── ProfileClient.tsx            # Perfil propio editable
│   │   ├── PublicProfileClient.tsx      # Perfil público con seguir/mensaje
│   │   └── FollowListModal.tsx          # Modal de seguidores/siguiendo
│   └── ui/
│       └── CityAutocomplete.tsx
├── lib/
│   ├── supabase/
│   │   ├── client.ts
│   │   ├── server.ts
│   │   └── middleware.ts
│   ├── types/
│   │   └── index.ts                     # Todas las interfaces TypeScript
│   ├── albumCovers.ts                   # Mapeo nombre de álbum → imagen local
│   ├── importParser.ts                  # Parser de mensajes WhatsApp de otras apps
│   └── cities.ts                        # Ciudades colombianas para autocompletado
├── supabase/
│   ├── migrations/
│   └── seed.sql
├── public/
│   ├── albums/                          # Portadas de álbumes
│   │   └── panini-mundial-2026.jpg
│   ├── manifest.json
│   └── icons/
└── middleware.ts

Arquitectura y patrones

Server Components vs Client Components

  • page.tsx (Server Component): Obtiene datos de Supabase con el cliente de servidor y los pasa como props al componente cliente.
  • *Client.tsx (Client Component): Maneja estado interactivo y llama a Supabase directamente desde el browser.

Clientes Supabase

Hay dos clientes y no son intercambiables:

  • lib/supabase/client.ts → usar en componentes con 'use client'
  • lib/supabase/server.ts → usar en page.tsx, layout.tsx (requiere await)

Navegación

La app tiene dos sistemas de navegación sincronizados:

  • Sidebar.tsx: visible en pantallas md y mayores
  • BottomNav.tsx: visible en pantallas menores a md

Si se agrega una sección nueva, actualizar ambos componentes. Ambos reciben userId desde el layout para el badge de no leídos.

TypeScript — Set iteration

El target de compilación no es es2015, por lo que nunca usar [...new Set()]. Siempre usar Array.from(new Set(...)).

Supabase Realtime — nombres de canal

Siempre usar un nombre único por instancia de componente para evitar el error "cannot add callbacks after subscribe()":

const channelName = useRef(`mi-canal-${Date.now()}`).current

Base de datos

Migraciones

Las migraciones viven en supabase/migrations/ y se aplican en orden desde Supabase Dashboard → SQL Editor.

Archivo Descripción
001_initial_schema.sql Esquema base: tablas, RLS, trigger de perfil, storage
002_add_sticker_code.sql Columna code en stickers
003_add_rarity.sql Columna rarity (NORMAL / FOIL) en stickers
004_enable_messages_realtime.sql Habilita Realtime en messages
005_add_communities.sql Tablas communities y community_messages con RLS + Realtime
007_add_follows_and_bio.sql Tabla follows + columna bio en profiles
008_public_user_stickers.sql RLS: cualquier usuario autenticado puede leer user_stickers ajenas
009_add_trade_reads.sql Tabla trade_reads + función get_unread_count() para badges
010_allow_direct_trades.sql trades.listing_id pasa a nullable para mensajes directos
011_add_reply_to_and_unread_per_trade.sql messages.reply_to_id + función get_unread_counts_per_trade()
012_delete_policies.sql RLS: remitente puede borrar sus mensajes; participantes pueden borrar trades
013_add_updated_at_to_user_stickers.sql Columna updated_at en user_stickers con índice para ordenar por actividad reciente
014_add_notifications_seen_at.sql Columna notifications_seen_at en profiles para el badge de notificaciones
015_push_subscriptions.sql Tabla push_subscriptions para notificaciones push web (VAPID)

Tablas principales

profiles              — Datos públicos del usuario (username, full_name, avatar_url, bio)
albums                — Álbumes coleccionables
stickers              — Figuritas de cada álbum (número, código, jugador, sección, rareza)
user_stickers         — Relación usuario ↔ figurita: owned y repeated_count
listings              — Publicaciones de intercambio (ciudad + oferta/demanda)
listing_offers        — Figuritas que el usuario ofrece en una publicación
listing_wants         — Figuritas que el usuario busca en una publicación
trades                — Intercambio o mensaje directo entre dos usuarios
                        (listing_id es null para DMs)
messages              — Mensajes de chat con reply_to_id opcional
follows               — Relación seguidor ↔ seguido
communities           — Comunidades temáticas
community_messages    — Mensajes de chat grupal de una comunidad
trade_reads           — Último mensaje leído por usuario en cada trade (para badges)
push_subscriptions    — Suscripciones VAPID para notificaciones push web

Row Level Security (RLS)

  • user_stickers: cualquier usuario autenticado puede leer; solo el propietario escribe.
  • messages: solo participantes del trade pueden leer/escribir; el remitente puede borrar sus propios mensajes.
  • trades: solo participantes pueden leer/actualizar/borrar.
  • follows: cualquier autenticado puede leer; solo el seguidor gestiona sus propias relaciones.
  • profiles, albums, stickers: lectura pública para autenticados.

Autenticación

  • middleware.ts intercepta todos los requests, refresca el token y redirige a /login si no hay sesión.
  • Al registrarse con email, Supabase envía un correo de confirmación — la sesión no se inicia hasta validar.
  • Al registrarse con Google (OAuth), la sesión se inicia directamente.
  • El trigger on_auth_user_created crea automáticamente una fila en profiles.

Rutas y páginas

Ruta Descripción
/login Login con email/contraseña o Google
/register Registro con confirmación de email
/forgot-password Solicitar reset de contraseña
/reset-password Nueva contraseña desde link de email
/colecciones Lista de álbumes disponibles
/colecciones/[albumId] Grilla de figuritas del álbum personal
/marketplace Perfiles de usuarios ordenados por compatibilidad de intercambio
/marketplace/[listingId] Detalle de publicación (legacy)
/chats Lista de conversaciones con contador de no leídos
/chat/[tradeId] Chat en tiempo real (trade o DM)
/perfil Perfil propio editable (Instagram-style)
/perfil/[userId] Perfil público de otro usuario
/perfil/[userId]/album/[albumId] Álbum de otro usuario en modo lectura
/amigos Buscador de usuarios para seguir
/comunidades Lista de comunidades
/comunidades/[communityId] Chat grupal de una comunidad

Componentes

AlbumView.tsx

Grilla de figuritas con filtros (todos / faltan / completo / repetidos), buscador y barra de progreso. Soporta modo readOnly para ver álbumes de otros usuarios. Incluye botón "Importar colección" que abre ImportarModal.

ImportarModal.tsx

Modal de 3 pasos para importar colección desde mensajes de WhatsApp de otras apps. Parsea números, códigos (COL1, FWC3...) y rangos (1-5). Usa lib/importParser.ts.

ChatClient.tsx

Chat completo con: Realtime (mensajes en vivo), separadores de fecha, reply a mensajes específicos, borrar mensajes propios, borrar conversación, badge de no leídos al salir. Fondo verde suave.

UnreadBadge.tsx

Badge rojo en el ícono de Chats. Se suscribe a inserciones en messages via Realtime con nombre de canal único por instancia. Se resetea al entrar a un chat y se refresca al salir.

ProfileClient.tsx

Perfil propio estilo Instagram: avatar editable, @username editable con validación de unicidad, bio, stats (álbumes/seguidores/siguiendo) clickeables, grilla de álbumes con portada.

PublicProfileClient.tsx

Perfil público de otros usuarios. Botones Seguir/Siguiendo y Mensaje. Botón Intercambiar que muestra un modal con dos tabs: figuritas que puedes darle (tus repetidas que le faltan) y figuritas que recibirías (sus repetidas que te faltan), con un CTA que abre el chat directamente. Stats clickeables que abren FollowListModal.

FollowListModal.tsx

Modal que muestra la lista de seguidores o siguiendo de un usuario. Se carga al abrirse.

MarketplaceClient.tsx

Lista todos los usuarios ordenados por compatibilidad de intercambio (suma de figuritas que pueden darse mutuamente). Chips verdes "Te da N" y azules "Le das N". Filtro para mostrar solo usuarios con intercambio posible. Al hacer clic navega al perfil público donde se puede ver el detalle exacto.

NewListingModal.tsx

Crea una publicación en el marketplace (legacy). Solo pide ciudad; calcula automáticamente las repetidas (a ofrecer) y faltantes (a buscar) de la colección actual del usuario.

CommunityChatClient.tsx

Chat grupal con Realtime. Muestra nombre y avatar del remitente cuando cambia de persona. Avatar linkea al perfil.

AmigosClient.tsx

Buscador de usuarios por nombre o @username. Solo muestra resultados cuando hay texto — sin sugerencias iniciales.


Tiempo real (Realtime)

Tres tablas usan Realtime:

  • messages — chat de trades y DMs (migración 004)
  • community_messages — chat de comunidades (migración 005)
  • messages también para el UnreadBadge del nav

Requisito: las tablas deben estar en la publicación de Realtime y tener replica identity full. Si el Realtime deja de funcionar, verificar en Supabase Dashboard → Database → Replication.


Despliegue

El proyecto está en Vercel. Cada push a master dispara un deploy automático.

Variables de entorno en Vercel → Settings → Environment Variables:

NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
SUPABASE_SERVICE_ROLE_KEY

Verificar build antes de hacer push:

npm run build

Qué NO tocar

Archivo / Carpeta Por qué
supabase/migrations/001_initial_schema.sql Define toda la base de datos base. Crear siempre una nueva migración numerada en su lugar.
supabase/seed.sql ~670 figuritas del álbum. Modificarlo afecta datos de todos los usuarios.
lib/supabase/middleware.ts Maneja el refresco de sesión. Tocarlo puede romper la autenticación.
middleware.ts Define rutas protegidas. Cambiar el matcher puede dejar rutas expuestas.
.env.local Variables sensibles. Nunca commitear.
app/auth/callback/route.ts Callback de Supabase Auth. No modificar.