feat(club-002): club PWA: join flow, digital card and installable shell
This commit is contained in:
87
work/artifacts/CLUB-002/architect.md
Normal file
87
work/artifacts/CLUB-002/architect.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Arquitectura — CLUB-002 · PWA Club
|
||||
|
||||
## Objetivo
|
||||
Construir la primera experiencia frontend del Club sobre el backend ya entregado en CLUB-001:
|
||||
- alta/join del Club
|
||||
- tarjeta digital móvil
|
||||
- shell instalable tipo PWA
|
||||
|
||||
## Análisis del frontend actual
|
||||
- La tienda usa Next App Router en `project/frontend/src/app`.
|
||||
- No existe manifiesto PWA ni rutas `/club/*` todavía.
|
||||
- Los flujos autenticados usan proxies Next.js (`/api/auth/*`) para preservar cookies same-origin.
|
||||
- CLUB-001 ya expone backend suficiente para esta fase:
|
||||
- `GET /club/config`
|
||||
- `POST /club/join`
|
||||
- `GET /club/me`
|
||||
- `GET /club/movements`
|
||||
|
||||
## Diseño propuesto
|
||||
|
||||
### 1) Proxy Next dedicado para Club
|
||||
Añadir `project/frontend/src/app/api/club/[...path]/route.ts`.
|
||||
|
||||
Motivo:
|
||||
- preservar la cookie `mdv_club` como same-origin
|
||||
- reenviar `set-cookie` del backend en `POST /club/join`
|
||||
- evitar que componentes cliente hablen directo con `NEXT_PUBLIC_API_URL`
|
||||
|
||||
### 2) Rutas frontend Club
|
||||
Crear páginas:
|
||||
- `/club` → landing/resumen del Club
|
||||
- `/club/join` → alta anónima o acceso al card si ya existe identidad Club
|
||||
- `/club/card` → tarjeta digital con member code, saldo y últimos movimientos
|
||||
|
||||
### 3) Shell móvil instalable
|
||||
Añadir:
|
||||
- `app/manifest.ts`
|
||||
- iconos PWA a partir del branding existente
|
||||
- metadatos PWA en `app/layout.tsx` (`themeColor`, `appleWebApp`, manifest)
|
||||
- CTA de instalación desde cliente (`beforeinstallprompt`) en la superficie Club
|
||||
|
||||
### 4) Estado de Club en cliente
|
||||
No crear aún un provider global complejo.
|
||||
En CLUB-002 basta con fetch puntual desde páginas/componentes Club:
|
||||
- `GET /api/club/config`
|
||||
- `POST /api/club/join`
|
||||
- `GET /api/club/me`
|
||||
- `GET /api/club/movements`
|
||||
|
||||
Esto minimiza acoplamiento y deja abierta una futura extracción a `ClubContext` si la PWA crece.
|
||||
|
||||
### 5) Tarjeta digital
|
||||
La card mostrará:
|
||||
- member code
|
||||
- saldo actual
|
||||
- estado del Club
|
||||
- últimos movimientos
|
||||
- superficie visual tipo credencial móvil
|
||||
|
||||
### 6) Código visual de identificación
|
||||
CLUB-002 mostrará un identificador visual en la tarjeta usando datos ya emitidos por CLUB-001.
|
||||
|
||||
Nota de alcance:
|
||||
- CLUB-003 es la fase de integración TPV/identificación en caja.
|
||||
- Por tanto, CLUB-002 prioriza la UX de tarjeta digital e instalación móvil.
|
||||
- La semántica exacta de escaneo en TPV se cerrará en CLUB-003 para no fijar prematuramente un contrato visual incompatible con caja.
|
||||
|
||||
## Alcance
|
||||
### Sí entra
|
||||
- proxy Next para Club
|
||||
- landing/join/card frontend
|
||||
- manifiesto e iconos PWA
|
||||
- CTA de instalación
|
||||
- movimientos recientes y saldo en tarjeta
|
||||
|
||||
### No entra
|
||||
- earn/redeem en TPV
|
||||
- linking con usuario registrado
|
||||
- recovery codes
|
||||
- admin UI del Club
|
||||
- contrato final de lectura en caja
|
||||
|
||||
## Validación prevista
|
||||
- `cd project/frontend && npm run build`
|
||||
- `cd project && npm run build`
|
||||
- `cd project && npm run typecheck`
|
||||
- `./scripts/verify.sh`
|
||||
22
work/artifacts/CLUB-002/documenter.md
Normal file
22
work/artifacts/CLUB-002/documenter.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# CLUB-002 — Documentation notes
|
||||
|
||||
## New storefront routes
|
||||
- `/club`
|
||||
- `/club/join`
|
||||
- `/club/card`
|
||||
- `/manifest.webmanifest`
|
||||
|
||||
## New same-origin proxy
|
||||
- `GET/POST/PATCH /api/club/[...path]`
|
||||
- Forwards Club requests to backend `/club/*`
|
||||
- Preserves incoming cookies and forwards backend `set-cookie`
|
||||
|
||||
## User-facing behavior
|
||||
- Customers can activate an anonymous Club identity from the storefront without a separate login flow.
|
||||
- After join, the browser keeps the `mdv_club` cookie and the Club card can be reopened later from the same device.
|
||||
- The Club card shows member code, current balance and recent ledger movements.
|
||||
- The storefront now exposes install metadata for a standalone Club PWA shell.
|
||||
|
||||
## Scope reminder
|
||||
- CLUB-002 is frontend/PWA only.
|
||||
- TPV scan semantics, recoveries and account linking stay in later Club phases.
|
||||
52
work/artifacts/CLUB-002/implementer.md
Normal file
52
work/artifacts/CLUB-002/implementer.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Implementer evidence — CLUB-002
|
||||
|
||||
## Resumen
|
||||
Implementé la primera superficie frontend/PWA del Club sobre el backend de CLUB-001.
|
||||
|
||||
## Qué se añadió
|
||||
|
||||
### 1) Proxy Next.js para Club
|
||||
- `project/frontend/src/app/api/club/[...path]/route.ts`
|
||||
|
||||
Función:
|
||||
- reenviar `GET/POST/PATCH` al backend
|
||||
- propagar cookies entrantes
|
||||
- reenviar `set-cookie` del backend para conservar `mdv_club` same-origin
|
||||
|
||||
### 2) Rutas frontend del Club
|
||||
- `project/frontend/src/app/club/page.tsx`
|
||||
- `project/frontend/src/app/club/join/page.tsx`
|
||||
- `project/frontend/src/app/club/card/page.tsx`
|
||||
- `project/frontend/src/components/club/ClubExperience.tsx`
|
||||
|
||||
Comportamiento:
|
||||
- landing Club
|
||||
- alta anónima desde `POST /api/club/join`
|
||||
- tarjeta digital con `memberCode`, saldo y movimientos recientes
|
||||
- CTA para abrir la tarjeta cuando ya existe identidad Club en el dispositivo
|
||||
- bloque de instalación PWA usando `beforeinstallprompt`
|
||||
|
||||
### 3) Manifest e iconos PWA
|
||||
- `project/frontend/src/app/manifest.ts`
|
||||
- `project/frontend/public/images/club-icon-192.png`
|
||||
- `project/frontend/public/images/club-icon-512.png`
|
||||
|
||||
Además:
|
||||
- `project/frontend/src/app/layout.tsx` ahora declara manifest + metadata Apple Web App + viewport themeColor.
|
||||
|
||||
### 4) Descubribilidad en storefront
|
||||
- enlace `Club` añadido en header y footer.
|
||||
|
||||
## Decisiones de implementación
|
||||
- Reutilicé un único componente cliente `ClubExperience` para `/club`, `/club/join` y `/club/card`.
|
||||
- El frontend consume el backend Club vía proxy Next same-origin para que la cookie `mdv_club` quede gestionada correctamente por el navegador.
|
||||
- La tarjeta muestra una credencial visual móvil basada en `memberCode` y deja el contrato final de lectura en caja para CLUB-003, como estaba previsto en la fase TPV.
|
||||
|
||||
## Validación ejecutada
|
||||
- `cd project/frontend && npm run build` ✅
|
||||
- `./scripts/verify.sh` ✅
|
||||
- `git diff --check` ✅
|
||||
|
||||
## Riesgos / siguiente paso
|
||||
- CLUB-003 debe cerrar el contrato exacto de identificación en TPV/caja para usar la tarjeta digital también en escaneo/lectura operativa.
|
||||
- Conviene hacer smoke manual real en navegador/móvil para comprobar el prompt de instalación y la persistencia de `mdv_club` después de `POST /club/join`.
|
||||
29
work/artifacts/CLUB-002/leader-close.json
Normal file
29
work/artifacts/CLUB-002/leader-close.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"feature_id": "CLUB-002",
|
||||
"agent": "leader",
|
||||
"stage": "close",
|
||||
"verdict": "APPROVED",
|
||||
"summary": "CLUB-002 cerrada: PWA del Club con join, tarjeta digital, proxy same-origin y manifest instalable entregados con gates aprobados.",
|
||||
"gates_summary": {
|
||||
"reviewer": "APPROVED",
|
||||
"security": "APPROVED",
|
||||
"qa": "APPROVED"
|
||||
},
|
||||
"artifacts": [
|
||||
"architect.md",
|
||||
"implementer.md",
|
||||
"reviewer.json",
|
||||
"security.json",
|
||||
"qa.json",
|
||||
"documenter.md",
|
||||
"leader-close.json"
|
||||
],
|
||||
"evidence": [
|
||||
"cd project/frontend && npm run build",
|
||||
"cd project && npm run typecheck",
|
||||
"cd project && npm run build",
|
||||
"./scripts/verify.sh",
|
||||
"git diff --check"
|
||||
],
|
||||
"timestamp": "2026-08-26T17:21:45Z"
|
||||
}
|
||||
50
work/artifacts/CLUB-002/qa.json
Normal file
50
work/artifacts/CLUB-002/qa.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"feature_id": "CLUB-002",
|
||||
"agent": "qa",
|
||||
"stage": "qa_gate",
|
||||
"verdict": "APPROVED",
|
||||
"qa_check": "qa",
|
||||
"summary": "QA aprobado: CLUB-002 entrega navegación Club, alta/join, tarjeta digital y shell PWA instalable sin romper el build del storefront ni del backend principal.",
|
||||
"test_results": {
|
||||
"automated": [
|
||||
"cd project/frontend && npm run build ✅",
|
||||
"cd project && npm run typecheck ✅",
|
||||
"cd project && npm run build ✅",
|
||||
"./scripts/verify.sh ✅",
|
||||
"git diff --check ✅"
|
||||
],
|
||||
"coverage": [
|
||||
"ruta proxy /api/club/[...path]",
|
||||
"páginas /club, /club/join y /club/card",
|
||||
"manifest.webmanifest e iconos PWA",
|
||||
"metadata/viewport del layout del storefront",
|
||||
"descubribilidad del Club desde header y footer"
|
||||
],
|
||||
"manual_smoke_recommended": [
|
||||
"Abrir /club y /club/join en navegador real y comprobar que POST /api/club/join deja cookie mdv_club y redirige a /club/card.",
|
||||
"Comprobar en móvil/Chrome que aparece beforeinstallprompt y que la instalación abre start_url=/club en modo standalone.",
|
||||
"Verificar con un miembro ya existente que /club/card recupera saldo y movimientos desde la cookie sin necesidad de login manual."
|
||||
]
|
||||
},
|
||||
"notes": [
|
||||
"El build de Next lista correctamente /club, /club/join, /club/card y /manifest.webmanifest.",
|
||||
"No hay todavía tests E2E navegador para el prompt de instalación; queda como smoke manual recomendado."
|
||||
],
|
||||
"evidence": [
|
||||
"work/artifacts/CLUB-002/implementer.md",
|
||||
"work/artifacts/CLUB-002/reviewer.json",
|
||||
"work/artifacts/CLUB-002/security.json",
|
||||
"project/frontend/src/app/api/club/[...path]/route.ts",
|
||||
"project/frontend/src/app/club/page.tsx",
|
||||
"project/frontend/src/app/club/join/page.tsx",
|
||||
"project/frontend/src/app/club/card/page.tsx",
|
||||
"project/frontend/src/app/manifest.ts",
|
||||
"project/frontend/src/components/club/ClubExperience.tsx",
|
||||
"cd project/frontend && npm run build",
|
||||
"cd project && npm run typecheck",
|
||||
"cd project && npm run build",
|
||||
"./scripts/verify.sh",
|
||||
"git diff --check"
|
||||
],
|
||||
"timestamp": "2026-08-26T17:21:18Z"
|
||||
}
|
||||
47
work/artifacts/CLUB-002/reviewer.json
Normal file
47
work/artifacts/CLUB-002/reviewer.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"feature_id": "CLUB-002",
|
||||
"agent": "reviewer",
|
||||
"stage": "review_gate",
|
||||
"verdict": "APPROVED",
|
||||
"summary": "Revisión técnica aprobada: CLUB-002 añade una primera superficie PWA coherente con CLUB-001 mediante proxy same-origin, rutas /club, tarjeta digital y manifest instalable sin adelantar todavía el contrato TPV de CLUB-003.",
|
||||
"checks": [
|
||||
{
|
||||
"item": "El storefront añade las rutas /club, /club/join y /club/card reutilizando un componente cliente único y sin introducir acoplamiento global innecesario.",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"item": "El proxy Next /api/club preserva el prefijo backend /club/*, reenvía cookies y propaga set-cookie para mantener la identidad mdv_club same-origin.",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"item": "La PWA queda preparada con manifest, iconos, metadata Apple Web App y CTA de instalación, respetando el App Router actual.",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"item": "Los cambios quedaron validados con build del frontend, typecheck/build del backend, verify.sh y git diff --check.",
|
||||
"ok": true
|
||||
}
|
||||
],
|
||||
"issues": [],
|
||||
"notes": [
|
||||
"La tarjeta visual usa un identificador gráfico derivado del memberCode como placeholder UX; el contrato final de lectura/escaneo en caja sigue correctamente diferido a CLUB-003.",
|
||||
"No se añadió provider global de Club; para esta fase el fetch puntual por página mantiene el alcance contenido y simplifica evolución futura."
|
||||
],
|
||||
"evidence": [
|
||||
"work/artifacts/CLUB-002/architect.md",
|
||||
"work/artifacts/CLUB-002/implementer.md",
|
||||
"project/frontend/src/app/api/club/[...path]/route.ts",
|
||||
"project/frontend/src/app/club/page.tsx",
|
||||
"project/frontend/src/app/club/join/page.tsx",
|
||||
"project/frontend/src/app/club/card/page.tsx",
|
||||
"project/frontend/src/components/club/ClubExperience.tsx",
|
||||
"project/frontend/src/app/manifest.ts",
|
||||
"project/frontend/src/app/layout.tsx",
|
||||
"cd project/frontend && npm run build",
|
||||
"cd project && npm run typecheck",
|
||||
"cd project && npm run build",
|
||||
"./scripts/verify.sh",
|
||||
"git diff --check"
|
||||
],
|
||||
"timestamp": "2026-08-26T17:20:40Z"
|
||||
}
|
||||
32
work/artifacts/CLUB-002/security.json
Normal file
32
work/artifacts/CLUB-002/security.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"feature_id": "CLUB-002",
|
||||
"agent": "security",
|
||||
"stage": "security_gate",
|
||||
"verdict": "APPROVED",
|
||||
"security_check": "security",
|
||||
"summary": "Aprobado: CLUB-002 no introduce nuevas superficies privilegiadas y mantiene la identidad Club dentro de un proxy same-origin que reusa la cookie existente sin exponer secretos adicionales.",
|
||||
"checks": {
|
||||
"auth_cookie_flow": "OK: el frontend no gestiona tokens Club en localStorage ni query params; el proxy /api/club reenvía la cookie mdv_club y propaga set-cookie del backend para seguir con el modelo httpOnly de CLUB-001.",
|
||||
"data_exposure": "OK: las vistas Club muestran solo memberCode, saldo y movimientos del propio dispositivo; no se añaden datos administrativos ni personales adicionales.",
|
||||
"xss_client": "OK: la UI renderiza datos como texto y SVG generado en React; no usa dangerouslySetInnerHTML, innerHTML ni eval.",
|
||||
"proxy_integrity": "OK: el proxy apunta a rutas concretas del backend preservando el prefijo /club/* y pasa cookies/Content-Type de forma explícita, sin interpolación insegura de cabeceras arbitrarias del usuario.",
|
||||
"dependencies": "OK: no se añadieron dependencias nuevas de terceros.",
|
||||
"pwa_scope": "OK: manifest e iconos no amplían permisos del navegador ni añaden service worker propio en esta fase."
|
||||
},
|
||||
"notes": [
|
||||
"El identificador gráfico de la tarjeta es una representación visual UX, no un secreto ni un mecanismo criptográfico; la semántica operativa de lectura queda para CLUB-003.",
|
||||
"El uso de NEXT_PUBLIC_API_URL queda encapsulado en el proxy servidor; los componentes cliente hablan con /api/club same-origin."
|
||||
],
|
||||
"evidence": [
|
||||
"project/frontend/src/app/api/club/[...path]/route.ts",
|
||||
"project/frontend/src/components/club/ClubExperience.tsx",
|
||||
"project/frontend/src/app/manifest.ts",
|
||||
"project/frontend/src/app/layout.tsx",
|
||||
"rg -n \"dangerouslySetInnerHTML|eval\\(|new Function|innerHTML|beforeinstallprompt|set-cookie|cookie|NEXT_PUBLIC_API_URL\" project/frontend/src/app/api/club project/frontend/src/components/club project/frontend/src/app/layout.tsx project/frontend/src/app/manifest.ts",
|
||||
"cd project/frontend && npm run build",
|
||||
"cd project && npm run typecheck",
|
||||
"cd project && npm run build",
|
||||
"./scripts/verify.sh"
|
||||
],
|
||||
"timestamp": "2026-08-26T17:20:55Z"
|
||||
}
|
||||
Reference in New Issue
Block a user