# Integração demonstrativa MORA Imóveis

O padrão é local: nenhuma planilha é acessada. O destino opcional registra pedidos fictícios de contato, sem agendamento, anúncio real ou garantia de retorno. Use somente dados inventados. O contrato completo está em `content/integration.md`; os nove campos públicos são definidos por `content/site.json`.

## Instalação local

Requer Node.js compatível com Astro 7 e npm. Execute `npm ci`, `npm run check`, `npm test` e `npm run build`. Use `npm run dev` para desenvolvimento ou `npm run serve` para servir o `dist` existente. `PUBLIC_DEMO_GAS_ENDPOINT` permanece vazio em `.env.example`. Configurar um endereço requer novo build; tokens nunca usam variáveis `PUBLIC_*`.

## Preparar o Apps Script

Crie um projeto Google Apps Script, copie `gas/Code.gs` e execute `setup` manualmente. Autorize acesso ao Spreadsheet e Script Properties. A função cria uma planilha e as quatro abas, sem dados demonstrativos. Preserve os cabeçalhos; `setup` rejeita colunas incompatíveis sem reescrever registros.

As colunas, na ordem, são:

- `Records`: schemaVersion, brandId, requestId, kind, propertyId, turn, name, phone, demoAcknowledged, recordId, createdAt, status, cancelledAt, mode, purpose, propertyCode, price, pricePeriod, priceIllustrative, fingerprint.
- `Ledger`: brandId, requestId, fingerprint, state, recordId, accepted, tombstoned, createdAt, updatedAt.
- `Operations`: brandId, operationId, op, requestId, recordId, fingerprint, state, createdAt, updatedAt.
- `Capabilities`: brandId, requestId, recordId, capabilityHash, revoked, createdAt.

Configure nas Script Properties privadas `MORA_USER_TOKEN_HASH` e `MORA_ADMIN_TOKEN_HASH` com SHA-256 hexadecimal minúsculo de dois tokens distintos, longos e aleatórios. Não coloque tokens no código, URL, storage ou arquivos públicos. `MORA_SPREADSHEET_ID` identifica a planilha criada. `setup` também cria `MORA_CAPABILITY_SECRET`, segredo aleatório usado para derivar permissões recuperáveis, e `MORA_RATE_LIMIT_PER_MINUTE`, limite por papel entre 1 e 600, padrão 60.

Os hashes de proprietário ficam em `MORA_OWNER_<requestId>` e vinculam cada tentativa ao token que a criou. O token de administrador autoriza leitura/exclusão privadas. Um token de usuário compartilhado representa um único escopo de teste e não estabelece identidade individual; uma operação multiusuário exige autorização própria. O exemplo não oferece autenticação comercial. Não apague os vínculos de proprietário, ledger, operações ou segredo de capability enquanto tentativas puderem ser repetidas. A capacidade máxima das Script Properties e quotas do Apps Script limitam este exemplo; defina retenção, revogação e monitoramento próprios antes de qualquer uso diferente do teste.

## Web App e navegador

Publique uma implantação Web App, executando como o proprietário autorizado e com acesso compatível com o teste pretendido. As permissões da conta Google e a política de acesso da implantação são separadas dos tokens privados. Copie somente a URL `https://script.google.com/macros/s/.../exec` para `PUBLIC_DEMO_GAS_ENDPOINT` e gere o build. Alterações no GAS exigem nova versão da implantação. A configuração não atesta disponibilidade.

`GET ?op=health` retorna apenas `ok`, `schemaVersion` e `brandId`. Outros dados são privados. Na página `/demo/`, informe o token somente em RAM, escolha o modo remoto e marque o opt-in separado antes de registrar dados fictícios. Remover token ou recarregar a página exige nova autorização, mantendo a tentativa pendente. O painel local e a consulta administrativa remota são separados; não há migração automática.

POST usa envelope `{op,authToken,payload}` com `Content-Type: text/plain;charset=UTF-8`. O corpo tem limite de 8192 bytes e os tokens/capabilities de 512 pontos de código. GAS pode redirecionar a resposta. Verifique que o navegador consegue ler o JSON da implantação e que as permissões estão corretas: GAS não permite prometer configuração arbitrária de headers CORS. `no-cors`, resposta opaca, HTML, timeout ou HTTP 200 isolado não provam persistência. Sem resposta verificável, mantenha a tentativa e reconcilie com os mesmos dados e nonce.

## Persistência, recuperação e limites

O catálogo embutido no GAS é gerado por `node scripts/catalog.mjs` a partir do contrato; finalidade e preço são derivados no servidor. O cliente não envia valores comerciais. Strings são gravadas como RichText literal para impedir interpretação de fórmulas, inclusive nomes começando com `=`. Campos opcionais vazios são convertidos para null ao recuperar o registro.

O Script Lock protege intenção, registro e ledger. Interrupção após gravação do registro é reparada pela mesma chave; o replay não duplica, ressuscita cancelados ou recria excluídos. A exclusão administrativa grava tombstone antes de remover a linha e revoga capability. A exclusão remove dados pessoais da aba Records e mantém os identificadores necessários à proteção de replay. Operações reutilizam operationId; a capability permanece somente em RAM no navegador e seu hash no servidor. Em erro de leitura ou gravação, não tratar uma resposta incompleta como sucesso.

Sheets não tem transação entre abas. Escritas literais e releituras têm custo de execução e quota. Rate limit não substitui autorização, contenção de abuso ou revisão operacional. Não armazenar logs com dados ou tokens. Saúde pública positiva não valida criação, cancelamento, idempotência ou disponibilidade externa. Testes locais de contrato não equivalem a testes reais de GAS/Sheets.

## Pacote e dados locais

O ZIP inclui fonte, lockfile, configuração, imagens WebP, fontes com OFL e documentação. Não inclui registros do navegador, node_modules, dist, masters PNG, capturas de referências ou credenciais. `npm run package` prepara os quatro downloads e habilita somente seus quatro flags autorizados quando os arquivos existem; execute antes do build final. `npm run proof` compara fonte/downloads/ZIP e hashes de todo dist.

A comparação de arquivos protegidos usa o baseline quando ele está presente no diretório de evidências. Esse histórico não é distribuído no ZIP. Sem baseline, a prova declara essa comparação como não executada e continua verificando os downloads, o ZIP, os contratos embutidos e os arquivos de dist. Para gerar novamente a distribuição a partir da fonte baixada, execute `npm run package`, `npm run build` e `npm run proof`, nessa ordem.

Favoritos têm namespace próprio e não são leads. Solicitações usam ledger e intenção duráveis. Falha de quota ou corrupção preserva bytes e não confirma operação. Limpeza local é confirmada, bloqueada com pendência, não exclui remoto e não altera favoritos. A exclusão individual conserva proteção contra replay; limpar o histórico local remove essa proteção conforme aviso da interface.
