# Teleton Rifa: Admin and Operations Layer Design

## Summary
This document specifies the full admin and operations layer for the Teleton Peru raffle on Next.js: an internal dashboard, a fair and auditable live-draw runbook, a roles and permissions model, notification flows, Teleton-facing reporting and exports, launch and draw-day operational runbooks, and monitoring and alerting. Every design choice is driven by the three hard facts established in research: this handles real money (so payment state lives in webhooks and reconciliation, never the browser redirect), real personal data under Ley 29733 plus DS 016-2024-JUS (so least-privilege, audit logs, retention, and a 48-hour breach path are built in), and a national charity's reputation (so the draw must be reproducible, witnessed by a DGIN veedor, and publicly verifiable). The operations layer is also the fee-survival and compliance engine: it makes the upsell-driven pricing work against the S/3.50 Culqi minimum, it produces the exact records the bases del sorteo and the MININTER Formulario 6 require, and it gives Teleton clean per-event exports of entries, revenue, and winners. The single biggest schedule risk is not software; it is the MININTER authorization chain (classify, lock prizes, file, secure veedor, publish matching bases, freeze pricing), which the admin layer must be ready to feed but cannot itself resolve.

## Body
## 0. Grounding and scope of this section

This is the admin and operations layer only. The public buyer flow (landing, registration, upsell, payment redirect, confirmation email) is specified by the Requirements brief and the Payments brief; this document references it but designs the staff-facing and back-office systems that sit behind it.

Three established facts from the research drive every decision here, and I restate them because they are load-bearing:

- Payment state is owned by the provider webhook, not the browser redirect (Payments brief, "Idempotency and webhook reconciliation"). The admin layer treats the webhook and a nightly reconciliation job as the source of truth. A browser that returns from the Culqi popup proves nothing.
- Personal data is regulated by Ley 29733 plus the new reglamento DS 016-2024-JUS, in force since 31 March 2025 (Legal brief, section 2). The admin layer enforces least-privilege access, an append-only audit log, defined retention, and a 48-hour breach-notification path.
- The draw must be reproducible, auditable, and witnessed by a DGIN veedor (Legal brief, sections 1 and 3). The draw tooling in this document exists to make a public, defensible draw, not just to pick a name.

Mandated stack: Next.js. The admin app is a protected area of the same Next.js deployment (for example the route group `/admin`), sharing the database and the provider adapter with the public flow, so there is one source of truth and no data sync between systems.

Naming convention used below: an "entry" or "chance" (oportunidad) is the unit that enters the draw. A "purchase" (compra) is one paid transaction that yields one or more chances. A "participant" (participante) is a person, keyed by phone and email. This matches FR-1.3.1 and FR-1.3.2.

[inferred] markers below flag anything not directly stated in the transcripts or fixed by the Payments or Legal briefs.

---

## 1. Data model the admin layer reads and writes

The admin features are only as good as the schema beneath them. This is the minimum model that supports every feature in this document, the draw, and the compliance records. It is the same model the public flow writes to.

| Table | Key fields | Why the admin layer needs it |
|---|---|---|
| `events` (raffles) | id, slug, name, status (draft / open / closed / drawn / settled), opens_at, closes_at, draw_at, prize_count, pricing_config (JSON), bases_pdf_url, mininter_resolution_ref | Per-raffle configuration (FR-1.9.3). One row for the August warm-up, one for the September 12 main. Pricing is config, not code (FR-1.2.3, FR-1.9.3). |
| `participants` | id, full_name, phone, email, data_consent_at, data_consent_version, marketing_consent (bool), created_at | The person identity keyed by phone and email (FR-1.7.3). Supports post-hoc dedupe under Fork B and a future Fork A without migration. |
| `purchases` | id, event_id, participant_id, chances_count, gross_amount, fee_covered_amount, currency, status (pending / confirmed / failed / refunded / expired), rail (card / yape / pagoefectivo / manual), idempotency_key, provider_charge_id, provider_status, source_channel (UTM), created_at, confirmed_at | One row per transaction. `status = confirmed` is the only state that enters the draw (NFR-5.3.1). `provider_charge_id` makes every entry reconcilable to a provider transaction (NFR-5.3.2). |
| `chances` (entries) | id, purchase_id, event_id, participant_id, chance_number (sequential per event), is_void (bool) | One row per chance so the draw selects from real rows, not an expanded count at draw time. This is the cleaner of the two options in FR-1.3.2. A refund sets `is_void = true` (Payments brief, "Refunds"). |
| `payment_events` (webhook log) | id, provider, provider_event_id (unique), event_type, raw_payload (JSON), signature_valid (bool), received_at, processed_at, result | Idempotency on `provider_event_id` (Payments brief). Append-only. Drives the reconciliation view. |
| `audit_log` | id, actor_user_id, action, target_type, target_id, before (JSON), after (JSON), ip, created_at | Append-only record of every admin action and every payment-status change (NFR-5.3.5). The spine of auditability and the data-protection security requirement (Legal item 11). |
| `draws` | id, event_id, seed_commitment_hash, seed_revealed, snapshot_hash, snapshot_csv_url, method, executed_by, veedor_name, notary_name, executed_at, locked_at, results_published_url | The auditable draw record (NFR-5.3.3, Legal section 3). One row per executed draw. |
| `draw_results` | id, draw_id, prize_id, chance_id, participant_id, rank (winner / suplente), notified_at, claimed_at | The frozen winners and alternates. |
| `prizes` | id, event_id, name, brand, market_value, display_status (revealed / coming_soon / hidden), substitute_of (nullable), sort_order | Prizes come from brands and change late (FR-1.9.3). `display_status` lets the public page show "premio por anunciar" without a deploy. `market_value` feeds the bases and the MININTER filing (Legal section 3). |
| `notifications` | id, participant_id, event_id, type (confirmation / winner / non_winner / undeliverable_followup), channel (email), status (queued / sent / delivered / bounced / failed), provider_message_id, sent_at | Drives the winner and thank-you flows and undeliverable handling (section 5). |

Two model rules the admin UI must enforce, both from the research:

- Confirmed entries are immutable. The admin layer never lets a user silently edit a confirmed purchase or a chance (NFR-5.3.5). Corrections happen through explicit, logged actions (void, refund, manual adjustment), each writing to `audit_log`.
- One charge, one entry set, one ledger row, idempotent on `provider_charge_id` (Payments brief). The webhook handler, not the admin, mints chances. The admin can mint chances only through the explicit manual-entry path (section 1.6 below), which is itself logged and clearly flagged as `rail = manual`.

---

## 2. Admin dashboard features

### 2.1 Live entries and revenue dashboard (home)

The landing screen after login. Per-event, with an event switcher pinned at the top (August warm-up vs September 12 main), because the two raffles have separate pools and separate money (FR-1.9.1).

Headline tiles, all scoped to the selected event and all derived from `purchases` and `chances` where `status = confirmed`:

- Confirmed chances in the pool (the draw weight) and confirmed unique participants.
- Gross revenue confirmed, net revenue after provider fees [inferred fee estimate per rail until the provider statement reconciles], and "fee-covered" amount donors opted to add (the cubre la comision toggle from the Payments brief).
- Pending purchases and pending amount (started, not yet confirmed), shown separately and never added to the pool total. This is the visible expression of NFR-5.3.1.
- Failed and expired in the last 24 hours, as an early fraud and friction signal.
- Refunded count and amount.

Because traffic is bursty and influencer-driven (NFR-5.2.1), the dashboard adds:

- A live sparkline of confirmed purchases per minute for the current and prior hour, so an operator can see a live or a post landing in real time.
- A by-channel breakdown from `source_channel` (UTM), so Teleton sees which influencer post or platform drove entries (FR section 4, optional UTM field; decision 11). This is low cost and high value and is the answer to "which channel worked".
- A by-rail breakdown (Yape vs card vs PagoEfectivo), because rail mix drives the real fee drag and validates the Payments-brief recommendation to push Yape on small tickets.

Refresh model [inferred]: the read tiles refresh on an interval (for example every 20 to 30 seconds) rather than per-event streaming, to keep the admin off the hot write path during spikes. The dashboard reads from cached aggregates, not by scanning every row on each load.

### 2.2 Participant table (search and filter)

A table over `participants` joined to their confirmed chances.

- Columns: name, phone (masked by default, see roles in section 3), email (masked by default), total confirmed chances in the selected event, total across both events, first seen, last purchase, marketing-consent flag.
- Search: by name, phone, or email. This is the same lookup that a future Fork A "find my entries" would use, so building it now de-risks the September upgrade (Requirements section 3).
- Filters: event, has at least one confirmed chance, marketing-consent yes or no, source channel.
- Row detail: the person's purchase history, each purchase's status, rail, amount, and provider charge id, and the list of their chance numbers. This is also where a privileged operator initiates a refund or a manual adjustment, each logged.

Data-protection note baked into the UI: phone and email render masked (for example `+51 9** *** *87`, `r***@gmail.com`) for viewer roles, and unmasking is a logged action available only to operator and admin roles (Legal section 2, least-privilege and audit). This is a direct control for the "displaying stored personal data" risk the Requirements brief raised under Fork A.

### 2.3 Transaction table (search and filter)

A table over `purchases` joined to `payment_events`.

- Columns: created time, participant, event, chances, gross amount, fee-covered amount, rail, status, provider charge id, provider status, source channel.
- Filters: status (pending / confirmed / failed / refunded / expired), rail, event, date range, amount range, "mismatch" (see reconciliation below).
- Each row links to the underlying webhook events for that charge, so an operator can see exactly what the provider sent and when. This is the auditability requirement made operable (NFR-5.3.2).
- Export of the current filtered view to CSV (section 6).

### 2.4 Payment-reconciliation view

This is the trust core of the admin layer and follows the Payments brief directly ("Reconcile asynchronously", "nightly reconciliation job").

- A nightly (and on-demand) job compares the local `purchases` and `chances` against the provider's charge list (Culqi charges, or Mercado Pago if it is the live rail). It surfaces four mismatch classes:
  1. Confirmed locally but not found or not paid at the provider (the dangerous one: an entry in the pool without real money).
  2. Paid at the provider but still pending locally (a missed or delayed webhook; the buyer paid and is owed their chances and confirmation email).
  3. Refunded at the provider but chances not voided locally (fairness leak into the draw; Payments brief, "Refunds must reverse the raffle entry").
  4. Amount mismatch (local amount differs from provider amount).
- For PagoEfectivo specifically, the view models the explicit Pending state with a timeout and reconciles against the provider as the backstop if a webhook is missed (Payments brief). A buyer who pays a CIP voucher at an agent hours later must still get chances and an email.
- Each mismatch is actionable: re-fetch from provider, re-process the stored webhook payload (idempotently), manually confirm with a required reason (logged), or void. No action silently mutates a confirmed entry; every action writes `audit_log`.
- A "reconciled through" timestamp shows when the pool was last proven against the provider. Teleton finance can reconcile total entries against total money received straight from this view (NFR-5.3.2).

This view is what lets Sebastian and Teleton stand behind the statement "every chance in the draw corresponds to confirmed money", which is the entire reputational point.

### 2.5 Prize and inventory management

Prizes are donated, late, and changeable (FR-1.9.3, decision 6). The admin must absorb that without a deploy.

- CRUD over `prizes` per event: name, brand, market value, photo, sort order, and `display_status` (revealed / coming_soon / hidden). The public landing reads this, so Teleton can publish "premio por anunciar" placeholders and reveal prizes as brand deals close, with zero code change (FR-1.9.3).
- `market_value` is mandatory before an event can move to `open`, because the bases del sorteo and the MININTER Formulario 6 both require a precise valuation per prize (Legal section 3, Legal item 2). The UI enforces this as a publish gate.
- A substitution field (`substitute_of`) plus a logged action to swap a prize, because the bases need a "substitution of equal or greater value" clause given prizes depend on brands (Legal section 3, substitution clause). When a prize is swapped after the bases are published, the action is logged and flagged for legal review, since the published bases cannot be changed casually (Legal section 5, INDECOPI).
- Prize-to-winner-count mapping: the number of winners per event defaults to the prize count (about 5, about 10) at one winner per prize (FR-1.8.2). The admin sets this explicitly per event and can mark whether one person may win more than one prize (decision 4, draw without replacement per FR-1.8.5).

### 2.6 Per-raffle configuration

One configuration surface per `events` row, so the same codebase runs both raffles and future ones (FR-1.9.3).

- Event identity, status, open and close windows, draw date and time.
- Pricing config as data, never code (FR-1.2.3): base price per chance, the upsell ladder (the literal "1 mas por S/5, 2 mas por S/10, 3 mas por S/15" from the audio), an optional maximum chances per person (decision 1), and the cubre-la-comision toggle copy and amount (Payments brief, lever 3). Because the audio itself states two conflicting pricing framings, this MUST be editable config and MUST be frozen before the bases are filed (decision 1, decision 6, Legal item 6).
- A "smallest card or voucher purchase" floor, so the system can steer a bare S/5 to Yape and require bundles on card and PagoEfectivo to clear the S/3.50 minimum-fee cliff (Payments brief, levers 1 and 2). This is a config value, not a hardcoded constant, because the fee schedule is provider-dependent and still being negotiated.
- Status transitions are guarded: an event cannot move to `open` without prizes priced, pricing frozen, bases PDF uploaded, and (for the legal record) the MININTER resolution reference attached or explicitly waived by an admin with a logged reason. This wires the Legal launch-blockers into the software so the app cannot accidentally open an unauthorized raffle.

### 2.7 Manual-entry path (offline and direct-Yape cases)

Rodrigo's distribution is social and live, and direct Yape transfers happen (the Payments brief positions Yape as the dominant rail). Some money will arrive outside the hosted checkout: a direct Yape to Teleton's number during a live, a corporate or cash donation, a buyer the team helped by phone.

- A privileged manual-entry form: select event, enter or match a participant (reuses the participant search so it links to an existing person if phone or email matches, preserving the per-person picture for Fork B), enter chances, amount, rail = `manual`, and a required proof reference (Yape operation number, deposit slip id, or a note).
- Manual entries are clearly flagged `rail = manual` everywhere (dashboard, tables, exports) and always written to `audit_log` with the actor. They count in the pool like any confirmed entry, but they are visibly distinct so reconciliation and the draw record can account for money that did not flow through the gateway.
- [inferred] A manual entry requires the operator or admin role, never viewer. This is the one path that mints chances without a webhook, so it is the most sensitive write in the system and is gated and logged accordingly.
- Caution surfaced for Teleton: manual reconciliation is an audit and fraud liability (Payments brief warns against the "upload your receipt" pattern). The manual path is a controlled, logged exception for genuine offline money, not a primary rail, and the UI says so.

### 2.8 Refund handling

Refunds are a safety net and a reputation protector (Payments brief, "Refunds").

- A refund action on a purchase, available to operator and admin, requiring a reason from a fixed list (duplicate charge, proven error, other-with-note). The default Teleton policy [inferred, to confirm with Teleton] is no refunds except duplicate charge or proven error, stated in the bases the buyer accepts (Payments brief; Legal section 3, taxes and terms).
- The refund calls the provider's programmatic refund (Culqi Devoluciones or Mercado Pago refund via SDK) and, critically, voids the associated chances (`is_void = true`) before the draw filter runs, so a refunded charge never sits in the pool (Payments brief, "Refunds must reverse the raffle entry"). The refund webhook event is the confirmation, and it too is idempotent.
- Every refund writes `audit_log` and is visible in the reconciliation view's "refunded but not voided" check as a guardrail in case the void step ever fails.

---

## 3. Roles and permissions

The data-protection regime requires least-privilege access to the participant database (Legal item 11). The draw's integrity requires that running it is a restricted, witnessed action (Legal section 1). So roles are not optional polish; they are a compliance control.

Four roles [inferred role set; the principle of least privilege is from the Legal brief, the specific roles are a design choice to confirm with Teleton]:

| Role | Can do | Cannot do |
|---|---|---|
| Viewer (Teleton comms, leadership) | See dashboards and aggregate numbers, see tables with phone and email masked, export aggregate revenue and counts (not raw PII) | Unmask PII, run a draw, refund, manual-entry, edit config |
| Operator (raffle ops, support) | Everything Viewer can, plus unmask PII (logged), handle reconciliation actions, manual-entry, refunds, prize and inventory edits, trigger and resend notifications | Run or lock a draw, change roles, change pricing once frozen |
| Draw officer | Everything Operator can, plus execute the draw runbook (commit seed, freeze snapshot, select, lock results) | Change roles, delete audit records (no one can) |
| Admin (Sebastian, Teleton system owner) | All of the above, plus user and role management, event configuration and status transitions, pricing freeze, retention and deletion jobs | Edit or delete the append-only audit log (immutable by design for everyone) |

Cross-cutting rules:

- Separation of duties on the draw: the person who runs the draw should ideally not be the same person who can silently alter entries. In practice the draw is also performed in front of a DGIN veedor and possibly a notary (Legal section 1), which is the real external control; the role split is the internal complement.
- Every privileged action (unmask, refund, manual-entry, config change, draw step) writes `actor_user_id` to `audit_log`. There is no shared admin login; named accounts only, because attribution is the point.
- Authentication [inferred]: email-based login with a second factor for operator, draw officer, and admin, given this is a money and PII system. Sessions for the admin area are short-lived and the admin area is not indexed and not embeddable.

---

## 4. The draw execution runbook and UI

This is the highest-reputation surface. It must produce a draw that is fair, reproducible, auditable, and defensible if any participant or journalist questions it (NFR-5.3.3), conducted with a DGIN veedor present and (advisable) before a notary (Legal section 1, section 3). The design assumes the draw is run live on camera, which both aligns with Rodrigo's "hacer live" plan and maximizes perceived fairness (FR-1.8.4).

### 4.1 Method: commit-reveal seeded RNG with a frozen snapshot

The method is chosen so that nobody, including Teleton and Sebastian, can have rigged the result, and anybody can verify it after the fact.

1. Pre-commit the seed (before the draw). The draw officer generates a random seed, and the system stores only its hash (`seed_commitment_hash`) and shows that hash publicly before the draw begins. Publishing the commitment first proves the seed was fixed in advance and not chosen to produce a desired winner. [inferred mechanism; the brief requires "a recorded random seed or a witnessed live draw", and commit-reveal is the standard way to make a seed verifiable.]
2. Freeze the entry snapshot. At draw time the system takes an immutable snapshot of all confirmed, non-void chances for that event, stores its hash (`snapshot_hash`) and a downloadable CSV (`snapshot_csv_url`), and shows the snapshot hash and the total chance count on screen. After this moment the pool for that draw cannot change (NFR-5.3.3, "a frozen snapshot of confirmed entries at draw time"). The reconciliation view should be green before this step so the snapshot equals proven money.
3. Exclude the invalid. The snapshot includes only `status = confirmed` purchases and `is_void = false` chances, and excludes any ineligible participants the bases name (for example Teleton staff and families, a common and defensible exclusion per Legal section 3). The exclusion list is shown and counted so the audience sees what was removed and why.
4. Reveal and select. The draw officer reveals the seed live. The system shows that the revealed seed matches the pre-committed hash, then runs a deterministic, documented selection (seed plus a fixed algorithm over the ordered snapshot) to pick one winner per prize, drawing without replacement so no chance wins twice unless Teleton chose otherwise (FR-1.8.5, decision 4). It also selects a configured number of alternates (suplentes) per prize [inferred, strongly advisable] so an unreachable or disqualified winner has a defined, pre-drawn replacement rather than an ad-hoc redo.
5. Lock results. Results are written to `draw_results`, the `draws` row is locked (`locked_at`), and from that moment results are immutable. No re-draw is possible without an explicit, logged, admin-level action that itself creates a new draw record citing a reason (for example, the veedor required it). Silent re-rolling is impossible by construction.
6. Generate the publicly verifiable record. The system produces a one-page public draw certificate containing: event name, draw date and time, the DGIN veedor name (and notary if used), the pre-committed seed hash, the revealed seed, the snapshot hash and total chance count, the selection algorithm name and version, and the winners (with privacy-appropriate identification, see below). Anyone can re-run the published algorithm with the revealed seed against the published snapshot hash and confirm the same winners. This is the "publicly verifiable record" requirement made concrete.
7. Export winners for operations (section 6), which kicks off the notification flow (section 5).

### 4.2 The draw UI, step by step for the operator

A single guided screen with a strict forward-only stepper, so a nervous operator running it on a livestream cannot skip a control:

- Step 1 Pre-flight checklist (must all be checked to proceed): reconciliation is green as of a shown timestamp; pricing was frozen; the bases PDF is published and matches the filing; the MININTER resolution reference is attached; the DGIN veedor is present and named; the exclusion list is loaded. This checklist is the software encoding of the Legal launch-blockers.
- Step 2 Commit seed. One button generates and commits the seed; the screen displays the commitment hash in large type, suitable for showing on camera.
- Step 3 Freeze snapshot. One button freezes and displays the snapshot hash and the total confirmed chance count and the excluded count.
- Step 4 Reveal and draw. One button reveals the seed, shows the hash match, and renders the winners one by one (a deliberate reveal cadence that suits a live; respects reduced-motion per NFR-5.4.4).
- Step 5 Lock. A confirm-locked action freezes results and generates the certificate and exports.

The whole screen is keyboard-operable and screen-reader-announced (NFR-5.4.1, NFR-5.4.2), because accessibility is a Teleton brand imperative and the operator may rely on it.

### 4.3 What the draw never does

- It never includes a pending, failed, expired, refunded, or void entry (NFR-5.3.1).
- It never lets the pool change after the snapshot.
- It never produces a result that cannot be reproduced from the published seed and snapshot.
- It never silently re-draws.

### 4.4 Decisions this runbook depends on (from the research)

- Whether the draw is executed in-app live or exported and drawn by other means (decision 5). This design supports the in-app live draw because it is the most verifiable and matches "hacer live"; if Teleton prefers an external draw, the same snapshot, seed commitment, and certificate still apply and the system just records the external method.
- Winners per event and whether one person can win multiple prizes (decision 4).
- Exact draw date and time and who runs it (decision 5).
- Coordination of the DGIN veedor and the notary decision (Legal item 4). The software cannot schedule the veedor; it can only refuse to run without one named.

---

## 5. Notifications

Email is the only confirmation channel named in the audio, and the brief is explicit not to build SMS or WhatsApp confirmation unless Rodrigo asks (FR-1.5.4). So all participant-facing notifications are email, from a Teleton-controlled domain for deliverability and trust (decision 8). All copy is Peruvian-register Spanish, soles as S/, no em-dashes, no emojis (NFR-5.5).

### 5.1 Purchase confirmation (already in the public flow, surfaced in admin)

Triggered by the payment-confirmed webhook, never by the browser return (Payments brief; FR-1.5.1). States the number of chances, the amount paid, and which raffle event the entry belongs to (FR-1.5.2). The admin `notifications` table shows its send and delivery status so an operator can see if a confirmation bounced and resend it.

### 5.2 Winner notification flow

Kicked off when the draw is locked (section 4) and winners are exported.

- The system queues a winner email to each `draw_results` winner. The email tells the person they won, which prize, and the next step to claim it, and it states the claim deadline and the documents needed (DNI at handoff), because prizes must be delivered within 90 calendar days and the bases must state the claim process and documents (Legal section 3, section 5).
- It does not put prize-claim secrets in a public place. Winner identity on the public certificate is shown in a privacy-appropriate form (for example first name plus masked phone or a winning chance number), not full PII, to satisfy both transparency and data protection. [inferred balance; the exact public identification format is a legal and comms decision tied to the bases.]
- An operator works a winners worklist in the admin: queued, sent, delivered, contacted, claimed. Each winner row tracks `notified_at` and `claimed_at`.
- Because in-kind prizes raise a tax question (who bears the income tax on the prize), the winner email and the claim step reference whatever the bases decided on tax (gross-up vs winner pays), so the advertised prize value is not misleading (Legal section 4, section 5; decision and disclosure required). The admin does not compute tax; it surfaces the bases-defined statement.

### 5.3 Non-winner thank-you

[inferred that Teleton will want this; the audio does not request it, but it is consistent with the supportive Teleton tone and is low cost.] After the draw is locked, the system can send a thank-you to non-winning confirmed participants: thank them, restate the impact their aporte funded, and (only if they gave marketing consent) invite them to the next raffle or to follow Teleton. Sending this is an explicit operator action, gated on the participant having the relevant consent, never automatic spam. Marketing contact requires the separate opt-in consent captured at registration (FR section 4, marketing-consent field; Legal section 2, consent must be unbundled).

### 5.4 Undeliverable handling

- The email provider's bounce and complaint webhooks update `notifications.status` to bounced or failed (decision 8 names the email service). Bounced confirmations and bounced winner emails surface in an admin "undeliverable" worklist.
- For a bounced winner email, the operator has a defined fallback: use the captured phone to reach the winner (phone was captured at registration precisely as the reliable Peru contact and prize-handoff backup, per FR section 4), and log the contact attempt. This is why capturing both phone and email, not one-or-the-other, was the right call.
- An unreachable winner within the bases-defined window triggers the pre-drawn alternate (suplente) from section 4.1, following the bases' rule for unclaimed prizes (Legal section 3). The replacement is a logged action referencing the original draw, not a fresh secret draw.

### 5.5 Notification rules

- Idempotent sends: one confirmed purchase yields exactly one confirmation, keyed off the charge id, so a re-fired webhook never double-emails (consistent with the one-charge-one-entry rule).
- Every send and every status change writes to `notifications` and, for winner and bulk actions, to `audit_log`.
- No notification reveals more PII than necessary, and no marketing send goes to a participant without marketing consent.

---

## 6. Reporting and exports for Teleton, and the compliance records

Teleton runs "el filtro" and must be able to reconcile money, file with MININTER, publish the bases, and report to authorities (FR-1.6.2, FR-1.6.4; Legal sections 1, 3, 4). The admin provides clean, per-event exports and the specific records the compliance regime needs.

### 6.1 Operational exports (CSV, per event)

- Entries export: one row per confirmed chance or per confirmed purchase (both views available), with participant name, masked or full contact (full only for operator and admin, and the export of full PII is itself a logged action), event, chances, amount, rail, provider charge id, source channel, timestamps. This is the draw pool and the participant master (FR-1.6.4).
- Revenue export: per-event and per-day gross, fees, net, fee-covered, and rail mix, so Teleton finance can reconcile against the provider's settlement and compute the Impuesto a los Juegos base (10 percent on ticket value, not prize value, per Legal section 4). The revenue export explicitly outputs the total nominal ticket value, which is the exact base imponible the municipal tax needs.
- Winners export: from `draw_results`, with prize, winner, alternate, notification and claim status, for prize fulfillment and the post-event report.
- Reconciliation export: the mismatch report from section 2.4 as of a timestamp, as the auditor-facing artifact (NFR-5.3.2).

All exports are UTF-8 CSV, soles formatted as `S/` plus amount, dates in a clear Lima-local format, and every export action is logged with actor and row count (data-protection accountability, Legal section 2).

### 6.2 Records the bases del sorteo and the MININTER filing require

The admin must be able to produce, for each event, the data that the bases and Formulario 6 demand (Legal sections 1 and 3). The admin does not write the legal text (Teleton legal supplies it, decision 9, Legal item 5), but it holds and exports the factual inputs:

- The final prize list with `market_value` per prize (Legal section 3, prizes; required for the filing valuation, Legal item 2). The publish gate in section 2.6 enforces that this exists before the event opens.
- The pricing model and ticket numbering: the frozen pricing config and the sequential `chance_number` per event give the "numbering and sale period" the Formulario 6 asks for (Legal section 1).
- The sale period: `opens_at` and `closes_at` per event.
- The draw record: the `draws` row plus certificate (section 4.6) is the documented, objective selection method, the seed, the snapshot, and the veedor name, which is exactly what defends the draw to DGIN and INDECOPI (Legal sections 1, 3, 5).
- The post-event financial report input: the revenue export plus the winners export together give Teleton the numbers for the financial report submitted to the authority after the event (Legal section 1, post-event financial report).

### 6.3 What reporting must respect

- Retention and minimization (Legal section 2, item 12): the admin has an admin-only, logged job to anonymize or delete non-winning participant contact data a defined period after the draw, keeping only what tax and audit rules require. The retention period is set with Teleton legal [inferred period].
- Data portability (Legal section 2, item 12): a per-subject data export on request, available to operator and admin, fulfilling the new reglamento's portability right.
- The bases are binding and cannot change after publication (Legal section 5). The reporting layer reads the published prize values and pricing; if a substitution happens, it is logged and flagged for legal, never silently rewritten in a report.

---

## 7. Operational runbooks

### 7.1 Pre-launch readiness (gate before either raffle opens)

A go or no-go checklist the admin status-transition guard enforces (section 2.6), combining the technical and the legal launch-blockers:

- Legal chain clear for this event: classified as rifa con fines sociales, prizes locked and valued, MININTER Formulario 6 filed and resolution in hand (silencio negativo means no resolution equals denied, so the reference must be real, not assumed), DGIN veedor coordinated for the draw, bases published and matching the filing, pricing frozen (Legal launch-blockers 1 to 6; decisions 1 and 6).
- Payments live: provider onboarded with Teleton's RUC and bank account, Yape prominent, webhooks configured and signature-verified, sandbox-tested success, failure, and abandoned outcomes, and the in-app-webview purchase tested inside Instagram and TikTok browsers (NFR-5.1.3, Payments brief).
- Data protection live: privacy policy published and linked, unbundled consent checkbox on the form, banco de datos registered, breach-notification path and template ready, audit log on, least-privilege roles assigned (Legal items 7, 8, 10, 11).
- Accessibility pass completed as a launch gate, not a follow-up, including a mobile screen-reader run end to end through the upsell and the payment return (NFR-5.4.6).
- Monitoring and alerting live (section 8).

### 7.2 Launch day and the viral spike

The August warm-up is itself the load test for September, so it must be instrumented, not just shipped (NFR-5.2.4).

- Before a scheduled influencer live or post, an operator watches the dashboard sparkline and the by-channel tile. The read path is static and CDN-cached so views do not hit app servers (NFR-5.2.1); the operator is watching writes (registrations and confirmations), which is the thin path.
- Abuse posture during the spike: rate limiting and a lightweight challenge (Turnstile or hCaptcha) on the registration and payment-initiation endpoints, plus velocity limits per phone, email, and IP, because a viral money form is exactly when carding attacks appear (Payments brief, "Chargeback and fraud basics"; NFR-5.2.5). The dashboard's failed-in-last-24h tile is the early warning; a spike in tiny failed card charges is the carding signature, and the runbook response is to tighten limits and, if needed, temporarily steer to Yape (a push rail that is non-reversible and not chargeback-exposed).
- Idempotency under retries: mobile users on bad connections retry; the server-side idempotency key per checkout and the webhook dedupe on event id mean retries never double-charge or double-credit (Payments brief). The operator does not need to do anything; this is structural.
- If the provider degrades mid-campaign: the provider sits behind a thin adapter so swapping Culqi for Mercado Pago is a config-and-adapter change, not a rewrite (Payments brief, "Fallback"). The runbook names who can flip it (admin) and that in-flight pending orders reconcile against whichever provider created them.

### 7.3 Draw day

- T minus 1 day: reconciliation must be green; resolve every mismatch. Confirm the DGIN veedor and, if used, the notary. Confirm the bases and the MININTER resolution reference are attached. Confirm the exclusion list (Teleton staff and families) is loaded.
- T minus 1 hour: freeze nothing yet, but stop accepting new entries for that event at `closes_at` so the pool is stable going into the live.
- During the live: run the section 4.2 stepper on camera, showing the seed commitment, the snapshot hash and count, the reveal and match, and the winners. The veedor witnesses; the certificate is generated on lock.
- Immediately after: publish the verifiable record, export winners, and start the winner notification flow (section 5). Begin the 90-day prize-delivery clock (Legal section 5).
- If a winner is unreachable: follow the undeliverable fallback (section 5.4) and, within the bases window, promote the pre-drawn alternate as a logged action.

### 7.4 Post-event

- Run the revenue and winners exports for Teleton finance and for the post-event financial report to the authority (Legal section 1).
- Confirm the Impuesto a los Juegos filing input is handed to finance (10 percent on ticket value, paid within the first 12 business days of the following month, per Legal section 4).
- After the retention period, run the logged anonymization or deletion job for non-winning contact data (Legal item 12).
- Capture lessons from the warm-up that feed September: real conversion burst, real rail mix, whether repeat-buying actually happened (which decides Fork A for September, Requirements section 3).

---

## 8. Monitoring and alerting

Because the failure modes that hurt most are silent (a missed webhook means a buyer paid and got nothing, which is both a trust and a data-subject harm), monitoring is part of the operations layer, not an afterthought.

What is monitored and what fires an alert [inferred thresholds; tune against the August warm-up, which is the live load test]:

- Webhook lag and gaps: time between provider charge and local confirmation. Alert if confirmations stop arriving while registrations continue (a webhook outage), or if the gap exceeds a threshold. This is the single most important alert because it directly maps to buyers paying without getting chances or emails.
- Payment failure rate: alert on an abnormal spike in failed or declined charges, especially small card charges (carding signature, Payments brief). The dashboard tile is the human view; the alert is the automated one.
- Reconciliation mismatches: alert if the nightly job finds any "confirmed locally but not paid at provider" (class 1) or "refunded but not voided" (class 3) mismatch, because both directly corrupt the draw pool.
- Error rates and latency on the write endpoints (registration, payment-initiation, webhook handler): alert on elevated 5xx or latency, the classic spike-overload signal (NFR-5.2). The read path is CDN-cached and less of a concern.
- Email deliverability: alert on a bounce or complaint rate spike, since a wave of bounced confirmations means buyers think they were not entered.
- Security and abuse: alert on rate-limit triggers and velocity-limit hits per phone, email, and IP crossing a threshold.
- Audit-log writes failing: alert if the append-only log ever fails to write, because losing auditability is itself a compliance failure (Legal item 11).
- Breach path readiness: the 48-hour breach-notification requirement (Legal section 2) means the alerting must reach a named human fast, with an escalation path and a pre-written notification template ready, so detection-to-notification fits inside 48 hours.

Alert routing [inferred]: to the operator and admin on a channel they actually watch during a campaign, with the most severe (webhook outage, reconciliation class 1 or 3, audit-log failure) escalating to the admin directly. During a scheduled live, an operator is actively watching the dashboard, so the live human-in-the-loop complements the automated alerts.

---

## 9. How this layer maps to the open decisions

The admin and operations design is intentionally decision-tolerant where the research flagged unresolved choices, so the build can proceed and absorb the answers as config rather than rework:

- Pricing model (decision 1, 6): pricing is editable config per event, frozen before the bases are filed. The two conflicting audio framings do not block the build; they block the freeze, which is a Rodrigo decision.
- Returning-buyer fork (decision 2): the schema captures phone and email on every purchase and the participant search already exists, so Fork B ships now and Fork A is a UI addition for September with no migration.
- Payment provider (decision 3): the provider sits behind a thin adapter; the admin reconciliation, webhook log, and refund actions are provider-agnostic.
- Winners per event and multi-win rule (decision 4): explicit per-event config in the draw setup.
- Draw mechanism and timing (decision 5): the runbook supports a live in-app draw and degrades to recording an external draw with the same certificate.
- Prize display and fulfillment (decision 6): prize `display_status` and substitution are first-class, so late and changing brand prizes are handled without deploys.
- Embedding strategy (decision 7): the admin is a protected route group, unaffected by whether the public landing is embedded by iframe or subpath; the embed choice mainly affects the public flow's cookies and payment redirect, not the back office.
- Email sender (decision 8): the notifications layer is wired to whatever Teleton-controlled domain and service is chosen, with delivery status surfaced.
- Legal and consent copy (decision 9): the admin holds and exports the factual inputs the bases and Formulario 6 need; Teleton legal supplies the text.
- Data ownership and retention (decision 12): named-account access, least-privilege roles, logged exports, and a retention or anonymization job, with the period set by Teleton legal.

## Recommendations
- **Treat the payment webhook plus a nightly reconciliation job as the single source of truth for which chances enter the draw, and build the reconciliation view as a first-class admin screen, not a report.**: The Payments brief is explicit that mobile users close tabs and the browser redirect proves nothing. For a charity, the statement every chance corresponds to confirmed money is the entire reputational point, and only reconciliation backs it. The four mismatch classes (confirmed-but-unpaid, paid-but-pending, refunded-but-not-voided, amount-mismatch) are the exact failure modes that corrupt the pool.
- **Implement the draw as a forward-only stepper using commit-reveal seeded RNG over a frozen, hashed snapshot, with a pre-flight checklist that hard-gates on the DGIN veedor being present and the MININTER resolution being attached.**: NFR-5.3.3 requires a reproducible, documented, auditable draw, and the Legal brief makes the DGIN veedor a binding requirement without which the draw is invalid. Encoding the legal blockers into the software stepper prevents an operator from running an unauthorized or unwitnessed draw live on camera, which would be a national-charity reputational disaster.
- **Make pricing, prizes, and event windows editable configuration per event, with a publish gate that requires priced prizes, frozen pricing, a published bases PDF, and an attached MININTER resolution reference before an event can open.**: The audio states two conflicting pricing framings and prizes are explicitly subject to what brands give, so both must be data, not code (FR-1.2.3, FR-1.9.3). The publish gate wires the Legal launch-blockers into the app so the system cannot accidentally open a raffle that is unpriced, unauthorized, or has ambiguous pricing that cannot be filed in the bases.
- **Capture and store both phone and email on every purchase and build the participant search now, even though the warm-up ships Fork B (flat-price repeat, no recognition).**: It costs nothing extra at the warm-up, keeps post-hoc dedupe and reconciliation possible, gives a reliable prize-handoff fallback when a winner email bounces, and lets Fork A (recognize returning buyers) ship for September as a UI addition with no data migration. This matches Rodrigo own si eso es sencillo, sino forma mas simple framing.
- **Enforce least-privilege roles with PII masked by default, unmasking and PII export as logged actions, and an append-only audit log on every privileged action and payment-status change.**: Ley 29733 plus DS 016-2024-JUS require least-privilege access, documented security measures, and accountability, with fines up to 100 UIT and a 48-hour breach-notification duty. For a national charity the reputational hit of a publicized data sanction exceeds the fine, so access control and audit are compliance controls, not polish.
- **Use the manual-entry path as a controlled, logged exception for genuine offline and direct-Yape money, flagged rail = manual everywhere, and never as a primary intake.**: Social and live distribution will produce direct Yape transfers and cash or corporate donations outside the hosted checkout, but the Payments brief warns that manual reconciliation is an audit and fraud liability. Gating it to operator and admin, requiring a proof reference, logging it, and making it visibly distinct preserves auditability while still capturing the money.
- **Position the upsell and a minimum card or voucher purchase floor as the fee-survival mechanism, steering bare S/5 tickets to Yape, and surface rail mix and fee drag on the dashboard.**: A bare S/5 card sale loses about 83 percent to the S/3.50 Culqi minimum, while S/5 over Yape loses about 3 percent, and bundles above roughly S/12 to S/15 clear the fee cliff. The pre-payment upsell Rodrigo already wants is also what makes the economics viable, so the admin must let Teleton see and tune rail mix and bundle uptake.

## Open Questions
- Pricing must be frozen before the bases del sorteo are filed: confirm the base price, whether extra chances are flat-priced (the recommended S/5-per-chance, matching audio framing B) or discounted, the exact upsell ladder, and whether there is a maximum number of chances per person (decision 1 and 6).
- Returning-buyer fork: confirm Fork B (flat-price repeat, no recognition) for the August warm-up, with Fork A (recognize by phone or email with verification) deferred to September only if the warm-up shows real repeat-buying (decision 2).
- Payment provider and fee schedule: confirm Culqi as primary (or Mercado Pago) and ask in writing for a charity or campaign rate and relief on the S/3.50 minimum, since that floor sets the minimum viable single-chance price (decision 3, Payments brief).
- Winners per event and multi-win rule: confirm one winner per prize (about 5 for the warm-up, about 10 for the main) and whether one person may win more than one prize in the same event, which sets draw-without-replacement behavior (decision 4).
- Draw mechanism and timing: confirm the live in-app draw on camera versus an external draw, the exact draw date and time for each event, who the draw officer is, and coordination plus scheduling of the mandatory DGIN veedor and whether a notary is also used (decision 5, Legal section 1).
- Roles and account list: confirm the four-role model (viewer, operator, draw officer, admin) and who at Teleton holds each, since least-privilege access is a data-protection requirement and named accounts are mandatory for audit.
- Email sender: confirm which email service sends confirmations and winner emails and from which Teleton-controlled domain, so deliverability, bounce webhooks, and the undeliverable worklist can be wired (decision 8).
- Refund policy: confirm Teleton stance (recommended: no refunds except duplicate charge or proven error) and the exact wording for the bases, plus who bears the income tax on in-kind prizes (gross-up vs winner pays), which must be disclosed in the bases (Payments refunds; Legal section 4 and 5).
- Retention period: confirm how long non-winning participant contact data is retained after each draw before the logged anonymization or deletion job runs, and who owns and can export the database (decision 12, Legal item 12).
- Legal and bases ownership: confirm Teleton legal supplies the bases del sorteo, privacy policy, and Ley 29733 consent text, and confirm whether one MININTER Formulario 6 covers both draws or each draw needs its own filing, since the resolution reference is a publish gate in the admin (decision 9, Legal items 3 and 5).
- Non-winner thank-you: confirm whether Teleton wants a post-draw thank-you to non-winning participants (recommended, gated on marketing consent), since the audio does not request it.

## Risks
- [critical] MININTER authorization is not in hand before a draw because the brand-deal-to-prize-list-to-Formulario-6 chain slips. Silencio administrativo negativo means no resolution equals denied, so the draw would be unauthorized and invalid.  -> FIX: The admin publish gate refuses to open an event without the MININTER resolution reference attached and refuses to run a draw without the veedor checklist passed. Operationally, start the MININTER track now and file the complete dossier 4 to 6 weeks before each draw; the software cannot resolve this, only refuse to proceed without it.
- [high] A missed or delayed payment webhook leaves buyers who paid without chances or a confirmation email. On a viral mobile flow this is common and it is both a trust harm and a data-subject harm.  -> FIX: Webhook is the source of truth, idempotent on event id, backed by a nightly reconciliation job that surfaces paid-but-pending charges, plus a monitoring alert that fires when confirmations stop while registrations continue. Operators can re-process the stored webhook payload to credit chances and send the email.
- [high] Carding attack during the viral spike: attackers test stolen cards on cheap S/5 charges, generating chargebacks, fees, and fraudulent entries that pollute the pool.  -> FIX: Rate limiting plus a lightweight challenge on registration and payment-initiation, velocity limits per phone, email, and IP, hosted Checkout with issuer fraud tooling and 3DS on, a dashboard failed-in-24h tile and an alert on small-card-failure spikes, and the option to steer to Yape (a non-reversible push rail) under attack.
- [high] A refunded charge or an invalid entry leaks into the draw pool, compromising fairness and exposing Teleton to an INDECOPI or participant dispute.  -> FIX: Refund webhook voids the associated chances before any draw filter runs; the snapshot includes only confirmed, non-void chances; the reconciliation view flags refunded-but-not-voided as a guardrail; the commit-reveal certificate makes the exact drawn pool publicly verifiable.
- [high] Personal-data exposure or a sanction under Ley 29733 plus DS 016-2024-JUS, where the reputational hit to a national charity exceeds the fine (up to 100 UIT) and the 48-hour breach-notification window is missed.  -> FIX: Least-privilege named-account roles, PII masked by default with logged unmasking and logged PII exports, encryption in transit and at rest, append-only audit log, defined retention with an anonymization job, a portability export path, and a ready breach-notification escalation and template wired into alerting.
- [medium] The bases del sorteo are changed after publication (for example a late prize substitution), which is an INDECOPI infraction and erodes participant trust.  -> FIX: Prize substitution is a first-class, logged action flagged for legal review; the bases carry a substitution-of-equal-or-greater-value clause; reports read published values and never silently rewrite them; the publish gate locks pricing and the bases PDF before the event opens.
- [medium] The manual-entry path becomes a sloppy primary intake for direct Yape and cash, creating unauditable money and fraud exposure that contradicts the charity-grade trust requirement.  -> FIX: Manual entry is gated to operator and admin, requires a proof reference, is flagged rail = manual everywhere including exports, writes to the audit log, and is documented in the UI as a controlled exception, not a primary rail. Reconciliation and the draw record account for it distinctly.
- [medium] The flow breaks inside the Instagram or TikTok in-app webview (cookies, payment redirect, or return), so traffic from the very channels Rodrigo is pushing fails to convert and the failures are invisible without instrumentation.  -> FIX: Test the full purchase inside the Instagram and TikTok in-app browsers as a launch gate, keep the read path static and CDN-cached, capture source channel via UTM so failures are visible per channel on the dashboard, and treat the August warm-up as the instrumented load test for September.
- [medium] An accessibility gap in the flow contradicts Teleton brand (it serves people with disabilities) and becomes a public embarrassment.  -> FIX: Treat a WCAG 2.1 AA pass, including a mobile screen-reader run through the upsell and the payment return, as a launch gate not a follow-up; the draw stepper and admin are also keyboard-operable and screen-reader-announced; respect reduced-motion in the playful animations and the draw reveal.
- [medium] Late prize finalization (prizes are subject to what brands give) blocks event configuration, the bases, and the MININTER filing, jeopardizing the end-of-August and September 12 dates.  -> FIX: Prize records support coming_soon and hidden display states so the public page shows premio por anunciar without a deploy, but market_value is a hard publish gate because the bases and the filing need it. Surface the brand-deal-to-prize dependency to Rodrigo as the schedule-gating item, separate from software readiness.

## Sources
- /Users/sebasbimbi/sebastian-bimbi/projects/teleton/rodrigo-nores-transcript.md
- /Users/sebasbimbi/sebastian-bimbi/projects/teleton/rodrigo-nores-2-transcript.md
- Requirements brief (provided in task): functional and non-functional requirements, pricing ambiguity, returning-buyer fork, data fields, decisions needed
- Payments (Peru) brief (provided in task): provider comparison, fixed-fee problem, idempotency and webhook reconciliation, refunds, fraud, recommended Next.js build shape
- Legal / Compliance (Peru) brief (provided in task): MININTER/DGIN authorization, Ley 29733 plus DS 016-2024-JUS, bases del sorteo, taxes, INDECOPI, compliance checklist
