En una línea: Landing + funnel de suscripción en Next.js 16 (App Router) sobre Vercel, que al cobrar con Stripe otorga acceso automático a un indicador privado de TradingView y a un canal VIP de Telegram, con estado operativo en Redis (Upstash) y un job diario que revoca accesos vencidos.
package.json, .github/workflows/ci.yml_
package.json_
package.json, components/ui/_
@upstash/redis) — _de lib/redis/_
jose (JWT HS256) para los tokens de beta — _de lib/beta/token.ts_
lib/**/schemas_
vercel.json, package.json_
*.test.ts) · Playwright (E2E en e2e/) — _de package.json_
app/
(landing)/ # sitio público: home, pricing, about, faq, how-it-works, education
(admin)/admin/ # dashboard interno: grant queue, customers/[id]/{stripe,telegram,tradingview}
api/ # stripe/{checkout,webhook} · telegram/webhook · tv/grant-manual · cron/revoke-expired
beta/ # activación por promo-code (JWT)
checkout/success/ # confirmación post-pago + invite Telegram
legal/ # billing, cookies, privacy, terms, trading-disclaimer
actions/ # Server Actions (admin.ts, checkout.ts)
components/ # admin · checkout · landing · shared · ui (shadcn)
lib/
redis/ # client, keys, session-store, customer-paid-store, lock, idempotency
stripe/ # client, checkout, webhook events, dispatch, price-map
telegram/ # client, invite, kick, membership, dispatch
tradingview/ # client, grant, revoke, session, expiration
access/ # fallback-queue
reconcile/ # reconcile.ts (lógica del cron)
beta/ consent/ constants/ feature-flags.ts
scripts/ # CLIs: stripe-setup, tv-access, tg-set-webhook, mint-beta-tokens, verify-*
e2e/ openspec/ .docs/ · next.config.ts · vercel.json · .github/workflows/ci.yml
| Componente | Rol | Dónde |
|---|---|---|
| Landing | Sitio público (home, pricing, FAQ, etc.) | app/(landing)/, components/landing/ |
| Checkout API + Server Action | Crea la Stripe Checkout Session | app/api/stripe/checkout/route.ts, actions/checkout.ts |
| Stripe Webhook | checkout.session.completed + ciclo de suscripción → grant/revoke | app/api/stripe/webhook/route.ts, lib/stripe/dispatch.ts |
| Capa TradingView | Cliente endurecido del API privado (grant/revoke de acceso Pine) | lib/tradingview/{client,grant,revoke,session}.ts |
| Capa Telegram | Invite single-use, kick, tracking de membresía | lib/telegram/{client,invite,kick,membership}.ts |
| Capa Redis | Estado: sesión TV, idempotencia, customer-paid, lock, fallback queue | lib/redis/ |
| Cola de fallback | Cola en Redis para grants fallidos; se drena por endpoint/operador | lib/access/fallback-queue.ts, app/api/tv/grant-manual/route.ts |
| Cron de reconciliación | Revoca accesos vencidos (TV + Telegram) | app/api/cron/revoke-expired/route.ts, lib/reconcile/reconcile.ts |
| Admin Dashboard | Grant queue + gestión de clientes + ops manuales | app/(admin)/admin/, actions/admin.ts |
| Beta | Gate por token JWT para ~10 testers | app/beta/page.tsx, lib/beta/token.ts |
Pago → acceso (principal):
app/(landing)/pricing/)
actions/checkout.ts) crea la Checkout Session (lib/stripe/checkout.ts) → redirige a Stripe
checkout.session.completed a app/api/stripe/webhook/route.ts
STRIPE_WEBHOOK_SECRET) y despacha (lib/stripe/dispatch.ts)
lib/tradingview/grant.ts → sesión cacheada en Redis → API privado de TradingView)
lib/redis/{customer-paid-store,idempotency}.ts)
lib/access/fallback-queue.ts)
lib/telegram/invite.ts) y lo muestra en app/checkout/success/
Revocación diaria: Vercel Cron (06:00 UTC) → /api/cron/revoke-expired (auth CRON_SECRET) → lib/reconcile/reconcile.ts busca vencidos en Redis → revoca en TV + Telegram, con tope de blast-radius (RECONCILE_MAX_REVOCATIONS).
Fallback manual: POST /api/tv/grant-manual (auth OPERATOR_SECRET) o el Admin Dashboard drenan los grants que fallaron.
flowchart LR
User --> Landing["Landing / Pricing"]
Landing --> CO["Server Action checkout"]
CO --> Stripe[("Stripe Checkout")]
Stripe -->|webhook| WH["/api/stripe/webhook"]
WH --> TV["lib/tradingview/grant"]
WH --> TG["lib/telegram/invite"]
WH --> Redis[("Upstash Redis")]
TV -. falla .-> FQ["Fallback queue"]
FQ --> Manual["/api/tv/grant-manual · Admin"]
Cron["Vercel Cron 06:00"] --> REC["lib/reconcile"] --> Redis
REC --> Revoke["TV revoke + Telegram kick"]
lib/stripe/, env STRIPE_*_
lib/redis/client.ts, env KV_REST_API_*_
lib/tradingview/client.ts, env TV_USERNAME/PASSWORD/PINE_IDS_
lib/telegram/client.ts, env TELEGRAM_*_
vercel.json (0 6 * * *)_
components/shared/analytics/_
pnpm build (next build) · Dev: pnpm dev · Start: pnpm start
pnpm lint · pnpm format:check · pnpm type-check · pnpm test (Vitest) · pnpm test:e2e (Playwright)
.github/workflows/ci.yml — matriz lint/format/type-check/test en Node 22, en PR y push a main
STRIPE_SECRET_KEY, STRIPE_PRICE_*, STRIPE_WEBHOOK_SECRET, KV_REST_API_URL/TOKEN, TELEGRAM_BOT_TOKEN/CHAT_ID/WEBHOOK_*, TV_USERNAME/PASSWORD/PINE_IDS, OPERATOR_SECRET, CRON_SECRET, RECONCILE_MAX_REVOCATIONS, BETA_JWT_SECRET, NEXT_PUBLIC_GA_ID, NEXT_PUBLIC_APP_URL
lib/redis/keys.ts). Simple para serverless, pero dependencia operativa única. ⚠️
lib/tradingview/client.ts maneja endpoints privados/no documentados con UA de navegador y cookies sessionid+sessionid_sign. Frágil: cambios en el WAF o expiración de sesión pueden romper los grants silenciosamente. ⚠️
next.config.ts documenta que la CSP con nonce quedó fuera de alcance (sigue unsafe-inline). ⚠️
scripts/mint-beta-tokens.ts), sin enrolamiento self-serve.
NEXT_PUBLIC_SHOW_TESTIMONIALS/EDUCATION) — sin servicio de gestión de flags.
auto-renew-compliance-ux, checkout-success-page, reticle-motif-system, telegram-optional-checkout (en openspec/changes/).
In one line: A Next.js 16 (App Router) subscription landing on Vercel that, on a Stripe payment, auto-grants access to a private TradingView indicator and a Telegram VIP channel; operational state lives in Upstash Redis, and a daily cron revokes expired access.
pnpm build / pnpm dev; CI runs lint + type-check + Vitest + (Playwright E2E) on Node 22; deployed to Vercel.
Read from real code at
../predatoralgo(commit218d1f9, branchmain) + GitHubswissbull-group/predatoralgo. Paths, stack, and integrations are verified against the source; nothing invented.