01-overview.md
Visión general · Conciergo
Conciergo es una app web mobile-first para ir a conciertos cuando no tienes compañía. Login con Neon Auth, feed y mapa de bolos, matching automático al apuntarte y chat 1-1 en tiempo real.
El producto vive en un workspace pnpm: front React+Vite (artifacts/conciergo), API Express (artifacts/api-server), contratos OpenAPI (lib/api-spec) y Postgres + Drizzle (lib/db).
Enlaces
- Producción: conciergo-sigma.vercel.app
- GitHub: código fuente
- Health:
GET /api/healthz→{ "status": "ok" }
Stack
| Capa | Tecnología |
|---|---|
| Monorepo | pnpm workspaces, TypeScript 5.9 |
| Web | React, Vite, Tailwind 4, shadcn/ui, Framer Motion, Wouter |
| Auth | Neon Auth (@neondatabase/neon-js + Better Auth). La API verifica el JWT con jose + JWKS |
| API | Express 5, OpenAPI + Orval + Zod, Socket.io |
| Datos | Neon Postgres + Drizzle ORM (users.auth_id) |
| Mapas | Google Maps JS + Places |
| Chat | Socket.io (/api/socket.io) |
02-product.md
Producto y features
Problema
Un gran bolo se pierde si no hay con quién ir. Los grupos de WhatsApp son caos; Tinder no es para conciertos; las apps de entradas no conectan a extraños con el mismo cartel.
Solución
Conciergo: feed de eventos, mapa, “voy” y match automático con el resto de asistentes. Cada match abre un chat 1-1 para cuadrar el viaje o el foso.
Características
Landing
Copy de cartel: «No dejes que un gran bolo se pierda». Entrar / Unirse (Neon Auth). Invitados pueden explorar eventos y mapa.
Eventos
Lista con filtro por género. Cards compactas (artista, venue, fecha). Usuarios logueados se apuntan; FAB para crear evento (venue via Places).
Mapa
Pins de Google Maps. Click → ficha y apuntarse. Invitados ven el mapa.
Conexiones
Matches del usuario + badge de no leídos. Chat en tiempo real (Socket.io).
Perfil (Backstage)
Nombre, ciudad, géneros, avatar de Neon Auth.
Invitados
Eventos y mapa abiertos. Conexiones, perfil y crear evento piden sesión.
03-architecture.md
Arquitectura
Diagrama lógico
React (Vite) Conciergo
│ Neon Auth (sesión + JWT)
│ React Query → /api/*
│ Socket.io → /api/socket.io
▼
Express 5 (/api)
│ requireAuth → verifica JWT (jose + JWKS) + JIT user
▼
Neon Postgres (Drizzle)
users, events, event_attendees, matches, messages
Capas
- Front —
artifacts/conciergo: Wouter, shadcn, layout sidebar desktop + bottom nav móvil. - API —
artifacts/api-server: rutasusers,events,matches,health. - Contrato —
lib/api-spec/openapi.yaml→ Orval genera hooks (lib/api-client-react) y Zod (lib/api-zod). - DB —
lib/dbDrizzle.
Decisiones de runtime
- Match automático al marcar asistencia: un match (y chat) con cada otro asistente.
- Chat por Socket.io (
/api/socket.io), autenticado con el mismo JWT. - JIT provisioning: el primer
requireAuthinserta al usuario desde elsubde Neon Auth (users.auth_id).
04-monorepo.md
Estructura (pnpm workspace)
No es Angular/Nest. Es un monorepo Replit (pnpm-workspace.yaml).
| Path | Rol |
|---|---|
artifacts/conciergo | App React + Vite |
artifacts/api-server | Express + Neon Auth (jose) |
artifacts/mockup-sandbox | Sandbox de UI (no prod) |
lib/api-spec | OpenAPI + Orval |
lib/api-client-react | Hooks generados |
lib/api-zod | Schemas Zod generados |
lib/db | Drizzle + schema |
replit.md | Runbook del producto |
Tras cambiar el spec: pnpm --filter @workspace/api-spec run codegen.
05-api.md
Superficie API
Base: /api. Fuente: lib/api-spec/openapi.yaml.
| Método | Ruta | Auth | Qué hace |
|---|---|---|---|
| GET | /healthz | pública | { status: "ok" } |
| GET/PUT | /users/me | JWT Neon Auth | Perfil propio |
| GET | /users/:userId | JWT Neon Auth | Perfil ajeno |
| GET | /events | opcional | Feed + isAttending |
| POST | /events | JWT Neon Auth | Crear bolo |
| POST/DELETE | /events/:id/attend | JWT Neon Auth | Apuntarse / salir (crea matches) |
| GET | /matches | JWT Neon Auth | Conexiones + unread |
| GET/POST | /matches/:id/messages | JWT Neon Auth | Historial de chat |
| WS | /api/socket.io | JWT Neon Auth | Chat en tiempo real |
Código de ejemplo (health)
router.get("/healthz", (_req, res) => {
const data = HealthCheckResponse.parse({ status: "ok" });
res.json(data);
});
HealthCheckResponse sale de Zod generado. Si el spec y el handler se desalinean, falla el typecheck.
06-auth.md
Autenticación (Neon Auth)
Flujo
- Landing → Entrar / Unirse (
@neondatabase/neon-js+ Better Auth, UI de@neondatabase/auth-ui). - El front pide un JWT (
getAccessToken) y lo manda comoAuthorization: Bearer. - Las rutas protegidas usan
requireAuth:- Sin token o JWT inválido → 401.
- Primera llamada →
INSERTenusers(auth_id=sub, nombre + avatar del payload).
- Invitados ven Eventos y Mapa; Conexiones/Perfil/crear evento piden sesión.
Cómo se verifica
La API usa jose y el JWKS de Neon Auth (NEON_AUTH_BASE_URL/.well-known/jwks.json). El chat Socket.io valida el mismo token.
Qué no hay
No hay Clerk ni PIN. La sesión es de Neon Auth; la fila de negocio es users.auth_id.
07-data-model.md
Modelo de datos (Drizzle / Postgres)
users
id, auth_id (único, sub de Neon Auth), name, bio, avatar_url, music_genres[], city, created_at.
events
title, artist_name, venue_name, venue_address, date, lat, lng, genre, image_url, created_by_user_id.
event_attendees
Quién va a qué bolo. Al insertar, el servidor crea matches con el resto de asistentes.
matches
event_id + user_id_1 + user_id_2 (unique). Un chat por pareja y evento.
messages
Mensajes del match. El front cuenta unreadCount para el badge de Conexiones.
08-deploy-vercel.md
Deploy (Vercel)
Producción: https://conciergo-sigma.vercel.app
El front y la API se sirven en Vercel. El mismo proceso cubre el build de Vite y Express (/api, /api/socket.io).
Env
| Variable | Dónde |
|---|---|
DATABASE_URL | Neon Postgres |
NEON_AUTH_BASE_URL / NEON_AUTH_URL | API (JWKS) |
VITE_NEON_AUTH_URL | Front (cliente Auth) |
VITE_GOOGLE_MAPS_API_KEY | Maps + Places |
La key de Maps debe tener Maps JavaScript API y Places API.
Local
pnpm --filter @workspace/api-server run dev
pnpm --filter @workspace/conciergo run dev 09-decisions.md
Decisiones
Match automático, no swipe
Apuntarse a un evento es el gesto de matching. Evita un segundo feed tipo dating.
Socket.io para el chat
El historial sigue en REST; los mensajes en vivo van por Socket.io con el JWT de Neon Auth.
OpenAPI como fuente de verdad
Orval genera hooks y Zod. El typecheck del workspace pilla desvíos entre front y API.
Neon Auth, no Clerk
Email/social y sesión los resuelve Neon Auth (Better Auth). El dominio de negocio es el perfil y los matches (users.auth_id).
Invitados ven el cartel
Eventos + mapa sin login para que el landing no sea un muro. Crear y chatear sí piden cuenta.
10-challenges-results.md
Retos y aprendizajes
Google Maps Loader
La API clásica Loader de @googlemaps/js-api-loader v2 ya no existe (setOptions + importLibrary). Un preview Replit llegó a mostrar el overlay rojo de Vite. Hay que alinear la versión del loader con el código.
Keys de Maps
Sin Maps JS + Places activados, el mapa enseña «This page can’t load Google Maps correctly». Es configuración de GCP, no de UI.
JIT user vs “Anonymous”
Si Neon Auth aún no ha enviado el nombre, el primer insert usa el email o «Usuario». requireAuth reintenta actualizar el perfil.
Resultado
Un workspace que se entiende: spec → codegen → Express → React. El producto (compañía para un bolo) cabe en cuatro tabs.
11-angular-skill.md
Front en Conciergo
Este repo no usa Angular. El cliente es React + Vite + Tailwind 4 + shadcn. La skill Angular de Task Cloud no aplica.
Qué se aplica
| Práctica | Dónde |
|---|---|
| Wouter | Rutas (/events, /events/map, /matches, /profile) |
| React Query | Listados y estado de sesión |
| Neon Auth | Landing signed-in → redirect /events |
| Framer Motion | Landing y cards |
| Bottom nav + sidebar | app-layout.tsx (móvil / desktop) |
Por qué está este doc
La suite featured 01–11 se llama igual que en Task Cloud / Presencia. Aquí el “11” documenta el techo real del front para no fingir un stack Angular.