# Integração demonstrativa ENTREPLANO

A ENTREPLANO registra briefings fictícios de arquitetura. A implementação começa em modo local, sem endpoint. Ela não inicia projeto, visita ou contratação e deve receber somente nome e telefone inventados. O contrato completo está em `content/integration.md`; os campos e catálogos canônicos estão em `content/site.json`.

## Configuração

1. Crie um projeto Google Apps Script e copie `gas/Code.gs` para o editor. Use o runtime V8.
2. Execute `setup` com a conta que administrará a demonstração. A função cria uma planilha, configura colunas como texto e registra propriedades privadas. Não publique o retorno ou os segredos.
3. Em Script Properties, confira `SPREADSHEET_ID`, `CREATE_TOKEN`, `ADMIN_TOKEN`, `CANCEL_SECRET` e `RATE_LIMIT_PER_MINUTE`. O limite padrão é 60 operações por minuto por papel; o intervalo permitido é 1 a 600. Defina rotação, revogação, retenção e permissões conforme o destino. Nunca coloque tokens em variáveis públicas, URLs, logs ou arquivos distribuídos.
4. Publique como aplicativo da web sob a conta responsável, com acesso compatível com as requisições da demonstração. A autenticação das operações ocorre no envelope POST. URL conhecida não concede autoridade.
5. Configure apenas `PUBLIC_DEMO_GAS_ENDPOINT` com a URL HTTPS `https://script.google.com/macros/s/IDENTIFICADOR/exec` e gere novamente o build. A variável começa vazia em `.env.example`.
6. Em `/demo/#acesso`, informe a autorização de teste. Escolha o destino remoto, confira a URL e marque o aceite de envio separado antes de revisar e registrar. A autorização fica somente em memória e precisa ser informada novamente após reload.

Não use `no-cors` ou proxy para esconder uma resposta ilegível. A resposta precisa ser legível e validada no navegador. Um health positivo não comprova recebimento, CORS, persistência ou idempotência.

## Planilha e colunas

`setup` cria três abas e preserva dados existentes se encontrar colunas incompatíveis:

| Aba | Colunas, na ordem |
| --- | --- |
| `pedidos` | RecordId, BrandId, RequestId, Fingerprint, PayloadJson, Status, CreatedAt, CancelledAt, CancelHash, DeletedAt |
| `tentativas` | BrandId, RequestId, Fingerprint, State, RecordId, CreatedAt |
| `operacoes` | OperationId, Kind, RecordId, RequestId, State, CreatedAt |

`PayloadJson` contém exatamente `schemaVersion,brandId,requestId,kind,projectType,stage,projectId,name,phone,demoAcknowledged`. `kind` é `architecture_brief`. Etapa e referência opcionais são `null`. Referências precisam pertencer ao tipo selecionado. Não há relato livre, endereço exato, documentos, arquivos, agenda ou orçamento numérico. Strings são gravadas em colunas configuradas como texto e o payload é preservado como JSON canônico.

## Operações

GET aceita somente `op=health`, com resposta `{ok,schemaVersion,brandId}` sem dados pessoais. POST recebe exatamente `{op,authToken,payload}` em `text/plain;charset=UTF-8`, com corpo de até 8.192 bytes. Tokens e capabilities têm limite de 512 pontos de código, sem controles.

| Operação | Payload | Papel |
| --- | --- | --- |
| `register_brief` | Os dez campos normalizados | Usuário |
| `request_status` | brandId, requestId, fingerprint | Usuário ou admin |
| `cancel_record` | brandId, requestId, recordId, operationId, cancelCapability | Usuário ou admin com capability |
| `list_records` | brandId | Admin |
| `delete_record` | brandId, recordId, operationId | Admin |

Cada resposta privada vincula versão, marca, operação e resultado. Criação/status vinculam requestId e SHA-256 do JSON canônico; cancelamento/exclusão vinculam também registro e operationId. HTTP 200 não comprova sucesso. Os resultados `received` e `cancelled` exigem registro válido, estado correspondente, identidade e fingerprint corretos.

## Persistência e recuperação

O backend usa Script Lock e um ledger durável antes do efeito. Repetir o mesmo código e payload devolve o registro anterior; payload diferente não altera a tentativa. Recuperação repara interrupções entre registro e ledger. Cancelamento e exclusão conservam proteção contra replay; um teste cancelado não volta a recebido.

No navegador, guard síncrono antecede hashing, locks e rede. A intenção é salva e relida antes do envio. Resposta opaca, HTML, timeout, not-found, fingerprint divergente ou rejeição incompleta conservam a tentativa pendente, bloqueiam alterações e permitem apenas verificar a mesma tentativa. Reload preserva código, payload e destino, sem persistir autorização. Sem Web Locks, a interface informa o limite de coordenação entre abas.

Rejeição terminal requer `ok:false,outcome:rejected,definitive:true,accepted:false,tombstoned:true` e identidade correspondente, sem registro. Só após essa prova a interface oferece um novo teste local com novo código. Falha de cache depois de aceitação remota mantém a tentativa bloqueada para recuperar o mesmo registro. A capability de cancelamento fica em memória e pode ser recuperada por status privado autorizado.

`/demo` mostra a cópia deste navegador; a consulta administrativa remota é uma visão separada. Excluir ou limpar a cópia local não apaga dados remotos. Cancelar, excluir e limpar exigem confirmação. Contagens globais não mudam com filtros.

## Limites

O código é um exemplo configurável. Sua distribuição não comprova endpoint ativo, recebimento externo, segurança comercial, conformidade profissional ou persistência em Sheets. Valide um destino autorizado com dados inventados, incluindo efeito e retorno, antes de afirmar que a integração funciona. Não forneça credenciais comerciais ou dados reais a esta demonstração.
