# Deploying the Teletón Rifa demo to Vercel

A step-by-step for putting a clickable, no-payment demo of the buyer journey on a
shareable Vercel URL. The demo confirms entries via `/api/demo/confirm` (a
hard-gated, no-charge mint) so the whole flow works without Culqi.

> SAFETY MODEL. The demo arms only when `DEMO_MODE="true"`, and the mint route
> additionally refuses whenever `NEXT_PUBLIC_APP_URL` contains `teleton.pe`. So a
> throwaway `*.vercel.app` deployment runs the demo (including its own production
> URL); the real `rifa.teleton.pe` raffle stays protected even if the flag leaks.
> Never set `DEMO_MODE` on the live `teleton.pe` deployment.

---

## 1. Cloud Postgres (Neon, free)

1. neon.tech → New project. Pick a region close to Perú (AWS us-east is fine for a
   demo). Neon creates a database named `neondb` and a role for you.
2. Project dashboard → Connection Details. Copy two strings, both ending in
   `?sslmode=require`:
   - POOLED (host contains `-pooler`) → `DATABASE_URL` (runtime).
   - DIRECT (toggle "Pooled connection" OFF; host has NO `-pooler`) → `DIRECT_URL`
     (migrations / db:push).

   They look like:
   - `DATABASE_URL="postgresql://<role>:<pwd>@<endpoint>-pooler.<region>.aws.neon.tech/neondb?sslmode=require"`
   - `DIRECT_URL="postgresql://<role>:<pwd>@<endpoint>.<region>.aws.neon.tech/neondb?sslmode=require"`

   Keep the existing `postgres.js` driver (`lib/db/index.ts`); `prepare:false` is
   already set, which the Neon pooler requires. Do NOT switch to
   `drizzle-orm/neon-http` (it cannot run the FOR UPDATE transaction in the webhook
   mint path). If a copied string carries `&channel_binding=require` and the
   connection errors, drop that one parameter.

## 2. Create the schema + seed data (run once, from your machine)

`prepare:false` is already set in `lib/db/index.ts`, so the Supabase pooler works
with no code change.

```bash
cd /Users/sebasbimbi/sebastian-bimbi/projects/teleton
export DATABASE_URL='<neon POOLED URI, ?sslmode=require>'
export DIRECT_URL='<neon DIRECT URI, ?sslmode=require>'
npm run db:push     # uses DIRECT_URL; creates every table/enum/index from lib/db/schema.ts
npm run db:seed     # uses DATABASE_URL; inserts the two raffles (draft) + prizes + tiers
```

Then open a raffle so the buyer flow accepts registrations. Neon Console →
SQL Editor:

```sql
update raffles set status = 'open' where slug = 'warmup-ago-2026';
```

## 3. Import the repo into Vercel

- vercel.com → Add New → Project → import `sebasbimbi/teleton`.
- Team: Bimbi Digital (Hobby is fine). Project name: `teleton`. Branch: `main`.
- Framework preset: Next.js (auto-detected). Root directory: `./`.
- Set the environment variables (next step) BEFORE the build finishes, or just
  redeploy after adding them. `NEXT_PUBLIC_*` vars are baked in at BUILD time, so
  they only take effect on a build that runs AFTER they are set.

## 4. Environment variables (Vercel → Settings → Environment Variables)

Set these for all environments:

| Key | Value |
|---|---|
| `DATABASE_URL` | your Neon POOLED URI (host has `-pooler`, `?sslmode=require`) |
| `DIRECT_URL` | your Neon DIRECT URI (no `-pooler`, `?sslmode=require`) |
| `PAYMENT_PROVIDER` | `culqi` |
| `DEMO_MODE` | `true` |
| `NEXT_PUBLIC_DEMO_MODE` | `true` |
| `NEXT_PUBLIC_TURNSTILE_SITE_KEY` | `1x00000000000000000000AA` |
| `TURNSTILE_SECRET_KEY` | `1x0000000000000000000000000000000AA` |

The two Turnstile values are Cloudflare's documented always-pass test keys. Vercel
builds in production mode, which turns off the dev fail-open, so registration
needs a working site key. The always-pass pair makes the widget auto-verify and
the server `verifyTurnstile` pass, with no real challenge.

Leave UNSET (each fails open / no-ops without it):
`CULQI_*`, `MERCADOPAGO_*`, `UPSTASH_*` (rate limiting fails open),
`DRAND_*` (defaults work), `CRON_SECRET`.
Set `RESEND_API_KEY` + `RESEND_FROM_EMAIL` ONLY if you want the demo to send a
real confirmation email. Do NOT set `NEXT_PUBLIC_APP_URL` to anything containing
`teleton.pe` (that would disarm the demo by design).

Optional, only to also demo the admin dashboard + mock draw:

| Key | Value |
|---|---|
| `ADMIN_SESSION_SECRET` | any 32+ random characters |
| `ADMIN_ALLOWLIST` | `ops@teleton.pe:admin:scrypt$<salt>$<hash>` |

Mint the hash locally first:

```bash
npx tsx -e 'import{hashPassword}from"./lib/auth";console.log(hashPassword("demo1234"))'
```

Then log in at `/admin/login` with `ops@teleton.pe` / `demo1234`.

## 5. Deploy / redeploy

If env vars were added after the first build, force a rebuild so `NEXT_PUBLIC_*`
gets inlined: Vercel → Deployments → latest → ... → Redeploy. The production URL
of this `teleton` project (e.g. `teleton.vercel.app`) is your shareable demo link.

## 6. Smoke-test before showing anyone

On your phone or a browser, open the deployed URL and:

1. The red `MODO DEMO · No se procesan pagos reales` banner is visible.
2. Tap "Quiero participar".
3. Fill the register form (Turnstile auto-passes). Continue.
4. Pick a tier or keep 1 chance. Continue.
5. On the review screen, tap "Pagar S/X".
6. You land on the gracias screen and it flips to "Listo. Ya estás participando."
   with your chance count.

Verify the entry actually minted — Neon Console → SQL Editor:

```sql
select status, provider_status, chances
from purchases order by created_at desc limit 1;   -- status=paid, provider_status=demo_simulado

select count(*) from tickets;                       -- one row per chance
```

## Troubleshooting

- Register button stays disabled / "verificación" never completes → the
  `NEXT_PUBLIC_TURNSTILE_SITE_KEY` always-pass key is missing, or you did not
  redeploy after setting it (it is build-time inlined).
- "La rifa no está abierta" on /participar → you did not flip a raffle to
  `status='open'` (step 2).
- Pay button shows "No encontrado." → `DEMO_MODE` is not `true` on the server, or
  `NEXT_PUBLIC_APP_URL` contains `teleton.pe` (the demo is disarmed there by
  design). Set both flags consistently and redeploy.
- "No pudimos iniciar el pago." → the DB is unreachable (`DATABASE_URL` wrong) so
  order creation failed. Check the Neon URIs (pooled host + `?sslmode=require`).
- Landing renders but is empty (0 prizes, 0 total) → the DB is reachable but not
  seeded, or the seed ran against a different database than `DATABASE_URL`.
