Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Después abrir <http://localhost:8768>.

## Inscripción y pago

`/inscripcion/` usa el contrato `registration-v3` de PMP Myth Bot. El navegador conserva una
`/inscripcion/` usa el contrato legal `registration-v4` con PMP Myth Bot. El navegador conserva una
clave idempotente hasta recibir una respuesta válida, abre únicamente el checkout específico que
devuelve la API y consulta después un estado canónico con un token separado. Apps Script queda
como importador legacy `registration-v2`; no recibe el formulario actual ni envía emails.
Expand Down Expand Up @@ -51,6 +51,9 @@ GitHub Pages desde `main` / root.
## Documentos legales públicos

Los documentos consumidos por sistemas externos se publican con nombres versionados e inmutables.
Las evidencias HTML exactas de inscripción permanecen en `legal/terms/registration-v3.html` y
`legal/terms/registration-v4.html`; `/politica/` refleja la versión vigente y cada URL/hash
inmutable se coordina con el Bot antes del cutover.
La autorización vigente del bot de Harmonic Myth Projection está en
`legal/terms/bot-v8.pdf`; `bot-v6.pdf` y `bot-v7.pdf` permanecen como evidencia histórica y no se
sobrescriben.
26 changes: 14 additions & 12 deletions assets/hmp-commerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

const REGISTRATION_OPEN = true;
const API_ORIGIN = 'https://bot.harmonicbeacon.com';
const IDEMPOTENCY_KEY = 'hb-registration-v3-idempotency-key';
const STATUS_CONTEXT_KEY = 'hb-registration-v3-status-context';
const IDEMPOTENCY_KEY = 'hb-registration-v4-idempotency-key';
const STATUS_CONTEXT_KEY = 'hb-registration-v4-status-context';
const LEGACY_STATUS_CONTEXT_KEYS = Object.freeze(['hb-registration-v3-status-context']);
const REGISTRATION_TIMEOUT_MS = 15000;
const STATUS_TIMEOUT_MS = 8000;
const TICKET_TAILOR_WIDGET_SCRIPT = 'https://cdn.tickettailor.com/js/widgets/min/widget.js';
Expand Down Expand Up @@ -258,17 +259,18 @@
}

function readStatusContext() {
try {
const value = JSON.parse(storage().getItem(STATUS_CONTEXT_KEY) || 'null');
return value &&
value.schema_version === 'registration-status-context.v1' &&
REGISTRATION_ID_PATTERN.test(value.registration_id || '') &&
STATUS_TOKEN_PATTERN.test(value.commerce_status_token || '')
? value
: null;
} catch (_) {
return null;
for (const key of [STATUS_CONTEXT_KEY, ...LEGACY_STATUS_CONTEXT_KEYS]) {
try {
const value = JSON.parse(storage().getItem(key) || 'null');
if (
value &&
value.schema_version === 'registration-status-context.v1' &&
REGISTRATION_ID_PATTERN.test(value.registration_id || '') &&
STATUS_TOKEN_PATTERN.test(value.commerce_status_token || '')
) return value;
} catch (_) {}
}
return null;
}

async function commerceStatus(context) {
Expand Down
4 changes: 2 additions & 2 deletions assets/meta-pixel.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
es: {
label: 'Preferencias de privacidad',
title: 'Medición y publicidad',
text: 'Usamos el píxel de Meta para medir visitas y compras, y mejorar nuestras campañas. Meta puede usar cookies y datos del dispositivo. Podés aceptar o continuar sin esta medición.',
text: 'Usamos el píxel de Meta para medir visitas, inscripciones completadas y compras pagas, y mejorar nuestras campañas. Meta puede usar cookies y datos del dispositivo. Podés aceptar o continuar sin esta medición.',
policy: 'Ver política de privacidad',
reject: 'No aceptar',
accept: 'Aceptar',
Expand All @@ -39,7 +39,7 @@
en: {
label: 'Privacy preferences',
title: 'Measurement and advertising',
text: 'We use the Meta pixel to measure visits and purchases and improve our campaigns. Meta may use cookies and device data. You can accept or continue without this measurement.',
text: 'We use the Meta pixel to measure visits, completed registrations and paid purchases and improve our campaigns. Meta may use cookies and device data. You can accept or continue without this measurement.',
policy: 'View privacy policy',
reject: 'Decline',
accept: 'Accept',
Expand Down
2 changes: 1 addition & 1 deletion automatizacion-inscripciones/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Inscripciones en Google Sheets

Esta automatización se conserva únicamente para la transición de registros `registration-v2`.
Guarda inscripciones legacy en Google Sheets y no envía emails. La landing `registration-v3`
Guarda inscripciones legacy en Google Sheets y no envía emails. La landing `registration-v4`
usa la API canónica de PMP Myth Bot; Ticket Tailor envía su comprobante y el bot envía el acceso.

## Activación desde info@harmonicbeacon.com
Expand Down
8 changes: 8 additions & 0 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,12 @@ if (fs.existsSync('inscripcion')) {
console.log('✓ Copiado: inscripcion/');
}

// Conservar la política vigente y sus evidencias legales versionadas.
for (const directory of ['politica', 'legal']) {
if (fs.existsSync(directory)) {
copyRecursiveSync(directory, path.join('dist', directory));
console.log(`✓ Copiado: ${directory}/`);
}
}

console.log('\n✅ Build completado en dist/');
64 changes: 64 additions & 0 deletions docs/registration-v4-cutover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Registration v4 cutover

Status: prepared, not authorized for production cutover.

## Canonical legal artifacts

| Version | Public artifact | SHA-256 |
| --- | --- | --- |
| `registration-v3` | `/legal/terms/registration-v3.html` | `0d65e0c03acd635f08c3e04628a19ef0e674e08612e2ac2446502f3b10b6b54e` |
| `registration-v4` | `/legal/terms/registration-v4.html` | `c062b63921bf9a0d175a9770eaeaee647e65347bb3a1188285bc7c3706a1b773` |

The v3 artifact is byte-identical to the policy accepted since August 1, 2026. It must remain
immutable. Existing registration rows already retain their accepted `terms_version` and
`terms_sha256`; migration must not rewrite them.

V4 adds an explicit bilingual disclosure of the consent-gated Meta events `PageView`,
`CompleteRegistration` and `Purchase`, their canonical firing boundaries and the fields that are
never sent to Meta. It does not authorize additional standard events.

## Backend contract Mariano must incorporate

The browser sends `terms_version=registration-v4`. PMP Myth Bot must associate that exact version
with SHA-256 `c062b63921bf9a0d175a9770eaeaee647e65347bb3a1188285bc7c3706a1b773` and the canonical immutable
URL `https://harmonicbeacon.com/legal/terms/registration-v4.html`. `/politica/` serves the same bytes
as the human-facing current policy at cutover time.

The current Bot accepts only one configured terms version. A no-downtime rollout therefore needs a
small compatibility window in which both immutable pairs are accepted:

- `registration-v3` → `0d65e0c03acd635f08c3e04628a19ef0e674e08612e2ac2446502f3b10b6b54e`
- `registration-v4` → `c062b63921bf9a0d175a9770eaeaee647e65347bb3a1188285bc7c3706a1b773`

The server must choose the hash from the submitted, allowlisted version; it must never accept a hash
from the browser. It must store the selected version/hash on the registration exactly as it does
today. Unknown versions remain rejected. Idempotent retries made under v3 must continue resolving to
their original v3 result; a v4 payload uses a version-scoped browser idempotency key.

If dual acceptance is not implemented, registration must be closed for the coordinated cutover. Do
not deploy backend-v4 and landing-v3, or landing-v4 and backend-v3, while registration is open.

## Safe rollout

1. Verify the v3 archive hash and the v4 policy hash from committed bytes.
2. Deploy Bot support for both allowlisted version/hash pairs; keep v3 as the current browser version.
3. Verify synthetic v3 and v4 requests against a non-production environment, including idempotent
retry, legacy v3 confirmation-context recovery and rejection of unknown versions/hashes.
4. Deploy the landing with v4 form, notice and policy.
5. Verify a synthetic v4 registration, stored evidence and checkout handoff end to end.
6. Keep v3 accepted for at least the maximum in-flight browser retry window. Retire it from new
submissions only after confirming there are no unresolved v3 attempts; never delete historical
evidence.

This procedure does not authorize opening registration, enabling commerce or changing a real event.

## Rollback

1. Close new registration before changing only one side of the contract.
2. Restore the landing commit that submits `registration-v3` and restore the Bot's current version to
the v3 pair above.
3. Verify v3 registration in a non-production/synthetic flow, then reopen only with explicit approval.
4. Preserve all v4 rows and the v4 document as historical evidence; do not rewrite or delete them.

When dual acceptance is active, a landing rollback to v3 does not require an emergency Bot rollback:
the Bot can continue accepting both exact pairs until the incident is understood.
2 changes: 1 addition & 1 deletion inscripcion/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ <h3 data-copy="es">Tu nombre en este viaje</h3><h3 data-copy="en">Your name in t
<input type="hidden" name="eventCode" id="eventCodeInput" value="hmp-2026-08-08">
<input type="hidden" name="sessionCode" id="sessionCodeInput" value="">
<input type="hidden" name="locale" id="localeInput" value="es">
<input type="hidden" name="registrationTermsVersion" value="registration-v3">
<input type="hidden" name="registrationTermsVersion" value="registration-v4">
<input type="text" name="_honey" tabindex="-1" autocomplete="off" style="display:none">
<div class="form-row"><label><span data-copy="es">nombre</span><span data-copy="en">first name</span><input name="firstName" autocomplete="given-name" required></label><label><span data-copy="es">apellido</span><span data-copy="en">last name</span><input name="lastName" autocomplete="family-name" required></label></div>
<label><span data-copy="es">correo electrónico</span><span data-copy="en">email address</span><input type="email" name="email" autocomplete="email" required></label>
Expand Down
Loading