# Pedidos de interesse · PULSO

## Escopo

Esta experiência demonstra interesse em visita à academia ou conversa sobre personal. Use somente dados de teste. Não há calendário, reserva, cobrança, mensagem, promessa de retorno ou operação comercial real. O painel `/demo/` lê dados deste navegador; não lista automaticamente clientes de Sheets.

## Implantar Google Apps Script

1. Crie um projeto Apps Script, copie `Code.gs` integralmente e salve. O catálogo embarcado é derivado de `content/site.json` durante o empacotamento; não substitua IDs por nomes de outro projeto.
2. Nas configurações do projeto, defina `SPREADSHEET_ID` com uma planilha privada existente ou deixe a propriedade ausente para `setup()` criar uma planilha. Execute `setup()` pelo editor, autorize o acesso e preserve as abas e colunas geradas. Não exponha a planilha por link público.
3. `setup()` gera `LEAD_TOKEN`, `ADMIN_TOKEN` e `CANCEL_SECRET` em Script Properties se estiverem ausentes. Consulte-os somente no editor autorizado. `LEAD_TOKEN` autoriza criação, consulta e cancelamento; `ADMIN_TOKEN` autoriza listagem e exclusão. O segredo de cancelamento deriva uma capacidade HMAC por marca, pedido e nonce, validada pelo hash armazenado. Não coloque esses valores no source, URL, logs, export ou variáveis `PUBLIC_*`. Tokens e capacidades do navegador ficam apenas em RAM.
4. Implante como Web App, executando como o proprietário. Escolha a permissão de acesso compatível com seus testadores e a política da organização. O acesso HTTP, mesmo aberto para transportar o formulário, não substitui os tokens exigidos pelo código. Use a URL final `/exec`, não `/dev`. Um Apps Script privado com login interativo pode exigir um intermediário autenticado para consumo pelo navegador.
5. Preencha `PUBLIC_DEMO_GAS_ENDPOINT` no ambiente de build com a URL `/exec`; refaça `npm run package` e `npm run build`. O modo local continua padrão. A interface só oferece remoto para uma URL válida e exige a escolha explícita, destino visível e token antes de enviar.
6. Teste de um navegador autorizado: POST com `Content-Type: text/plain;charset=UTF-8`, resposta JSON legível, marca/schema, nonce e estados corretos. Não use `no-cors`: resposta opaca, redirecionamento de login ou HTTP 200 sozinho não confirmam sucesso. GET `?op=health` retorna só `{ok,schemaVersion,brandId}`.

## Abas e colunas exatas

`pedidos`, nesta ordem:

`RecordId`, `BrandId`, `RequestId`, `Fingerprint`, `Intent`, `ServiceId`, `ProfessionalId`, `PreferredShift`, `NameJson`, `Phone`, `Status`, `CreatedAt`, `CancelledAt`, `CancelHash`.

`tentativas`, nesta ordem:

`BrandId`, `RequestId`, `Fingerprint`, `State`, `RecordId`, `CreatedAt`, `CancelOperationId`, `DeletedAt`.

Todas as colunas são formatadas como texto. `NameJson` contém o nome codificado como string JSON para evitar fórmulas de planilha; não digite nomes diretamente nessa coluna. IDs e datas são gerados no servidor. Preferências vazias são strings vazias na planilha e `null` no contrato HTTP. Status de pedidos ativos são `received` ou `cancelled`. Exclusão administrativa sanitiza nome/telefone e marca a linha como `deleted`, invisível à listagem. O ledger conserva o tombstone `rejected` para impedir recriação por retry antigo. Não renomeie nem reordene colunas; `setup()` rejeita cabeçalhos incompatíveis e preserva os dados.

## Contrato privado

Envelope: `{op,authToken,payload}`. Campos de criação: `schemaVersion`, `brandId`, `requestId`, `kind`, `intent`, `serviceId`, `professionalId`, `preferredShift`, `name`, `phone`, `demoAcknowledged`. `kind` é `lead`; schema é `1.1.0`; marca é `pulso-academia-personal-demo`. Propriedades desconhecidas, booleanos coercíveis, IDs estranhos e seleções incompatíveis são rejeitados. Nome: 2–80 pontos de código Unicode após trim. Telefone: até 20 caracteres brutos, normalização para 10–11 dígitos. Aceite: booleano `true`.

`visita`: `musculacao`, `funcional`, `mobilidade` ou `null`; personal obrigatoriamente `null`. `personal`: `personal-individual`, `personal-dupla` ou `null`; `lia-ramos`, `caio-nunes` ou `null`, respeitando elegibilidade. Turno: `manha`, `tarde`, `noite` ou `null`. Nenhuma preferência representa disponibilidade.

`register_lead` usa esse payload completo. `request_status` usa `{brandId,requestId}`. `cancel_record` usa `{brandId,requestId,recordId,operationId,cancelToken}` e exige token privado mais capacidade do pedido. `list_records` usa `{brandId,requestId}` com token administrativo. `delete_record` usa `{brandId,requestId,recordId}` com token administrativo. Essas operações são POST; GET não fornece PII.

O ScriptLock serializa leitura e escrita. A criação grava intenção no ledger antes da linha de pedido, e o retry recupera pelo mesmo nonce após falha entre gravações. Sheets não fornece transação. O fingerprint SHA-256 usa a ordem fixa dos campos do catálogo, normalizados, sem token. Mesmo nonce/payload devolve o original; alteração produz `request_mismatch`, exigindo reconciliação. Consulta de nonce sem registro grava rejeição terminal sob lock, impedindo que um envio atrasado crie o pedido. Cancelamento é repetível, preserva timestamp e operação; timeout não altera a confirmação na interface.

## Limites, retenção e privacidade

A planilha e os tokens são de uma demonstração compartilhada. Token de criação permite consultar nonce autorizado; token administrativo é mais amplo. Não trate isso como isolamento de clientes ou autenticação comercial. O código aplica 4.096 caracteres por requisição e 60 operações/minuto por papel; não substitui controle de abuso e identidade em uma implantação pública. Apps Script não disponibiliza controle confiável de Origin nem headers CORS customizados; quando sua política exigir uma allowlist de origens, use intermediário autenticado com rate limit, sem ampliar o GET público.

O operador define acesso aos editores e retenção. Exclua os dados de teste pela operação administrativa: ela sanitiza PII e mantém o ledger de dedupe. Não apague tombstones enquanto um retry antigo puder existir. Não há expiração automática de capacidades ou cron de retenção. Mantenha `CANCEL_SECRET` estável enquanto houver pedidos: a rotação exige reconciliar hashes/capacidades dos registros sob controle do operador. Revogue tokens após a rodada de testes sem apagar pendências de navegador antes da resolução. Dados locais podem ser cancelados/apagados em `/demo/`; isso não remove dados remotos.

Falhas de rede ou resposta inválida conservam payload e nonce no armazenamento local. As ações conflitantes ficam bloqueadas até consulta ou retry do mesmo ID. O token deve ser informado novamente após recarga. Não invente um resultado a partir de timeout nem apague a pendência para forçar fallback. Resultados remotos simulados por interceptação não comprovam implantação `/exec` real.
